0

I am puzzled on how to model this situation. Suppose you have an algorithm operating in a loop. At every loop, a procedure P must take place, whose role is to modify an input data I into an output data O, such that O = P(I).

In reality, there are different flavors of P, say P1, P2, P3 and so on. The choice of which P to run is user dependent, but all P have the same finality, produce O from I.

This called well for a base class PBase with a method PBase::apply, with specific reimplementations of P1::apply(I), P2::apply(I), and P3::apply(I). The actual P class gets instantiated in a factory method, and the loop stays simple.

Now, I have a case of P4 which follows the same principle, but this time needs additional data from the loop (such as the current iteration, and the average value of O during the previous iterations).

How would you redesign for this case?

9
  • I guess you meant "... have the same finality, produce O from I"? Feb 15, 2011 at 12:54
  • @Steven : yesss.. Feb 15, 2011 at 12:55
  • Isn't this the Strategy design pattern?
    – S.Lott
    Feb 15, 2011 at 12:59
  • @S.Lott : yes, I designed it as a Strategy, but the strategy accepts different inputs. That's why it does not fit perfectly. The strategy assumes that the algos are different, but the input is the same Feb 15, 2011 at 13:01
  • @Stefano Borini: "the strategy accepts different inputs". No, it doesn't. They can trivially be made all the same. I don't understand the question.
    – S.Lott
    Feb 15, 2011 at 13:04

3 Answers 3

6

I would add a single "context" parameter to the method; the context can contain the loop iteration, and any other data needed - and can be extended (by adding extra data) without breaking the API.

The strategies that don't care about the context simply don't use it - and it is only a single extra reference on the signature, not 16 arguments.

1

I'd simply add an empty iterationBegin(CurrentO, Index) to PBase, then override in P4. The main reasoning (assuming a somewhat more complex scenario than your sample case) would be to both avoid cluttering apply with unneeded arguments, and to avoid changing the existing PBase subclasses.

If you later on need more than just the O and the Index, using the same approach with methods like setAverageO, setLoopIndex will allow you to pick up only what is needed in specific PBase subclasses.

6
  • Yuk at virtual functions when abstract is a viable solution. Also, you should always try to prevent shared state. In your case the iterationBegin() call will force you to store these parameters. You will have shared state between iterationBegin() and the apply() call, where the order of those calls is important. Feb 15, 2011 at 13:49
  • @Steven Jeuris:Making such functions pure virtual would require changing all PBase subclasses, which I'd want to avoid. Sure, for 4 concrete classes, who cares, but for a more complex scenario with e.g. a few hundred subclasses, avoiding changes to all classes due to the requirements of one new subclass would be preferable. The "Context" mechanism mentioned below and the method I describe allow changing requirements with minimum modification of existing code.
    – Erik
    Feb 15, 2011 at 14:05
  • @Steven Jeuris: On the topic of shared state, this is IMO an acceptable tradeoff, as it is both easy to detect when the expected state is not provided to the P4 class, and there's a single rarely changing point (the primary loop) where you need to ensure that the state is passed to PBase subclasses.
    – Erik
    Feb 15, 2011 at 14:12
  • @Erik: The fact that there is almost never a need to override a virtual method indicates a wrong abstraction, hence my posted answer. Using a context parameter simply moves the need to know specific types to where the context parameter is interpreted. This is similar to my proposal to make the loop aware about different P's. Either could be good solutions, depending on where most change is expected. Ofcourse, when the context could be encapsulated inside a specific implementation, this is still better. Feb 15, 2011 at 14:17
  • 1
    @Steven Jeuris: I completely agree on the indication of wrong abstraction, and I wouldn't choose this as a design. My answer is more focused on the "redesign" context, where my primary goal would be to make an existing design extensible without breaking anything or requiring large amounts of changes. I think we're basically agreeing on how this can/should be solved, but answer based on different primary goals.
    – Erik
    Feb 15, 2011 at 14:27
1

Is there any reason why passing the current iteration to P1, P2 and P3 would seem 'wrong'? If these parameters would be relevant, but simply aren't necessary, I would simply pass them, so that other implementations can use them.

If the current iteration is something which isn't related at all to P1, P2 and P3 but only to P4, you should probably use a different design, where the loop is aware of different P's. P's that need no iteration awareness, and P's that do need it.

I'm guessing the first design which simply passes iteration parameters is OK.

UPDATE relating to comment:

In case you need parameters which aren't specific to the iteration, and can be scoped entirely inside a specific type (or abstract subtype) of P, be sure to do so.

2
  • what I don't like is that if more and more P are added, each one with other parameters, the convergence would be that all Ps accept a bunch of data they don't need, and have to extract only those which are needed (potentially just one or two) Feb 15, 2011 at 13:01
  • @Stefano Borini: If this were to be the case, that it's not just simply some iteration parameters. (current, total, step?) You should consider a redesign. What other parameters would you have to pass? You could store these parameters, not relevant to the iteration, inside the specific implementations as members where necessary. Feb 15, 2011 at 13:04

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.