我正在尝试使用装饰器构建一个原型,将面向方面的编程应用于我的项目。我的项目的某些部分将使用通用存储库(用于简单的 CRUD),但最终我还将合并命令和查询处理程序(这些将执行特定任务,如 ProcessCustomerOrders 等)。此外,我想在这里举例说明的横切关注点是安全和日志记录。
另外,我知道我的示例代码不是使用装饰器模式,而只是我为该原型提供上下文的代码示例。
我知道还有其他方法可以实现 AOP(或横切关注点),例如代理或代码编织模式,但我不熟悉这些模式,因此不知道它们之间的权衡。
我在这里使用控制台应用程序只是为了展示如果我以链式方式“新建”它们会是什么样子。
我的问题是:
(1) 如何使用 Simple Injector(在引导类中)将其连接起来并且仍然保持顺序相同?
(2)这是装饰器模式的正确使用(因为我没有使用基础抽象或接口类或装饰器基础)?
(3) 是否有一种干净的方法可以在同一个存储库中使用多个 ILogger 服务的实现(例如 DatabaseLogger 和 ConsoleLogger),而无需注入两个不同的版本?
(4) 实际的日志记录是在 Repository 方法中实现的,并且 ILogger 服务被注入到 Repository 类中,但是有没有比硬连接记录器并仍然使用 Generic Repositories 更好的方法呢?
(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<>));
}
}
控制器调用的装饰器链的顺序应该是控制器(检查)> 安全(如果可以继续,则允许调用)> 回购(然后更新持久层)> 日志(到某个设施)> 并返回到控制器。
新的控制器类:
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 中执行一些操作(例如从 Security Decorator 返回一个 bool),我是否必须修改 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:
似乎现在应该像装饰器模式一样工作;感谢 Steven 提供的所有出色答案。
原型主要功能:
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;
}
// ..