9

(Using Python 3.2, though I doubt it matters.)

I have class Data, class Rules, and class Result. I use lowercase to denote an instance of the class.

A rules object contains rules that, if applied to a data object, can create a result object.

I'm deciding where to put the (rather complicated and evolving) code that actually applies the rules to the data. I can see two choices:

  1. Put that code inside a class Result method, say parse_rules. Result constructor would take as an argument a rules object, and pass it onto self.parse_rules.

  2. Put that code inside a new class ResultFactory. ResultFactory would be a singleton class, which has a method, say build_result, which takes rules as an argument and returns a newly built result object.

What are the pros and cons of the two approaches?

Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
max
  • 49,282
  • 56
  • 208
  • 355

5 Answers5

3

The GRASP design principles provide guidelines for assigning responsibility to classes and objects in object-oriented design. For example, the Creator pattern suggests: In general, a class B should be responsible for creating instances of class A if one, or preferably more, of the following apply:

  • Instances of B contains or compositely aggregates instances of A
  • Instances of B record instances of A
  • Instances of B closely use instances of A
  • Instances of B have the initializing information for instances of A and pass it on creation.

In your example, you have complicated and evolving code for applying rules to data. That suggests the use of the Factory Pattern.

Putting the code in Results is contraindicated because 1) results don't create results, and 2) results aren't the information expert (i.e. they don't have most of the knowledge that is needed).

In short, the ResultFactory seems like a reasonable place to concentrate the knowledge of how to apply rules to data to generate results. If you were to try to push all of this logic into class constructors for either Results or Rules, it would lead to tight coupling and loss of cohesion.

max
  • 49,282
  • 56
  • 208
  • 355
Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
  • Thanks for the link, I'm now going through Craig Larman's book it referenced. Maybe I misunderstood something, but it seems to me that the Creator pattern takes for granted that class A instances are created in A's constructor, and only discusses which class should call that constructor? – max Jan 16 '12 at 02:50
2

Third scenario:

You may want to consider a third scenario:

  • Put the code inside the method Rules.__call__.
    Instantiating Result like: result = rules(data)

Pros:

  • Results can be totally unaware of the Rules that generates them (and maybe even of the original Data).
  • Every Rules sub-class can customize its Result creation.
  • It feels natural (to me): Rules applied to Data yield Result.
  • And you'll have a couple of GRASP principle on your side:
    • Creator: Instances of Rules have the initializing information for instances of Result and pass it on creation.
    • Information Expert: Information Expert will lead to placing the responsibility on the class with the most information required to fulfill it.

Side effects:

  • Coupling: You'll raise the coupling between Rules and Data:
    • You need to pass the whole data set to every Rules
    • Which means that each Rules should be able to decide on which data it'll be applied.
Community
  • 1
  • 1
Rik Poggi
  • 28,332
  • 6
  • 65
  • 82
1

Why not put the rules in their own classes? If you create a RuleBase class, then each rule can derive from it. This way, polymorphism can be used when Data needs rules applied. Data doesn't need to know or care which Rule instances were applied (unless Data itself is the one who knows which rules should be applied).

When rules need to be invoked, a data instance can all RuleBase.ExecuteRules() and pass itself in as an argument. The correct subclass of Rule can be chosen directly from Data, if Data knows which Rule is necessary. Or some other design pattern can be used, like Chain of Responsibility, where Data invokes the pattern and lets a Result come back.

This would make a great whiteboard discussion.

Bob Horn
  • 33,387
  • 34
  • 113
  • 219
  • Rules do reside in their own classes; I didn't mention it, but `Rules` is a root of a large class hierarchy. So if I want to put the transformation code into Rules, I certainly can put it in the root of this hierarchy. – max Jan 16 '12 at 02:23
1

Can you make ResultFactory a pure function? It's not useful to create a singleton object if all you need is a function.

Janne Karila
  • 24,266
  • 6
  • 53
  • 94
0

Well, the second is downright silly, especially with all the singletonness. If Result requires Rules to create an instance, and you can't create one without it, it should take that as an argument to __init__. No pattern shopping necessary.

Cat Plus Plus
  • 125,936
  • 27
  • 200
  • 224
  • So what would have to change in my scenario, for a class factory to actually make sense? – max Jan 16 '12 at 02:02
  • @max: I don't know. Apply common sense, see which approach is simpler. I guess turning `Rules` into a factory might be an option, if you can create `Result` instances separately, by passing all the data through constructor. But if you always need `Rules` to create `Result`, then it should be a ctor argument. – Cat Plus Plus Jan 16 '12 at 02:07