Is this a common and/or named OOP-pattern?

2019-06-08 02:34发布

问题:

I wonder whether this pattern that I have frequently used is common and has a name:

  • an algorithm is a class
  • its parameters are passed via the constructor of the class
  • calling a parameter-less run method triggers the computation
  • the result(s) are fetched by calling getter methods

An example (in Python for simplicity):

class MyAlgorithm:

    def __init__(self, input, param1, param2):
        self.param1 = param1
        self.param2 = param2
        self.input = input

    def run(self):
        ...
        return self

    def getResultA(self):
        ...

    def getResultB(self):
        ...

As a second question: What are the pros and cons of this pattern? Is there a good reason to have a separate run-method rather than having the constructor do the computation?

回答1:

It is command pattern, where the intention is decoupling the implementation of the main operation of some classes(concrete Command classes) from its consumer(CommandHandler). In summary a client class who executes someCommandObject.run(); is free of any method arguments, hence the method implementation inside that concrete class can freely implement any desired operation which is specific to that particular class. So the client class can freely execute anyCommandObject.run(); methods, since their method declarations are not rigid with any typed parameters.

It is the same thing we are having in Thread.run(); in Java Threads.

Is there a good reason to have a separate run-method rather than having the constructor do the computation?

Yes. The constructor should never have explicit calculations. The purpose of a constructor is simply construct the object, not to uncover any behaviors. If you want to expose them, that's why methods are there.



回答2:

That is variation of command pattern.

The point is that you can pass the command to some other code for execution and then read the result back, without the execution code needing to know specific parameters/return types of the operation.

As for constructor doing the computation. Constructors shouldn't do any computation. Period.



回答3:

I suspect something like this is reasonably common. I've used it in my code, actually.

The main use case is where you have a bunch of data and you have a variety of transformations you want to do on it. For instance I may have a matrix of prices (open/high/low/close, so actually 4) and a matrix of earnings.

I want to construct outputs that take these ingredients and mix them in various ways. So for instance, one function produces the return between the open and close, and another produces the ratio of earnings to close prices. You could go on forever producing these outputs, and sometimes you want to turn some of them on or off, so it's nice to have a way to keep them separate.

So you do what you've demonstrated. You have a list of these algos in a file, and you pass them all references to all the data. You then loop over them and hit "run", and have a look at the outputs.

Pros: keep each algo separate from the others, ability to choose which to run.

Cons: Need to separate each algo into different units, not really worthwhile if you don't have several.

Why not do the calculation in the run method? It will still function, but normally we like to keep construction separate from calculation.

Sounds a bit like https://en.wikipedia.org/wiki/Command_pattern. I was looking for something containing the idea of a delegate.



回答4:

First answer:

I wonder whether this pattern that I have frequently used is common [...]

I have encounterd it widely, in several APIs. I didn't mind using when it was documented (you need to run() before you get() the results, etc), though sometimes the input objects were many, and hard to setup properly. However very few documentations were complete, and if they are at all necessary, then it could be done better.

However when I had to extend it, it proved very troublesome (required Ctor is messy, results sometimes hard to extend, Status unknown (has it run() yet?), some result objects might be unavailbable (for good reasons)...


Second answer:

I wonder whether this pattern that I have frequently used [...] has a name:

As others said, looks like Command Pattern.


Now for a few comments on extendability:

(Take with a grain of salt, may not apply to your application.)

  • an algorithm is a class

That is good. (containment of an algorithm to an entity).
But that is not very extensible - only through inheritance, which is restrictive. Too many APIs force inheritance because they believe they are the heart of your application. What if I want to extend something else to do the job, like a Map?
What would be better is:

  • A solver class can be interacted with through an interface
  • One or several concrete classes provide exchangeable algorithm implementations of the solver

When I want a solver instance, I can create one from scratch by extending the interface with complete freedom, or reuse some known implementation through (multiple) inheritance.

  • its parameters are passed via the constructor of the class

That's an implementation detail. Why force someone to handle objects, for which they may not care? How do you even enforce Constructors to have specific parameters? What if the initial state is complex and multi-faceted, and requires a Builder?

No pattern should require a specific constructor (except en empty one?). Also, beware, constructors shouldn't do too much computation.

  • calling a parameter-less run method triggers the computation
  • How do you ensure it isn't called several times?
  • Or that it is even called before the getResult() methods?
  • If I run() again, do I get the same results?
  • Or does the computation happen again?
  • the result(s) are fetched by calling getter methods
  • But when are they ready? The result object needs to be null-able (ugh), an OptionalResult, or have a isAvailable() method on it.
  • Can the result become stale()?
  • How can I share these results without sharing the whole algorithm?
  • What do I do with the Algorithm instance afterwards? is it re-usable? Can I reset() it? I might have been costly to set up.

I have found it it MUCH clearer everywhere I found it, to have (JAVA here):

 public interface Solver {
     <? extends SolverResult> run(SolverInput input);
 }

In here, implementations can vary, each instance of solver has a completely internal state which may speed it up (like a cache) for later use, but is distinct from the SovlerResult object, which is what I wanted. The SolverInput can be created as I want, and SolverResult can be a specialized result class of the solver instance, capable of giving better insight (with additional getComputationQuality(), getConvergenceRate() etc.).

I think separating the input, the solver, and the output is much more flexible. Design-patterns should not be an enforcement tool, but more a facilitator tool. It even allows your design patter by implementing all three interfaces at once (not that I recommended it).