装饰者模式在C#中的应用

装饰者模式是一种设计模式,它允许用户在不修改现有对象代码的情况下,通过创建一个包装对象来扩展对象的行为。在本文中,将探讨如何在C#中使用装饰者模式来实现缓存功能,并通过依赖注入配置装饰者。

项目初始状态

起始项目相当简单。有两个项目:CachingDemo.Business和CachingDemo.API。API是一个最小化的API,仅用于展示一些UI。业务逻辑是需要更改的部分,将主要关注这个项目。特别是MovieService.cs类。

打开这个类,会发现GetAll()方法。它看起来像这样: public IEnumerable<Movie> GetAll() { string key = "allmovies"; Console.ForegroundColor = ConsoleColor.Red; if (!memoryCache.TryGetValue(key, out List<Movie>? movies)) { Console.WriteLine("Key is not in cache."); movies = _dbContext.Set<Movie>().ToList(); var cacheOptions = new MemoryCacheEntryOptions() .SetSlidingExpiration(TimeSpan.FromSeconds(10)) .SetAbsoluteExpiration(TimeSpan.FromSeconds(30)); memoryCache.Set(key, movies, cacheOptions); } else { Console.WriteLine("Already in cache."); } Console.ResetColor(); return movies ?? new List<Movie>(); } 它实际上做了两件事:处理缓存数据和实际数据(如果缓存中不存在)。这就是装饰者模式可以解决的问题。让MovieService.cs做它需要做的事情,让另一个类处理缓存。

创建缓存服务

需要做的第一件事是创建一个缓存服务。每个服务都有自己的缓存服务。有一个服务,MovieService,创建了另一个类并称之为MovieService_Cache.cs。这个名字的原因很简单:它将直接放在原始MovieService文件下。

重用了MovieService使用的相同接口。 public class MovieService_Cache : IMovieService { public void Create(Movie movie) { throw new NotImplementedException(); } public void Delete(int id) { throw new NotImplementedException(); } public Movie? Get(int id) { throw new NotImplementedException(); } public IEnumerable<Movie> GetAll() { throw new NotImplementedException(); } }

依赖注入

使用IMemoryCache将缓存机制注入到MovieService类中,所以在缓存版本中也这样做。这是第一部分的技巧:也在这个类中注入IMovieService。IMovieService连接到MovieService,而不是MovieService_Cache,所以这样做是安全的。缓存类在更改后看起来像这样: public class MovieService_Cache : IMovieService { private readonly IMemoryCache memoryCache; private readonly IMovieService movieService; public MovieService_Cache(IMemoryCache memoryCache, IMovieService movieService) { this.memoryCache = memoryCache; this.movieService = movieService; } public void Create(Movie movie) { throw new NotImplementedException(); } public void Delete(int id) { throw new NotImplementedException(); } public Movie? Get(int id) { throw new NotImplementedException(); } public IEnumerable<Movie> GetAll() { throw new NotImplementedException(); } }

为方法添加主体

现在是时候为方法添加一些代码了。从上到下: Create方法不需要缓存。它所做的就是将数据发送到数据库,然后完成。所以将返回注入的MovieService实例的结果。 Delete的想法是一样的:不需要缓存,所以只需重用原始的MovieService实例。 Get(int id)可以使用缓存。在这里,使用缓存机制。代码如下。但是,一旦键不在缓存中(缓存中不存在该项),需要从数据库中检索它。这是原始MovieService做的,而不是缓存版本。看看是如何创建和使用单一责任原则的? 将对GetAll()方法做完全相同的事情。

public class MovieService_Cache : IMovieService { private readonly IMemoryCache memoryCache; private readonly IMovieService movieService; public MovieService_Cache(IMemoryCache memoryCache, IMovieService movieService) { this.memoryCache = memoryCache; this.movieService = movieService; } public void Create(Movie movie) { movieService.Create(movie); } public void Delete(int id) { movieService.Delete(id); } public Movie? Get(int id) { string key = $"movie_{id}"; if (memoryCache.TryGetValue(key, out Movie? movie)) return movie; movie = movieService.Get(id); var cacheOptions = new MemoryCacheEntryOptions() .SetSlidingExpiration(TimeSpan.FromSeconds(10)) .SetAbsoluteExpiration(TimeSpan.FromSeconds(30)); memoryCache.Set(key, movie, cacheOptions); return movie; } public IEnumerable<Movie> GetAll() { string key = "movies"; if (memoryCache.TryGetValue(key, out List<Movie>? movies)) return movies ?? new List<Movie>(); movies = movieService.GetAll().ToList(); var cacheOptions = new MemoryCacheEntryOptions() .SetSlidingExpiration(TimeSpan.FromSeconds(10)) .SetAbsoluteExpiration(TimeSpan.FromSeconds(30)); memoryCache.Set(key, movies, cacheOptions); return movies; } }

为MovieService添加装饰

如果现在启动API,它不会使用任何缓存方法。它将一遍又一遍地从数据库中获取所有电影。需要装饰MovieService类。这是一个依赖注入配置。

为了实现这一点,安装了Scrutor包。这个包包含了扩展方法Decorate,它帮助用MovieService_Cache装饰MovieService。 所以,首先,安装包: Install-Package Scrutor 然后前往API并打开Program.cs。找到连接IMovieService到MovieService的行。在下面添加以下代码行: builder.Services.Decorate<IMovieService, MovieService_Cache>();

这只是装饰者模式的一个小例子。但它可以非常强大。不会过度使用它;不要装饰拥有的每个类。不必改变现有类中的任何内容,这使得它在在一个大应用程序中工作时更安全,如果一个类已经工作了,但只需要一点点变化。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485