2

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) ?

ONETHRD
  • 21
  • 1
  • 3
  • Can you tells us what you exactly want to do? I cannot give a good answer with just "evaluate several objects" – Paco Feb 24 '10 at 16:52
  • Concretely, I want to create a rule which will tell me if I need to create an email account for my customer. This rule needs to know stuff about my Customer, and stuff about the Contract he has selected. To achieve this, I wanted to create a Specification class which exposes a boolean isSatisfiedBy() method, and which will indicates if my rule is verified by the candidate object(s) passed as parameters. – ONETHRD Feb 24 '10 at 18:07
  • 1
    my answer was unclear, overlooked several aspects of your question, and made a few unwarranted assumptions (that you could navigate to Customer from Contract, for example). So I deleted it. I think you hit the nail on the head with your second question: the specification should operate on an object that can access both the Customer and Contract in question. – Jeff Sternal Feb 24 '10 at 19:13

4 Answers4

5

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;
    }
}
Paco
  • 8,335
  • 3
  • 30
  • 41
  • In that case, you can't chain it up with other specification with a different subject. – Jeboy Feb 04 '19 at 07:51
  • the way is correct, but in this impl. you can't work with interfaces or have to create custom interfaces for each spec just to display SelectedContract property via interface. – Artem A Sep 02 '22 at 22:58
2

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;
    }
}
2

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 */
}
jlew
  • 10,491
  • 1
  • 35
  • 58
  • Good point about generics - I overlooked the actual code in the question! D: – Jeff Sternal Feb 24 '10 at 17:27
  • I'd choose this method. This way, you still have very focused specifications that follow SRP which can be used in isolation in other parts of the application and you are composing them in order to solve this particular problem. – Dan Jun 26 '13 at 03:47
0

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.

bloparod
  • 1,716
  • 1
  • 14
  • 17