7

I recently realized that I have way too many dependencies in many of my classes. Now I am trying to solve that for the most important class. I think I have an idea how but I'm not sure if it is the best idea.

The most important class is a Service class. What it does is make one "thing" in the app go through different states. There is a specific flow it follows, triggered by users taking certain actions in the app. (Submit, Confirm, Reject, Close, ...)

At this moment, there is a public method for each action that you can do to change the status of that object, about 10 methods. This feels wrong although the responsibility of this class is "changing the state".

When the state changes however, other actions need to happen as well like sending an e-mail, closing certain tasks, looking up documents etc. Many of those tasks have other different dependencies and because all of the methods are in the same class the main service now has 12 dependencies.

My main goal is reducing the number of dependencies in the main service while still containing some kind of "overview" of all the actions you can take.

I was looking through a list of common design patterns and noticed the "Command" pattern. I think I can create a series of command classes like "SubmitThing", "CloseThing". Each with their own specific dependencies. This way I move the dependencies to other classes and each action has its own dependencies.

The information I can find about the Command pattern however describe its main benefits being that you can keep track of which commands where executed, revert them, add logging. This is not really the problem I wanted to solve, even though the pattern description sounds appealing.

So, is the Command design pattern the way to go here or are there better patterns for splitting up a class that has several actions into smaller pieces?

A small example:

    public class ThingService {

        private IDocumentService documentService;
        private IMailService mailService;
        private IAnotherService anotherService;
        private IActionsOnClosingService actionsOnClosingService;

        public ThingService(IDocumentService documentService,
                            IMailService mailService,
                            IAnotherService anotherService,
                            ...
                            ) {
            this.documentService = documentService;
            this.mailService = mailService;
            this.anotherService = anotherService;
            ...
        }

        public void submitThing(Thing thing, SubmitData data) {
            thing.setStatus(SUBMITTED);
            thing.setData(data);
            documentService.doSomething();
            mailService.doSomething();
        }

        public void closeThing(Thing thing, CloseData data) {
            thing.setStatus(CLOSED);
            thing.setData(data);
            documentService.doSomething();
            mailService.doSomething();
            actionsOnClosingService.doActionsOnClosing();
        }
    }
4

This could be solved by using event driven programming and a few observers. How many things does this method do?

public void submitThing(Thing thing, SubmitData data) {
    thing.setStatus(SUBMITTED);
    thing.setData(data);
    documentService.doSomething();
    mailService.doSomething();
}

Three. It does three things.

  1. Submits the thing.
  2. Does something with a document service.
  3. Sends a mail notification that the thing was submitted.

What happens when we need yet another something to happen when a thing is submitted? I know! We'll just modify this class again!

This design violates both the Single Responsibility and the Open-Closed principles. Compare to this event driven design. (Please excuse my C#...)

public event EventHandler<ThingSubmittedEventArgs> ThingSubmitted;

public void submitThing(Thing thing, SubmitData data) {
    thing.setStatus(SUBMITTED);
    thing.setData(data);

    var handler = ThingSubmitted;
    if (handler != null) {
        handler(this, data);
    }
}

Now when we need to do yet another something else, we don't have to modify this class. We just listen for the ThingSubmitted event and take whatever action is appropriate for the listener.

As a beneficial side effect of this design, this class no longer has dependencies on MailService or DocumentService.

  • I like this idea, this way the dependencies are gone and the non "thing-related" responsibilites are gone. However I feel like I need to keep all the code that is related to submitting together. When someone has to look up "What happens when I submit?" they would probably have to look across several event handlers. I'd like to have 2 things reflected in my design: "What states and actions can a "thing" go through". And two, "What are all the things that happen when I take action x?" – geoffreydv May 12 '16 at 7:23
  • Your current design muddles your question of "what state can thing go through". A mail notification has nothing to do with the state of your "thing" @geoffreydv. I'll admit that the observer makes it more difficult to see what actions happen when a thing is submitted, but with a modern IDE, you're only 2 or 3 click away from "Find all references". A well designed test suite helps there too. – RubberDuck May 12 '16 at 10:55
2

So, is the Command design pattern the way to go here or are there better patterns for splitting up a class that has several actions into smaller pieces?

It's going to depend somewhat on how tightly the actions are coupled to each other.

On possibility is to combine the idea of the Command pattern with that of Composite. Each individual, isolated action is its own command. Composite commands, that know how to order the invocation of their children, are used to organize the command graph.

Factories, fluent Builders, and so on can help you create the command graph without too much direct coupling of the dependencies.

Another possibility is an event driven approach; when a command runs, it publishes message; the subscribers to those messages in turn run commands that can publish new events, and turtles all the way down.

In domain driven design, the latter implementation is very common. Each individual command targets a single aggregate, which is responsible for maintaining its own invariant. When the aggregate changes state, it broadcasts domain events. Downstream event processors observe those events and dispatch additional commands to drive a process.

On the happy path, where everything works, there's little to separate the choices. Pay attention to your use cases when commands start failing -- what's supposed to happen with earlier commands if the MailService is unavailable? Udi Dahan gave a pretty good talk on reliable messaging that discusses some of these concerns.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.