30

Let's say I have a procedure that does stuff:

void doStuff(initalParams) {
    ...
}

Now I discover that "doing stuff" is quite a compex operation. The procedure becomes large, I split it up into multiple smaller procedures and soon I realize that having some kind of state would be useful while doing stuff, so that I need to pass less parameters between the small procedures. So, I factor it out into its own class:

class StuffDoer {
    private someInternalState;

    public Start(initalParams) {
        ...
    }

    // some private helper procedures here
    ...
}

And then I call it like this:

new StuffDoer().Start(initialParams);

or like this:

new StuffDoer(initialParams).Start();

And this is what feels wrong. When using the .NET or Java API, I always never call new SomeApiClass().Start(...);, which makes me suspect that I'm doing it wrong. Sure, I could make StuffDoer's constructor private and add a static helper method:

public static DoStuff(initalParams) {
    new StuffDoer().Start(initialParams);
}

But then I'd have a class whose external interface consists of only one static method, which also feels weird.

Hence my question: Is there a well-established pattern for this type of classes that

  • have only one entry point and
  • have no "externally recognizable" state, i.e., instance state is only required during execution of that one entry point?
8
  • I have been known to do things like bool arrayContainsSomestring = new List<string>(stringArray).Contains("somestring"); when all I cared about was that particular piece of information and the LINQ extension methods are not available. Works fine, and fits inside an if() condition without needing to jump through hoops. Of course, you want a garbage-collected language if you're writing code like that.
    – user
    Commented Nov 8, 2012 at 9:07
  • If it's language-agnostic, why add java and c# as well? :) Commented Nov 8, 2012 at 11:01
  • I'm curious, what is the functionality of this 'one method'? It would help a great deal in determining what the best approach is in the specific scenario, but interesting question nonetheless! Commented Nov 8, 2012 at 11:06
  • @StevenJeuris: I've stumbled upon this issue in various situations, but in the current case it's a method that imports data into the local database (loads it from a web server, does some manipulations and stores it in the database).
    – Heinzi
    Commented Nov 8, 2012 at 11:28
  • 2
    If you always call the method when you create the instance, why not make it an initialization logic inside the constructor?
    – NoChance
    Commented Nov 8, 2012 at 12:53

7 Answers 7

34

There's a pattern called Method Object where you factor out a single, large method with a lot of temporary variables / arguments into a separate class. You do this rather than just extracting parts of the method out into separate methods because they need access to the local state (the parameters and temporary variables) and you can't share the local state using instance variables because they would be local to that method (and the methods extracted from it) only and would go unused by the rest of the object.

So instead, the method becomes its own class, the parameters and temporary variables become instance variables of this new class, and then the method gets broken up into smaller methods of the new class. The resulting class usually has just a single public instance method that performs the task the class encapsulates.

5
  • 1
    This sounds exactly like what I'm doing. Thanks, at least I have a name for it now. :-)
    – Heinzi
    Commented Nov 8, 2012 at 11:30
  • 2
    Very relevant link! It does smell like an anti-pattern to me. If your method is that long, perhaps parts of it could be extracted in reuseable classes or generally be refactored in a better way? I also haven't heard about this pattern before, neither can I find many other resources which makes me believe it's generally not used that much. Commented Nov 8, 2012 at 15:05
  • 1
    @StevenJeuris I don't think it's an anti-pattern. An anti-pattern would be to clutter up refactored methods with lot parameters instead of storing this state that is common between these methods in an instance variable. Commented Nov 8, 2012 at 17:36
  • @AlfredoOsorio: Well, it does clutter up refactored methods with lot of parameters. They live in the object, so it's better than passing all of them around, but it's worse than refactoring to reusable components that only need a limited subset of the parameters each.
    – Jan Hudec
    Commented Nov 9, 2012 at 6:50
13

I would say that the class in question (using a single entry point) is well designed. It's easy to use and extend. Quite SOLID.

Same thing for no "externally recognizable" state. It's good encapsulation.

I don't see any problem with code like:

var doer = new StuffDoer(initialParams);
var result = doer.Calculate(extraParams);
17
  • 1
    And of course, if you don't need to use the same doer again, that reduces to var result = new StuffDoer(initialParams).Calculate(extraParams);.
    – user
    Commented Nov 8, 2012 at 9:08
  • Calculate should be renamed to doStuff!
    – shabunc
    Commented Nov 8, 2012 at 9:39
  • 1
    I would add that if you don't want to initialize (new) your class where you use it (probably you want to use a Factory) you have the option of creating the object somewhere else in you business logic and than pass the parameters directly to the single public method you have. The other option is to use a Factory and have on it a make method which gets the parameters and creates you object with all the params through it's constructor. Than you call your public method without params from wherever you want. Commented Nov 8, 2012 at 10:30
  • 2
    This answer is overly simplistic. Is a StringComparer class which you use as new StringComparer().Compare( "bleh", "blah" ) well designed? What about creating state when there is entirely no need to? Okay the behavior is well encapsulated (well, at least to the user of the class, more on that in my answer), but that doesn't make it 'good design' by default. As to your 'doer-result' example. This would only make sense if you would ever use doer separate from result. Commented Nov 8, 2012 at 12:15
  • 1
    It's not one reason to exist, it's one reason to change. The differnce might seem subtle. You have to understand what that means. It will never create one method classes
    – jgauffin
    Commented Nov 8, 2012 at 13:03
8

From a SOLID principles perspective jgauffin's answer makes sense. However, you shouldn't forget about the general design principles e.g. information hiding.

I see several problems with the given approach:

  • As you pointed out yourself people don't expect to use the 'new' keyword, when the object created doesn't manage any state. Your design reflects it's intention. People using your class might get confused as to what state it manages, and whether subsequent calls to the method might result in different behavior.
  • From the perspective of the person using the class the inner state is well hidden, but when wanting to make modifications to the class or simply understanding it, you are making things more complex. I have already written a lot about the problems I see with splitting methods just to make them smaller, especially when moving state to the class scope. You are modifying the way your API should be used just in order to have smaller functions! That in my opinion is definitely taking it too far.

Some related references

Possibly a main point of argument lies in how far to stretch the Single Responsibility Principle. "If you take it to that extreme and build classes that have one reason to exist, you may end up with only one method per class. This would cause a large sprawl of classes for even the most simple of processes, causing the system to be difficult to understand and difficult to change."

Another relevant reference relating to this topic: "Divide your programs into methods that perform one identifiable task. Keep all of the operations in a method at the same level of abstraction." - Kent Beck Key here is "the same level of abstraction". That doesn't mean "one thing", as it is often interpreted. This level of abstraction is entirely up to the context for which you are designing.

So what is the proper approach?

Without knowing your concrete use case it is hard to tell. There is a scenario in which I sometimes (not often) use a similar approach. When I want to process a dataset, without wanting to make this functionality available to the entire class scope. I wrote a blog post about it, how lambdas can even further improve encapsulation. I also started a question on the topic here on Programmers. The following is a latest example of where I used this technique.

new TupleList<Key, int>
{
    { Key.NumPad1, 1 },
            ...
    { Key.NumPad3, 16 },
    { Key.NumPad4, 17 },
}
    .ForEach( t =>
    {
        var trigger = new IC.Trigger.EventTrigger(
                        new KeyInputCondition( t.Item1, KeyInputCondition.KeyState.Down ) );
        trigger.ConditionsMet += () => AddMarker( t.Item2 );
        _inputController.AddTrigger( trigger );
    } );

Since the very 'local' code within the ForEach isn't reused anywhere else, I can simply keep it in the exact location where it is of relevance. Outlining code in such a way that code which relies on each other is strongly grouped together makes it more readable in my opinion.

Possible alternatives

  • In C# you could use extension methods instead. So operate on the argument directly you pass to this 'one thing' method.
  • See whether this function actually doesn't belong to another class.
  • Make it a static function in a static class. This most likely is the most appropriate approach, as is also reflected in the general APIs you referred to.
1
4

I would say it's pretty good, but that your API should still be a static method on a static class, since that's what the user expects. The fact that the static method uses new to create your helper object and do the work is an implementation detail that should be hidden from whoever is calling it.

1
  • Wow! You managed to get to the gest of it a lot more concisely than I did. :) Thanks. (of course I go a bit further where we might disagree, but I agree with the general premise) Commented Nov 8, 2012 at 14:12
2
new StuffDoer().Start(initialParams);

There is a problem with clueless developers who could use a shared instance and use it

  1. multiple times (ok if previous (maybe partial) execution does not mess up later execution)
  2. from multiple threads (not OK, you explicitly said it has internal state).

So this needs explicit documentation that it is not thread safe and if it's lightweight (creating it is fast, does not tie up external resources nor lot of memory) that it's OK to instantiate on the fly.

Hiding it in a static method helps with this, because then reusing the instance never happen.

If it has some expensive initialization, it could (it isn't always) be beneficial to prepare initializing it once and using it multiple times by creating another class that would contain state only and make it cloneable. Initialization would create state that would be stored in doer. start() would clone it and pass it to internal methods.

This would also allow for other things like persisting state of partial execution. (If it takes long and external factors, e.g. electricity supply failure, could interrupt execution.) But these additional fancy things are usually not needed, so not worth it.

1
  • Good points. Hope you don't mind the cleanup. Commented Nov 8, 2012 at 14:23
2

Besides my more elaborate, personalized other answer, I feel the following observation deserves a separate answer.

There are indications that you might be following the Poltergeists anti-pattern.

Poltergeists are classes with very limited roles and effective life cycles. They often start processes for other objects. The refactored solution includes a reallocation of responsibilities to longer-lived objects that eliminate the Poltergeists.

Symptoms And Consequences

  • Redundant navigation paths.
  • Transient associations.
  • Stateless classes.
  • Temporary, short-duration objects and classes.
  • Single-operation classes that exist only to “seed” or “invoke” other classes through temporary associations.
  • Classes with “control-like” operation names such as start_process_alpha.

Refactored Solution

Ghostbusters solve Poltergeists by removing them from the class hierarchy altogether. After their removal, however, the functionality that was “provided” by the poltergeist must be replaced. This is easy with a simple adjustment to correct the architecture.

0

There are several patterns can be found on Martin Fowler's P of EAA catalog;

  1. Transaction Script: Somewhat similar to the approach you mentioned.
  2. Replace method w/ Method object: Look like a good approach too as @anon mentioned.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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