gpt4 book ai didi

design-patterns - 在通用存储库上使用Decorator模式实现的AOP

转载 作者:行者123 更新时间:2023-12-05 00:28:03 26 4
gpt4 key购买 nike

我正在尝试构建一个使用Decorators将面向方面编程应用于我的项目的原型。我项目的某些部分将使用通用存储库(用于简单的CRUD),但最终我还将结合使用Command和Query处理程序(这些处理程序将执行诸如ProcessCustomerOrders等特定任务)。另外,我想在此举例说明的跨领域问题是安全性和日志记录。


另外,我知道我的示例代码不是使用Decorator模式,而只是为该原型提供上下文的代码示例。


我知道还有其他方法可以实现AOP(或跨领域问题),例如Proxy或Code Weaving模式,但我不熟悉这些模式,因此不知道它们之间的取舍。

我在这里使用控制台应用程序只是为了展示如果我以连锁的方式“新建”它们时的外观。

我的问题是:


(1)如何使用Simple Injector(在bootstrap类中)进行连接,并且仍然保持顺序相同?

(2)这是否正确使用了Decorator Pattern(因为我没有使用基抽象或接口类或装饰器基)?

(3)是否有一种干净的方法可以在同一个存储库中使用ILogger服务的多个实现(例如DatabaseLogger和ConsoleLogger),而无需注入两个不同的版本?

(4)实际的日志记录是通过Repository方法实现的,并且将ILogger服务注入到Repository类中,但是有比硬连接记录器并仍然使用通用Repository更好的方法吗?

(5)是否应根据我在此原型中使用存储库的方式使用代理模式或代码编织模式?


同样,也欢迎对此设计提出一般批评。

原型代码:

public class Program
{
public static void Main(string[] args)
{
var e = new Entity
{
Id = 1,
Name = "Example Entity",
Description = "Used by Decorators",
RowGuild = Guid.NewGuid()
};

Controller controller =
new Controller(
new GenericRepository<Entity>(
new ClientManagementContext(),
new ConsoleLogger()
),
new WebUser()
);

controller.Create(e);
}
}

public static class RepositoryBoostrapper
{
public static void Bootstrap(Container container)
{
container.RegisterOpenGeneric(typeof(IGenericRepository<>), typeof(GenericRepository<>));
}
}

public class Entity
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Guid RowGuild { get; set; }
public byte[] RowVersion { get; set; }
}

public class Controller
{
private readonly IGenericRepository<Entity> _repository;
private readonly IUserSecurity _userSecurity;

public Controller(IGenericRepository<Entity> repository, IUserSecurity userSecurity)
{
_repository = repository;
_userSecurity = userSecurity;
}

// Displays all Entities on a web page view
public ActionResult Index() {
IEnumerable<Entity> e = null;
User user = User.Identity.Name;

if (_userSecurity.ValidateUser(user))
{
e = _repository.ReadTs();
}
return View(e);
}

public ActionResult Create(Entity e) {
User user = User.Identity.Name;

if (_userSecurity.ValidateUser(user))
{
if (ModelState.IsValid)
{
_repository.CreateT(e);
return RedirectToAction("Index");
}
}
return View(e);
}
}

public interface IGenericRepository<T>
{
T ReadTById(object id);
IEnumerable<T> ReadTs();
void UpdateT(T entity);
void CreateT(T entity);
void DeleteT(T entity);
}

public class GenericRepository<T> : IGenericRepository<T> where T : class
{
private readonly ClientManagementContext _context;
private readonly ILogger _logger;

public GenericRepository(ClientManagementContext context, ILogger logger)
{
_context = context;
_logger = logger;
}

public T ReadTById(object id) {
return _context.Set<T>().Find(id);
}

public IEnumerable<T> ReadTs() {
return _context.Set<T>().AsNoTracking().AsEnumerable();
}

public void UpdateT(T entity) {
var watch = Stopwatch.StartNew();

_context.Entry(entity).State = EntityState.Modified;
_context.SaveChanges();

_logger.Log(typeof(T).Name +
" executed in " +
watch.ElapsedMilliseconds + " ms.");
}

public void CreateT(T entity) {
var watch = Stopwatch.StartNew();

_context.Entry(entity).State = EntityState.Added;
_context.SaveChanges();

_logger.Log(typeof(T).Name +
" executed in " +
watch.ElapsedMilliseconds + " ms.");
}


public void DeleteT(T entity) {
_context.Entry(entity).State = EntityState.Deleted;
_context.SaveChanges();
}
}



public class Logger
{
private readonly ILogger _logger;

public Logger(ILogger logger)
{
_logger = logger;
}

public void Log(string message)
{
_logger.Log(message);
}
}

public interface ILogger
{
void Log(string message);
}

public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}

public class DatabaseLogger : ILogger
{
public void Log(string message)
{
// database logging
}
}

public interface IUserSecurity
{
bool ValidateUser(User user);
}

public class UserSecurity
{
private readonly IUserSecurity _userSecurity;

public UserSecurity(IUserSecurity userSecurity)
{
_userSecurity = userSecurity;
}

public bool ValidateUser(User user)
{
return _userSecurity.ValidateUser(user);
}
}

public class WebUser : IUserSecurity
{
public bool ValidateUser(User user)
{
// validate MVC user

return true;
}
}


更新基于@Steven的答案:

装饰器和存储库的简单注入器DI:

public static class RepositoryBoostrapper
{
public static void Bootstrap(Container container)
{
container.RegisterOpenGeneric(
typeof(IGenericRepository<>),
typeof(GenericRepository<>));

container.RegisterDecorator(
typeof(IGenericRepository<>),
typeof(LoggingRepositoryDecorator<>));

container.RegisterDecorator(
typeof(IGenericRepository<>),
typeof(SecurityRepositoryDecorator<>));

}
}


由Controller调用的Decorator链的顺序应为Controller(检查)> Security(如果可以继续,则允许调用)> Repo(更新持久层,然后)> Log(到某个设施)>然后返回给控制器。

新的控制器类:

public class Controller
{
private readonly IGenericRepository<Entity> securityGenericRepository;

public Controller(
IGenericRepository<Entity> securityGenericRepository)
{
this.securityGenericRepository = securityGenericRepository;
}

// Displays all Entities on a web page view
public bool Index() {
var e = new Entity
{
Id = 1,
Name = "Example Entity",
Description = "Used by Decorators",
RowGuild = Guid.NewGuid()
};
this.securityGenericRepository.CreateT(e);
return false;
}

public ActionResult Create(Entity e) {
if (ModelState.IsValid)
{
this.securityGenericRepository.CreateT(e);
return RedirectToAction("Index");
}
return View(e);
}
}


有关上述代码摘录的问题:

如果我想根据返回值在Controller中执行某些操作(例如,从安全装饰器返回布尔值),那么是否必须修改IGenericRepository接口(以及因此的GenericRepository类)?从某种意义上说,由于Repo和Security Decorator类都实现了相同的接口,因此,如果我想更改Security方法的返回值或参数,还需要更改Repository方法吗?

另外,我现在是否仅将IGenericRepository的安全性实现传递给控制器​​?

此外,记录器已更改为如下所示:

public class LoggingRepositoryDecorator<T> : IGenericRepository<T>
{
private readonly IGenericRepository<T> decoratee;
private readonly ILogger logger;

public LoggingRepositoryDecorator(IGenericRepository<T> decoratee, ILogger logger)
{
this.decoratee = decoratee;
this.logger = logger;
}

// ...

public void CreateT(T entity)
{
var watch = Stopwatch.StartNew();

this.decoratee.CreateT(entity);

this.logger.Log(typeof(T).Name + " executed in " +
watch.ElapsedMilliseconds + " ms.");
}
// ...
}


在上面,我只是调用Decoratee,并在顶部添加Decorator的功能。

最后是安全装饰器:

public class SecurityRepositoryDecorator<T> : IGenericRepository<T>
{
private readonly IGenericRepository<T> decoratee;
private readonly IUserSecurity userSecurity;
private User user;

public SecurityRepositoryDecorator(
IGenericRepository<T> decoratee,
IUserSecurity userSecurity)
{
this.decoratee = decoratee;
this.userSecurity = userSecurity;
this.user = User.Identity.Name;
}

// ...

public void CreateT(T entity)
{
if (userSecurity.ValidateUser(user))
this.decoratee.CreateT(entity);
}
// ...
}


我上面不明白的是,记录器在哪里/何时被调用?

更新2:

似乎可以像现在的装饰器模式一样工作;感谢史蒂文的所有出色解答。

原型主要功能:

public static void Main(string[] args)
{
var container = new Container();
PrototypeBoostrapper.Bootstrap(container);

IRepository<Entity> repository =
new ValidateUserDecorator<Entity>(
new LoggingDecorator<Entity>(
new Repository<Entity>(
new PrototypeContext()),
new ConsoleLogger()),
new ClaimsPrincipal());

var controller = new Controller(repository);

var e = new Entity
{
Id = 1,
Name = "Example Entity",
Description = "Used by Decorators",
RowGuild = Guid.NewGuid()
};

controller.Create(e);
}


验证(安全)装饰器:

public class ValidateUserDecorator<T> : IRepository<T>
{
private readonly IRepository<T> decoratee;
//private readonly IUserSecurity userSecurity;
private IPrincipal User { get; set; }

public ValidateUserDecorator(
IRepository<T> decoratee,
IPrincipal principal)
{
this.decoratee = decoratee;
User = principal;
}

//..
public void CreateT(T entity)
{
if (!User.IsInRole("ValidRoleToExecute"))
throw new ValidationException();
this.decoratee.CreateT(entity);
}
//..


记录装饰器:

public class LoggingDecorator<T> : IRepository<T>
{
private readonly IRepository<T> decoratee;
private readonly ILogger logger;

public LoggingDecorator(IRepository<T> decoratee, ILogger logger)
{
this.decoratee = decoratee;
this.logger = logger;
}

// ..
public void CreateT(T entity)
{
var watch = Stopwatch.StartNew();

this.decoratee.CreateT(entity);

this.logger.Log(typeof(T).Name + " executed in " +
watch.ElapsedMilliseconds + " ms.");
}
// ..


通用存储库:

public class Repository<T> : IRepository<T> where T : class
{
private readonly PrototypeContext _context;

public Repository(PrototypeContext context)
{
_context = context;
}
//..
public void CreateT(T entity) {
_context.Entry(entity).State = EntityState.Added;
_context.SaveChanges();
}
//..


控制器:

public class Controller
{
private readonly IRepository<Entity> repository;

public Controller(
IRepository<Entity> repository) {
this.repository = repository;
}
// ..
public bool Create(Entity e) {
this.repository.CreateT(e);
return true;
}
// ..

最佳答案

(1)如何使用Simple Injector(在引导程序中)进行连接
类),并且仍然保持相同的顺序,


Simple Injector包含一个RegisterDecorator方法,可用于注册装饰器。已注册的装饰器(被保证)按照其注册顺序进行应用。例:

container.RegisterOpenGeneric(
typeof(IGenericRepository<>),
typeof(GenericRepository<>));

container.RegisterDecorator(
typeof(IGenericRepository<>),
typeof(LoggingRepositoryDecorator<>));

container.RegisterDecorator(
typeof(IGenericRepository<>),
typeof(SecurityRepositoryDecorator<>));


此配置确保每次请求 IGenericRepository<T>时,都返回一个 GenericRepository<T>,该 LoggingRepository<T>包裹有一个 SecurityRepository<T>,该 GenericRepository<T>ILogger包裹。最后注册的装饰器将是最外面的装饰器。


(2)这是否正确使用了装饰器模式(因为我不是
使用基本抽象或接口类或装饰器基)


我不确定您目前的状况如何;我在您的代码中看不到任何修饰符。但是有一件事是错误的。您的 securityGenericRepository使用 fooGenericRepository,但是记录是一个跨领域的问题。它应该放在装饰器中。该装饰器可能如下所示:

public LoggingRepositoryDecorator<T> : IGenericRepository<T> {
private IGenericRepository<T> decoratee;
private ILogger _logger;

public LoggingRepositoryDecorator(IGenericRepository<T> decoratee,
ILogger logger) {
this.decoratee = decoratee;
this._logger = logger;
}

public T ReadTById(object id) { return this.decoratee.ReadTById(id); }
public IEnumerable<T> ReadTs() { return this.decoratee.ReadTs(); }

public void UpdateT(T entity) {
var watch = Stopwatch.StartNew();

this.decoratee.UpdateT(entity);

_logger.Log(typeof(T).Name + " executed in " +
watch.ElapsedMilliseconds + " ms.");
}

public void CreateT(T entity) {
var watch = Stopwatch.StartNew();

this.decoratee.CreateT(entity);

_logger.Log(typeof(T).Name + " executed in " +
watch.ElapsedMilliseconds + " ms.");
}

public void DeleteT(T entity) { this.decoratee.DeleteT(entity); }
}



(3)是否有一种干净的方法来利用多个实现
ILogger服务(例如DatabaseLogger和ConsoleLogger)在
相同的存储库,而无需注入两个不同的版本?


这取决于您的需求,但是 Composite PatternProxy pattern在这里可能会有所帮助。 Composite模式允许您将事物的集合隐藏在事物接口的后面。例如:

public class CompositeLogger : ILogger {
private readonly IEnumerable<ILogger> loggers;

public CompositeLogger(IEnumerable<ILogger> loggers) {
this.loggers = loggers;
}

public void Log(string message) {
foreach (var logger in this.loggers) {
logger.Log(message);
}
}
}


您可以如下注册:

// Register an IEnumerable<ILogger>
container.RegisterCollection<ILogger>(new[] {
typeof(DatabaseLogger),
typeof(ConsoleLogger)
});

// Register an ILogger (the CompositeLogger) that depends on IEnumerable<ILogger>
container.Register<ILogger, CompositeLogger>(Lifestyle.Singleton);


另一方面,使用代理模式,您可以隐藏一些有关如何在代理中植根消息的决定。例:

public class LoggerSelector : ILogger {
private readonly ILogger left;
private readonly ILogger right;

public LoggerSelector(ILogger left, ILogger right) {
this.left = left;
this.right = right;
}

public void Log(string message) {
var logger = this.SelectLogger(message);
logger.Log(message);
}

private ILogger SelectLogger(string message) {
return message.Contains("fatal") ? this.left : this.right;
}
}


您可以如下注册:

container.Register<ConsoleLogger>();
container.Register<DatabaseLogger>();

container.Register<ILogger>(() => new LoggerSelector(
left: container.GetInstance<ConsoleLogger>(),
right: container.GetInstance<DatabaseLogger>());



(4)实际的日志记录是通过Repository方法实现的,并且
ILogger服务被注入到Repository类中,但是是否有一个
比硬连接记录器并仍然使用更好的方法
通用存储库?


绝对:不要将记录器注入到存储库中,因为这是一个跨领域的问题。您更改日志记录逻辑的时间可能比更改其余通用存储库代码的时间要早​​得多。因此,您应该编写一个装饰器。

令人高兴的是,由于您为存储库创建了通用接口,因此您只需编写一个通用装饰器即可向存储库添加日志记录行为。不幸的是,由于存储库接口有5个成员,所以您的装饰器将需要实现所有这些成员。但是您不能为此责怪装饰工。是 Repository pattern本身违反了 Interface Segregation Principle

更新:


私有只读IGenericRepository securityGenericRepository;


您不应该这样命名存储库。安全性和日志记录是贯穿各领域的问题,用户不必知道它们的存在。如果您决定需要额外的跨领域关注点,而该关注点应在安全性关闭之前触发?您是否要将所有 SecurityException依赖项重命名为 Application_Error?那会破坏使用装饰器的全部目的:它们使您可以动态地插入新的跨领域关注点,而无需在应用程序中更改一行代码。


如果我想根据返回值在Controller中执行一些操作


如果确实需要的话,请认真考虑。特别是为了安全。在那个级别上,您通常只需要检查并引发异常。您不想在控制器中捕获此类异常,更不用说要返回值了。

这样的安全修饰符通常被用作安全机制,以防止恶意行为者对您的系统造成不良影响。扔 SecurityException是正确的事情。此类异常将被记录,并将由您的团队或支持人员选择。您可能想做的是在用户单击其当前角色不允许的按钮时向用户显示友好消息,但应避免向用户显示此按钮。

通过实现 IPromptableCommandHandler<TCommand>事件并检查是否抛出 ICommandHandler<TCommand>并将用户重定向到一个页面,该页面仍可以向用户显示友好消息,该页面解释了他们不幸地尝试访问系统不允许的页面进入。但是IMO,如果用户看到该页面,则说明他们是在“入侵”系统,或者您犯了编程错误。

请记住,装饰器实现与包装相同的抽象。这意味着您不能使用装饰器更改抽象(并且不能返回其他内容)。如果这是您所需要的,那么您的消费者将不得不依靠不同的东西。但是请注意,这不是很常见的情况,因此,如果这确实是您需要的,则必须认真思考。

在我现在正在使用的系统中,我的Windows窗体类依赖于 ICommandHandler<TCommand>而不是 ValidationException。那是因为我们想向用户显示一个对话框,解释他们输入的数据无效(某些数据只能由服务器验证),并且除了命令外,我们还传递了一个委托,该委托允许“提示命令处理程序”如果命令已成功处理,请回叫。可提示命令处理程序实现本身依赖于 WCF并委派工作并捕获从 IGenericRepositotory<Customer>服务返回的任何 Create。这样可以防止每种形式都有难看的try-catch块。仍然解决方案不是很好,当我找到更好的解决方案时,我会改变。

但是,即使采用这种解决方案,您仍然可能仍想创建一个具有安全性的装饰器,并具有一个包含catch语句的代理(在我的情况下为可提示命令处理程序)。不要尝试返回与装饰器不同的东西。


我上面不明白的是,记录器在哪里/何时被调用?


向两个装饰器的注册可确保在请求 GenericRepository<T>时,构造以下对象图:

IGenericRepository<Customer> repository =
new SecurityRepositoryDecorator<Customer>(
new LoggingRepositoryDecorator<Customer>(
new GenericRepository<Customer>(
new ClientManagementContext()),
DatabaseLogger(),
new AspNetUserSecurity());


当控制器调用存储库 IRepository<T>方法时,将执行以下调用链:

Begin SecurityRepositoryDecorator<Customer>.Create (calls `userSecurity.ValidateUser`)
Begin LoggingRepositoryDecorator.Create (calls `Stopwatch.StartNew()`)
Begin GenericRepository<Customer>.Create
End GenericRepository<Customer>.Create
End LoggingRepositoryDecorator.Create (calls ` this.logger.Log`)
End SecurityRepositoryDecorator<Customer>.Create


因此,安全性装饰器调用日志装饰器,因为安全性装饰器包装了日志装饰器(日志装饰器包装了 IGenericRepository<T>)。

ps。您为存储库命名的方法确实很丑陋。这里有一些提示:


调用接口 T而不是 T(因为 IRepository<Customer>.CreateT暗示它实际上是通用的)。
从方法中删除所有 IRepository<Customer>后缀;当您定义封闭存储库时,它们没有任何意义。例如, CreateCustomer是做什么的? IRepository<Order>.CreateCustomer上下文中的“ T”是什么?更好的名称是 IRepository<T>.Create,但这是不可能的,因为 毫无意义。通过将其命名为 ,所有这些问题都会消失。

关于design-patterns - 在通用存储库上使用Decorator模式实现的AOP,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19526474/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com