使用C#语言中的装饰器模式扩展对象行为

本文介绍了如何在C#项目中使用装饰器模式,通过创建MovieService_Cache类来添加缓存功能,同时保持对原始MovieService的依赖注入。作者展示了如何通过依赖注入和装饰技巧优化代码,使其更灵活且易于维护。
摘要由CSDN通过智能技术生成

目录

介绍

解释启动情况

缓存服务

依赖注入

向方法添加一些主体

向MovieService添加修饰

就是这样,伙计们!

结论


开发人员喜欢模式。我们可以使用或遵循许多模式。一些众所周知的模式是策略模式、观察者模式和构建器模式。还有很多,每个都有自己的优点和缺点。这一次,我想向你展示装饰器模式。此模式背后的思想是,您可以向现有对象添加行为,而不会影响同一类的其他对象。 听起来很复杂?好吧,事实并非如此......一旦你了解了它。

介绍

如果你想跳过我在接下来的章节中所做的所有艰苦工作,并且不知道我将要讲述什么,你可以简单地从我的GitHub存储库下载最终产品。

如果您想关注我展示的所有内容,请确保打开分支StartProject。分支EndProject包含我将在本教程中添加的所有代码。

解释启动情况

我的启动项目非常简单。有两个项目; CachingDemo.BusinessCachingDemo.APIAPI是一个最小的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类中,所以我在缓存版本中也做了同样的事情。

这是诀窍第1部分:我也在这个类中注入了IMovieServiceIMovieService连接到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;
    }
}

如果你看一下这个Get(int id)方法,你看不出有什么特别之处。如果密钥在缓存中不存在,它将从原始MovieService中获取电影,将其放入缓存中,并返回结果。

一件事:我从 MovieService.cs 中删除了所有缓存,包括注入IMemoryCacheMovieService.cs 现在如下所示:

public class MovieService : IMovieService
{
    private readonly DataContext _dbContext;

    public MovieService(DataContext dbContext)
    {
        _dbContext = dbContext;
    }

    public void Create(Movie movie)
    {
        _dbContext.Set<Movie>().Add(movie);
        _dbContext.SaveChanges();
    }

    public void Delete(int id)
    {
        Movie? toDelete = Get(id);

        if (toDelete == null)
            return;

        _dbContext.Remove(toDelete);
        _dbContext.SaveChanges();
    }

    public Movie? Get(int id) => _dbContext.Set<Movie>().FirstOrDefault(x => x.Id == id);

    public IEnumerable<Movie> GetAll() => _dbContext.Set<Movie>().ToList();
}

MovieService添加修饰

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

为了实现这一点,我安装了软件包Scrutor。这个包包含扩展方法Decorate,它帮助我们修饰了使用MovieService_CacheMovieService

因此,首先,安装软件包:

Install-Package Scrutor

然后我们转到API并打开 Program.cs。找到IMovieService连接到MovieService的代码行。在该代码下添加以下代码行:

builder.Services.Decorate<IMovieService, MovieService_Cache>();

就是这样,伙计们!

没什么可做的了。装饰模式已准备好完成其工作。

为了测试它,我建议在MovieService_Cache中的方法上设置断点,启动API,然后使用断点执行该方法。

发生的情况是,API将调用MovieService,但由于它是用MovieService_Cache修饰的,它将首先执行MovieService_Cache中的方法,推翻原始类。

结论

这只是装饰器模式的一个小例子。但它可以非常强大。我不会过度使用它;不要装饰你拥有的每个类。您不必更改现有类中的任何内容,如果您在一个大型应用程序中工作,其中某个类已经工作,但只需要稍微更改一点,这将使它更安全。

我很想看看.NET自己的装饰器实现,因此我们不必使用第三方包。

https://www.codeproject.com/Articles/5358474/Extending-Object-Behavior-with-the-Decorator-Patte

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值