5

I like implementing service classes as stateless. However, I also like to decompose my logic into more, simple methods or functions. In some scenarios it seems like the two are somewhat against each other. Consider the following two examples.

I have an entity object called House, implemented something like this.

public class House {
  public string Address { get; set; }
  public List<Room> Rooms { get; set; }

  ...

  public IEnumerable<Furniture> GetAllFurnitures() {
    return Rooms.SelectMany(e => e.Furnitures);
  }

  ...
}

1. Stateful implementation

Pros: cleaner code, no parameter hell

Cons: one service per one House, makes Dependency injection harder because of constructor parameter

public class HouseCleaner {

  private readonly House _house;

  public HouseCleaner(House house) {
    _house = house;
  }

  public void Clean() {
    vacuumClean();
    cleanBathroom();
    cleanToilettes();
    cleanKitchen();
    wipeFloor();
  }
}

2. Stateless implementation

Pros: the service instance can be shared, makes Dependency injection easier

Cons: need to pass probably many parameters to all simple methods and functions to share the current state

public class HouseCleaner {

  public void Clean(House house) {
    vacuumClean(house);
    cleanBathroom(house);
    cleanToilettes(house);
    cleanKitchen(house);
    wipeFloor(house);
  }
}

Which one do you feel more appropriate considering that this is a simplified example. In reality there could be several service dependencies, and other parameters too.

Zoltán Tamási
  • 153
  • 1
  • 5
  • Whichever option you choose, the `House` references should be replaced with `IHouse` (ie an interface) to simplify testing. – David Arno Feb 08 '16 at 14:21
  • 1
    @DavidArno Thanks for your comment. I'm not used to introduce interfaces for entity classes because I rarely put any complex behavior there (ie. which would be worth mocking). – Zoltán Tamási Feb 08 '16 at 14:53
  • 1
    Ah, my apologies. If its simply a data store class of some sort, such an an entity class with no behaviour, then please ignore my previous comment! :) – David Arno Feb 08 '16 at 14:54
  • *"makes Dependency injection harder because of constructor parameter"* - Constructor parameters is dependency injection. – radarbob May 28 '17 at 21:44
  • @radarbob It is just *a form* of DI, the most common and explicit one, indeed. But there can be other forms too. For example if you expose a settable static property somewhere like `public static ILogger DefaultLogger { get; set; }`, then you have already done a form of DI in my opinion. – Zoltán Tamási May 29 '17 at 05:12

3 Answers3

2

There are arguments for both. The choice as it is said "depends". The first approach can be scaled, because you can create many instances of service (i.e. multithreaded service) - but the service must be lightweight. The second approach on the other hand is better suited for "fat" services that take time to instantiate. It is better to share them. As for the cons for it you've mentioned, it can be applied to both variants. But it can be overcome using aggreagates. As Mark Seeman states in his book "Dependency injection in .NET" - you should find or create a more aggregated class that would combine the desired classes, providing a wider meaning.

Alexander Y.
  • 117
  • 5
  • Thank you for the answer. Actually I'll accept it because I also feel that it really "depends". On the other hand, thank you for suggesting Mark Seeman's book, didn't know about it, definitely will have a look. – Zoltán Tamási Feb 13 '16 at 14:06
  • "Wider meaning" is, however, something you should take a lot of care when implementing. As a rule of thumb, the more specific, the better - Single Responsibility - but this isn't always the case. Take Mark Seeman advice's with a grain of salt. – T. Sar May 26 '17 at 18:00
  • 1
    I'm not sure about the scaling argument. Since the second variant is stateless you can instantiate it multiple times (the behaviour won't change) or use the same instance concurrently. I generally prefer to do as much stateless work as possible, of course as with all things, enforcing this to extremes is probably harmful. Which makes it hard to say much more about this single example :). – Roy T. May 29 '17 at 13:21
2

I don't see this as a question of technical pros and cons. It depends on the lifecycle of a HouseCleaner, what it is semantically, and how it relates semantically to a House.

Suppose you were dealing with a Car and a Wheel. It wouldn't make a lick of sense of re-inject Car into Wheel into each other every time the Car needed to move. The Car simply has four Wheel objects, and each of those Wheel objects is attached to that single Car until otherwise relocated. Conceptually, semantically, it's a long-lived, "permanent" relationship.

Contrast that with a HoneyBee and a Flower. The interaction between those two things is short-lived or transient: scoped down to single interactions.

As far the dependency injection: I don't believe it's "harder" either way. You get to inject the dependency in both cases. They differ primarily in when and how often you need to inject that dependency.


If your model is clean and you've come to the point of scrupulizing over whether it's OK to keep "state" on the service-side; you mostly just need to weigh the cost of loading state from memory or a database against the cost of sending it over the wire (a much longer and congested wire than there is between the service and the database) -- and then reassembling it and validating it.

svidgen
  • 13,414
  • 2
  • 34
  • 60
  • Thank you for the detailed answer. However, you're talking about a bit different scenario: association and aggregation. In your example, both `Car` and `Wheel` are entity classes and a car aggregates its wheels. In contrast, a bee and a flower (also entities) are in an association. In my example the class is purely a service class without internal state. In this case I still feel that this is a technical question. – Zoltán Tamási Feb 13 '16 at 14:03
  • 1
    I think the comparison is still applicable. Your service is still named and modeled after domain/business objects. And that's important to consider. But, if your question was more targeted at how to deal with the service object's public interface, I think the second part (subtext) addresses that. ... Consider that if I were to build services over my examples, the underlying state would likely be modeled as described above to facilitate sane client invocation. (Again, depending on the practicality of sending/not sending things over the wire.) – svidgen Feb 13 '16 at 17:30
  • I didn't exactly mean this as a remote service interface. It would be in a business logic behind any kind of server-side interface. You have some valid points though, but I found the other answer a bit more useful. – Zoltán Tamási Feb 13 '16 at 18:12
0

Across the question and answers I see it all hitting around the bullseye - The Visitor Pattern.

DoFactory, with a C# example, says:

Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

Wikipedia says:

the visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to extant object structures without modifying the structures. It is one way to follow the open/closed principle.

In essence, the visitor allows adding new virtual functions to a family of classes, without modifying the classes.

I generally like SourceMaking.com

  • Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

  • The classic technique for recovering lost type information.

  • Do the right thing based on the type of two objects.

  • Double dispatch

radarbob
  • 5,808
  • 18
  • 31
  • Yes, visitor pattern could be used to implement this scenario. However, the example was intentionally representing the main question, which was not about a proper implementation of this particular task. Thanks for the good points though. – Zoltán Tamási May 29 '17 at 05:14