2

I'm studying OOP design patterns in a solution-oriented way which I mean not concentrating just one pattern but with a comparative analysis like their combined or hybrid usages and equivalents in dynamic languages or in functional languages.

As the GoF grouping name behavioral patterns implies, this patterns are for behavior parameterization by utilizing single method dynamic dispatch in terms of polymorphism that OOP languages provides inherently AFAIK. I assume Strategy pattern as the main form of this mechanism and Template method pattern as its derivation which also keeps common code. Is that reasoning right? Can we approach polymorphism in OOP in this way?

If we think outside of OOP there are higher order functions and lambda functions in dynamic languages and functional languages or mix of them. I think that these languages provides only a few patterns/constructs in contrast to the GoF catalog which I map them to Strategy and Template Method Patterns in essence.

With this perspective, I have analyzed other behavioral patterns and then this question can't get out of my mind. Even if it looks general but I think I've tried to be specific, I'm taking the risk of asking here.

Can we do the same job with Strategy or Template patterns instead of Command, Chain of Responsibility or Decorator(Structural but let's assume in this group) for an example in more or less finer design?

Note: I'm not trying to generalize so for example I know Visitor pattern is out of this scope since it solves the expression problem.

2 Answers 2

4

It's an interesting question, and while a concrete, definite answer perhaps requires a precise definition of what "expressed" means, in general, I would agree that the structure of the strategy pattern

[Context]--->[Abstraction]<|-----[Derivative]

is the basic underlying form that appears in various forms in many other patterns. This is also the basic structure you get by applying the Dependency Inversion Principle (from SOLID):

[A]--->[Abstraction]<---[B]   

In general, the 'abstraction' does not have to be an abstract class or an interface, but any kind of contract that captures the nature of the interaction between the two components (e.g., it could be a contract of some sort, a protected interface only visible to subclasses, or some kind of a language, a data structure, a set of conventions, etc.).

Now, in the Go4 book, the patterns are not so much differentiated by structure as they are by their intent (e.g., the structure of Strategy and State, and Bridge is more or less exactly the same), and in fact, the details of the structure are free to vary in some ways. I don't quite see the Template Method pattern the same way (I think) you do, though. You can have common code in the abstract Strategy as well (it doesn't have to be an interface). Their basis for making the Template Method a distinct pattern is really predicated on it's intent (or the kind of problem it solves), which is to impose a certain structure on an algorithm and let the derivatives change parts of it. They do compare and contrast it with Strategy, saying that "Strategies use delegation [by which they mean composition] to vary the entire algorithm" while "Template methods use inheritance to vary part of an algorithm". In other words, they only consider the [AbstractTM]<|---[ConcreteTM] pair to be the part of the pattern (essentially, there's no external "Context"). But that's not necessarily the perspective that's useful to what you're trying to do. One way to look at it is that the public TemplateMethod() (the function) relies on an abstraction represented by the set of protected primitive operations that can be overridden, which then lets you write the TemplateMethod() in a generalized way:

[TemplateMethod()]---->[protected primitive ops]<|---[overriding implementations] 

And in fact, if you decided to pull out those protected primitive operations into, say, an abstract class (essentially utilizing the Strategy pattern), nobody could really claim that what you're doing is wrong.

To me, there isn't really a fundamental difference there. So, yeah, in general, wherever there's a need for a dependency to be inverted, you'll find some variation of that structure. So, I'm inclined to say that in many cases you could do "the same job" using that same basic structure. But since patterns aren't strictly defined by their structure, but rather by what they are trying to do and by the way their elements interact and collaborate to achieve that goal, what you'd end up with could still be described as those respective patterns. What you've stumbled upon here is a principle that's deeper (more fundamental) than the patterns themselves are.

The Decorator pattern is a bit specific in this regard, and it's worth understanding in what way. There's the same sort of dependency structure embedded in it too, but the thing that's specific to it is that the abstraction referenced by the "Context" (the AbstractDecorator) is also the one that the Context itself derives from (this is important because (1) it allows decorators to be transparent to client code, and (2) it allows decorators to be stacked on top one another). The other thing to notice is that decorator is not necessarily extended by deriving from that abstraction, but by deriving from the AbstractDecorator itself (assuming there's a base decorator that provides the basic wrapper capability).

I don't know how well all this answers your question, but hopefully you'll find it useful in some way.

P.S. I wouldn't say that Visitor solves (strictly speaking) the expression problem, but rather lets you flip it around. Objects make it easy to add new representations (by deriving classes), but make it harder to add new operations (methods) to the abstract interface. Visitor makes it easy to add new operations (new Visitor derivatives), but it's hard to add new representations (new types of elements).

P.P.S. In case my ad hoc ASCII diagrams aren't very clear:
----> - a dependency (in a general sense, not necessarily a direct reference)
---|> - an inheritance relationship
(the arrows define the dependency direction)

2
  • Thank you for your very detailed answer. I have found some really good pointers that I will think about. For express, actually I couldn't find the best word because from an aspect It looks like to me that they are all some kind of variations to strategy as you have also mentioned, but from another aspect I thought that before GoF patterns there, if we would only know Strategy pattern and implement the same thing that for example Chain of Responsibility does compactly, if it would be a reasonable design just with Strategy and Factories or not. So I have choosen that word. Jul 3, 2020 at 11:56
  • For the visitor you are right, I just meant a workaround for that problem, but what I meant with that note is I didn't wanted to generalize my observation to all behavioral patterns, because I think Visitor as "this as method argument pattern" since somehow bidirectional so a bit complex to me. I don't know if this my own definition is right but I tend to think about like this from implementation technique perspective. That being said I wanted to leave Visitor or maybe Observer out of scope not to make my generalization broader. Jul 3, 2020 at 12:12
2

No, behavioral patterns are not about OOP polymorphism

Design patterns are not about implementing or using OOP features: they are about addressing common design problem with an idea for a solution. The intent ("what for?") matters as much as the solution ("how?"). According to GoF :

Behavioral patterns are concerned with algorithms and the assignment of responsibilities between objects. (...)
Behavioral object patterns use object composition rather than inheritance.

It's not either about single method dynamic dispatch. In C++ for example, some patterns can be implemented with templates, using compile-time type subsititution and static dispatch. Alexandrescu's book "Modern C++ design" gives plenty of examples thereof. Again, what matters is not the OOP mechanism, but your design need, the decoupling of unrelated things, and the optimal distribution of responsibilities.

Template method is not derived from strategy, but both go well together

Template method is in fact a basic trick of the OOP trade. I assume that everybody discovers it him/herself when trying to use polymorphism in more complex scenarios and avoiding to rewrite very similar code.

Example: (very typical and extremely simple) - Imagine some kind of abstract Shape with a surface(), a perimeter() and a print_info() method that prints the surface and the perimeter. Nobody would make 3 separate implementation of print_info(): you would rather write it only once, and let it call the polymorphic methods. If something would be missing to achieve this elegant solution, like the name of the shape, for the sake of DRY, you'd just add a new polymorphic method for this small detail, rather than starting to duplicate almost identical code.

Template method is therefore tightly linked to polymorphism. But it's more a consequence of it, that uses it to bring it to the next level, rather than an implementation of polymorphism.

Strategy has another purpose: instead of deferring some steps of a method to subclasses, it provides a mean to outsource some steps to a foreign objet. So none of the tow patterns is derivated from the other. However, template methods lives well together with strategies, since it's very similar to subcontract a step to a strategy class rather than override it in a derivate.

Combining strategy and template methods to do the rest?

In such an attempt, you ignore completely the intent. The strategy is not just having a class to encapsulate some algorithms. THat's just a solution. It's just a way of using what classes are meant to do! Using a class that can be extended is not the same as using a strategy!

In the end, all patterns are related to the using some class that provides some methods. But this doesn't mean that it's all the same pattern.

Take just as example the observer pattern. If I'd follow your reasoning, I'd say that Subject is a strategy and Observer is a strategy and that Subject is moreover a context for Observer. But doesn't this sound somewhat like nonsense? The whole world would then just be reduced to sets of strategies; How would this help you to solve observer-like problems, i.e. informing interested objects that a state did change?

Conclusion

There might be a misunderstaning of patterns. The pattern is not the name given to an OO structure. THe pattern is a way to solve a specific problem. Or said in a very formal way: you see the pattern in the solution space, whereas they are in reality a bridge between the problem space and the solution space. (Bridge that you're not obliged to take, because sometimes it's shorter to jump over the river or funnier to swim across, but that's for another discussion ;-))

4
  • Thank you for the nice response, but I want clarify two things that may contribute to this. I tried not to say behavioral patterns are for OOP polymorphism, but rather that mechanism makes it possible to implement them and try to state that other paradigms have other mechanisms for that. GoF patterns are especially for static-typed OOP languages, but as you mentioned I see that they tell from intent perspective to make them universal patterns. The point is when you work with e.g. Java, to really use that patterns you feel to understand the structure also. Jul 4, 2020 at 16:20
  • And when you analyze structures you see some similarities which leads me to ask this question. While I agree intent perspective makes them universal and when you think in a solution its natural and helpful, it makes it hard to grasp the pattern in its own, since you work in the language environment so you are inclined to think in its techniques IMHO. Secondly, I agree in my reasoning it's hard to cover observer and visitor, but Chain of Responsibility and some others looked like possible to me. Jul 4, 2020 at 16:33
  • 1
    @TahaYavuzBodur you are fully right to ask the question which is very interesting. And indeed, composition over inheritance is the common implementation pattern here. I wanted to highlight a mental trap: design patterns are for design problems. Otherwise, they would be called implementation patterns (or idioms) ;-). Also, they build on top of polymorphism. If you would have no polymorphism at all, these patterns would not help. You'd first need to implement some kind of dispatch (you can do this in non-OOP languages such as C with for example aggregates of function pointers).
    – Christophe
    Jul 4, 2020 at 17:09
  • 1
    @TahaYavuzBodur I think morover that many of these patterns can be applied to dynamic languages. As soon as you have a mean to pass a function object or a function reference, you have the tools to build them. In pure functional many of those patterns make no sense (e.g. no state, no observer). But some still make sense: a strategy function can be passed as parameter to a function to influence the behavior. A higher order function could even be used to determine the right strategy of the context function. But that would lead to a very different (and long) discussion ;-)
    – Christophe
    Jul 4, 2020 at 17:14

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.