views:

47

answers:

1

So suppose my sandwich shop has two service interfaces, IMeatGetter and IVeggiesGetter. Now I have a domain object, Sandwich, with a property IsVegetarian. I want a method to select all the possible toppings, like

void GetToppings
{
  if IsVegetarian
    select (veggies)
  else
    select (veggies union meats)
}

Is it correct domain design to pass in the services to the sandwich constructor? Or would you, at some higher level, load the meats and veggies first, and pass them all to the sandwich?

What if IMeatGetter is a very slow operation? Would that change your answer?

+2  A: 

There's a school of thought associated with Domain-Driven Design that holds that Domain Objects should be POCOs/POJOs, so according to that philosophy the Sandwich class should be independent of the IMeatGetter and IVeggiesGetter services. I personally find that this approach works well despite certain drawbacks.

If the total lists of veggies and meats make sense in the context of a Sandwich (it sounds more like a potential sandwich to me, though), I would pass them in as part of the constructor. Here's a C# example:

public class Sandwich
{
    private readonly IEnumerable<Ingredient> veggies;
    private readonly IEnumerable<Ingredient> meats;

    public Sandwich(IEnumerable<Ingredient> veggies, IEnumerable<Ingredient> meats)
    {
        if(veggies == null)
        {
            throw new ArgumentNullException("veggies");
        }
        if(meats == null)
        {
            throw new ArgumentNullException("meats");
        }

        this.veggies = veggies;
        this.meats = meats;
    }

    // implement GetToppings as described in OP
}

If IMeatGetter is used to retrieve the list of meats and that is a very slow operation, then no, that would not change my answer. In this way we have decoupled the Sandwich class from the retrieval logic of the meats.

This allows us to attempt to manage the lifetime of the meats list elsewhere. We might, for example, write an implementation of IMeatGetter that caches the list of meats in memory.

Even if that's not possible, we can still change the list itself to do a lazy evaluation. Although I don't know which platform you are using, in .NET, the IEnumerable<T> interface allows deferred execution; in other words, it doesn't retrieve the list until you actually begin the enumeration.

If you are working on a different platform, it should still be trivial to introduce a custom interface that enables lazy loading of the meats.

In summary, you pass a reference to a list into the Sandwich class. That list may or may not be loaded into memory at that time, but that's up to you to control - independently of the Sandwich class. Thus, the Sandwich class conforms to the Single Responsibility Principle because it doesn't have to deal with managing the lifetime of meats.

Mark Seemann