规范模式C#的实现 - Enterprise Craftsmanship


规范模式不是一个新主题,它的许多实现已经在互联网上。在这篇文章中,我想讨论模式的用例,并将几种常见的实现相互比较。

规格模式:那是什么?
规范模式是一种模式,它允许我们将一些领域知识封装到一个单元:规范中 ,并在代码库的不同部分重用这个单元。
该模式的用例最好用一个例子表示。假设我们的域模型中有以下类:

public class Movie : Entity
{
    public string Name { get; }
    public DateTime ReleaseDate { get; }
    public MpaaRating MpaaRating { get; }
    public string Genre { get; }
    public double Rating { get; }
}
 
public enum MpaaRating
{
    [b]G[/b],
    [b]PG13[/b],
    [b]R[/b]
}

现在,让我们假设用户想要找一些相对较新的电影观看。为了实现这一点,我们可以向存储库类添加一个方法,如下所示:

public class MovieRepository
{
    public IReadOnlyList<Movie> GetByReleaseDate(DateTimeminReleaseDate)
    {
        /* … */
    }
}

如果我们需要按评级或类型搜索,我们也可以介绍其他方法:

public class MovieRepository
{
    public IReadOnlyList<Movie> GetByReleaseDate(DateTimemaxReleaseDate) { }
 
    public IReadOnlyList<Movie> GetByRating(double minRating) { }
 
    public IReadOnlyList<Movie> GetByGenre(string genre) { }
}

当我们决定结合搜索标准时,事情变得有点复杂,但我们仍处于良好状态。我们可以引入一个Find方法来处理所有可能的标准并返回一个统一的搜索结果:

public class MovieRepository
{
    public IReadOnlyList<Movie> Find(
        DateTime? maxReleaseDate = null,
        double minRating = 0,
        string genre = null)
    {
        /* … */
    }
}

当然,我们也可以随时为该方法添加其他标准。
当我们不仅需要搜索数据库中的数据而且还需要在内存中验证数据时,就会出现问题。例如,在我们向其出售门票之前,我们可能想检查某部电影是否符合儿童资格,因此我们引入了验证,如下所示:

public Result BuyChildTicket(int movieId)
{
    Movie movie = _repository.GetById(movieId);
 
    if (movie.MpaaRating != MpaaRating.[b]G[/b])
        return Error(“The movie is not eligible for children”);
 
    return Ok();
}

如果我们还需要查看满足相同标准的所有电影的数据库,我们必须引入类似于以下的方法:

public class MovieRepository
{
    public IReadOnlyList<Movie> FindMoviesForChildren()
    {
        return db
            .Where(x => x.MpaaRating == MpaaRating.[b]G[/b])
            .ToList();
    }
}

此代码的问题在于它违反了DRY原则,因为关于考虑儿童电影的内容的领域知识现在分布在两个位置:BuyChildTicket方法和MovieRepository。这就是规范模式可以帮助我们的地方。我们可以介绍一个新的类,它确切地知道如何区分不同类型的电影。然后我们可以在两种情况下重用此类:

public Result BuyChildTicket(int movieId)
{
    Movie movie = _repository.GetById(movieId);
 
    var spec = new MovieForKidsSpecification();
 
    if (!spec.IsSatisfiedBy(movie))
        return Error(“The movie is not eligible for children”);
 
    return Ok();
}
public class MovieRepository
{
    public IReadOnlyList<Movie> Find(Specification<Movie> specification)
    {
        /* … */
    }
}

这种方法不仅消除了域知识重复,还允许组合多个规范。反过来,这有助于我们轻松设置相当复杂的搜索和验证标准。
规范模式有3个主要用例:
  • 查找数据库中的数据。那就是找到符合我们手头规格的记录。
  • 验证内存中的对象。换句话说,检查我们检索或创建的对象是否符合规范。
  • 创建符合条件的新实例。这在您不关心实例的实际内容但仍需要具有某些属性的情况下非常有用。

C#中规范模式实现的完整源代码