How to adapt the Specification pattern to evaluate

2019-07-20 10:36发布

I know that the Specification pattern describes how to use a hierarchy of classes implementing ISpecification<T> to evaluate if a candidate object of type T matches a certain specification (= satisfies a business rule).

My problem : the business rule I want to implement needs to evaluate several objects (for example, a Customer and a Contract).

My double question :

  • Are there typical adaptations of the Specification patterns to achieve this ? I can only think of removing the implementation of ISpecification<T> by my specification class, and taking as many parameters as I want in the isSatisfiedBy() method. But by doing this, I lose the ability to combine this specification with others.

  • Does this problem reveal a flaw in my design ? (i.e. what I need to evaluate using a Customer and a Contract should be evaluated on another object, like a Subscription, which could contain all the necessary info) ?

4条回答
Lonely孤独者°
2楼-- · 2019-07-20 10:47

Paco's solution of treating one object as the subject and one as a parameter using constructor injection can work sometimes but if both objects are constructed after the specification object, it makes things quite difficult.

One solution to this problem is to use a parameter object as in this refactoring suggestion: http://sourcemaking.com/refactoring/introduce-parameter-object.

The basic idea is that if you feel that both Customer and Contract are parameters that represent a related concept, then you just create another parameter object that contains both of them.

public class ParameterObject  
{
    public Customer Customer { get; set; }
    public Contract Contract { get; set; }
}

Then your generic specification becomes for that type:

public class SomeSpecification : ISpecification<ParameterObject>
{
    public bool IsSatisfiedBy(ParameterObject candidate)
    {
        return false;
    }
}
查看更多
对你真心纯属浪费
3楼-- · 2019-07-20 10:51

I don't know if I understood your question.

If you are using the same specification for both Customer and Contract, this means that you can send the same messages to both of them. This could be solved by making them both to implement an interface, and use this interface as the T type. I don't know if this makes sense in your domain.

Sorry if this is not an answer to your question.

查看更多
我命由我不由天
4楼-- · 2019-07-20 11:08

Your problem is that your specification interface is using a generic type parameter, which prevents it from being used for combining evaluation logic across different specializations (Customer,Contract) because ISpecification<Customer> is in fact a different interface than ISpecification<Contract>. You could use Jeff's approach above, which gets rid of the type parameter and passes everything in as a base type (Object). Depending on what language you are using, you may also be able to pull things up a level and combine specifications with boolean logic using delegates. C# Example (not particularly useful as written, but might give you some ideas for a framework):

ISpecification<Customer> cust_spec = /*...*/
ISpecification<Contract> contract_spec = /*... */
bool result = EvalWithAnd( () => cust_spec.IsSatisfiedBy(customer), () => contract_spec.IsSatisfiedBy( contract ) );

public void EvalWithAnd( params Func<bool>[] specs )
{
    foreach( var spec in specs )
    {
       if ( !spec() )
          return false; /* If any return false, we can short-circuit */
    }
    return true; /* all delegates returned true */
}
查看更多
我命由我不由天
5楼-- · 2019-07-20 11:14

In that case (depending on what the specification precisely should do, I would use one of the objects as specification subject and the other(s) as parameter.

Example:

public class ShouldCreateEmailAccountSpecification : ISpecification<Customer>
{
    public ShouldCreateEmailAccountSpecification(Contract selectedContract)
    {
       SelectedContract = selectedContract;
    }

    public Contract SelectedContract { get; private set; }

    public bool IsSatisfiedBy(Customer subject)
    {
        return false;
    }
}
查看更多
登录 后发表回答