The role of the specification model is to build business logic elements that can be assembled freely. However, in the example of the previous article, the implementation of the "standard" specification mode is cumbersome, and simple functionality requires more complex code. However, since the "standard" approach, naturally refers to the implementation of any object-oriented language can be used, but we are using C #, in the actual development process, we can use C # today's powerful features to achieve a more easy-to-use, more lightweight specification mode.
Of course, there are pros and cons, in the use of "standard" or "lightweight" issues, but also according to your needs to choose.
The key to the specification pattern is that the specification class has a Issatisifiedby function that verifies that an object satisfies the conditions represented by that specification. Multiple specification objects can be assembled and generate new specification objects, which can form highly customizable business logic. From this, we can see that the key to a specification object is actually the logic of a Issatisifiedby method. Each object, a piece of logic. The only key to each object is this logic. Therefore, we can completely construct such a "generic" type, allowing the outside world to "inject" this logic into the specification object through the constructor:
public class Specification<T> : ISpecification<T>
{
private Func<T, bool> m_isSatisfiedBy;
public Specification(Func<T, bool> isSatisfiedBy)
{
this.m_isSatisfiedBy = isSatisfiedBy;
}
public bool IsSatisfiedBy(T candidate)
{
return this.m_isSatisfiedBy(candidate);
}
}
Well, it's also a dependency injection. In a common object-oriented language, the smallest unit that carries a piece of logic can only be "class," but we say that certain methods in certain classes are the logic we need. In C #, there is a "delegate" from the earliest, which can be used to host a piece of logic. Rather than defining a specific specification class for each situation, and letting that spcification class access external resources (that is, building dependencies), instead of preparing the logic that is the only need in this class, the various dependencies are automatically reserved by the compiler and injected directly into a " Generic "class. Crucially, this is also very easy to do in terms of programming.
As for the And,or,not methods in the original ispecification<t>, we can extract them into an extension method. A friend said that with the extension method, some methods that do not need access to the private member/state should be extracted to the outside of the entity to avoid "polluting" entities. But I disagree, in my opinion, whether it is an instance method or an extension method, or a certain according to duty and concept. I intend to use the extension here because And,or,not is not the logic of a specification object, not a specification object that says, "I'm going to and another," "I'm going to or another," or "I'm going to build ... Take the counter. " Like the two-dollar operator &&, | |, or +,-does the arithmetic numbers on both sides have primary and secondary points? No, they are tied. Therefore, I chose to use an additional extension method instead of handing these responsibilities to a Specification object:
public static class SpecificationExtensions
{
public static ISpecification<T> And<T>(
this ISpecification<T> one, ISpecification<T> other)
{
return new Specification<T>(candidate =>
one.IsSatisfiedBy(candidate) && other.IsSatisfiedBy(candidate));
}
public static ISpecification<T> Or<T>(
this ISpecification<T> one, ISpecification<T> other)
{
return new Specification<T>(candidate =>
one.IsSatisfiedBy(candidate) || other.IsSatisfiedBy(candidate));
}
public static ISpecification<T> Not<T>(this ISpecification<T> one)
{
return new Specification<T>(candidate => !one.IsSatisfiedBy(candidate));
}
}
In addition, the advantage of using an extension method is that if we want to add a logical operation (such as "XOR"), then there is no need to modify the interface. Modifying the interface is a costly and wasteful thing.
At this point, it's much easier to use the specification object because you don't need to create a separate ispecification<t> type for each piece of logic. But there is a simpler one: direct use of delegates. Since the logic of the entire Specificaiton object can be directly represented by a delegate, why do we need a "shell"? Instead, use this type of delegate directly:
public delegate bool Spec<T>(T candicate);