11

There is a fairly helpful question already along these lines ("Non-OOP Design Patterns?"), but I am more curious about a transitional point of view for someone just getting started with dynamic and weakly-typed languages.

That is: let's say I've been programming in C++, C#, or Java for many years, and absorbed lots of wisdom along the lines of the GoF design patterns, Fowler's Patterns of Enterprise Application Architecture, SOLID principles, etc. Now I'm dabbling in Ruby, Python, JavaScript, etc., and wondering how my knowledge applies. Presumably I could do direct translations in many cases, but almost certainly that wouldn't be taking full advantage of my new setting. Duck typing alone turns a lot of my interface-based thinking on its head.

What stays the same? What changes? Are there guiding principles like SOLID, or canonical patterns (perhaps entirely new ones) that a dynamic language newbie should know?

4 Answers 4

7

What stays the same? What changes?

The patterns are the same. The language techniques change.

Are there guiding principles like SOLID,

Yes. Indeed, they remain the guiding principles. Nothing changes.

or canonical patterns (perhaps entirely new ones) that a dynamic language newbie should know?

Some things are unique. Mostly the impact is that the implementation techniques change.

A pattern is -- well -- a pattern. Not a law. Not a subroutine. Not a macro. It's just a good idea that gets repeated because it's a good idea.

Good ideas don't go out of style or change dramatically.

Other notes. Python is not "weakly typed". It's more strongly-typed than Java or C++ because there's no cast operation. [Yes, there is a way to fudge the class associated with an object, but it's not the kind of thing that's done except to prove a fussy, legalistic point.]

Also. Most design patterns are based on different ways to exploit polymorphism.

Look at State or Command or Memento as examples. They have class hierarchies to create a polymorphic states, commands or mementos of state changes. Nothing changes significantly when you do this in Python. Minor changes include the relaxation of the precise class hierarchy because polymorphism in Python depends on common methods not common ancestors.

Also, some patterns are simply an attempt to achieve late binding. Most Factory-related patterns are an attempt to allow easy change to a class hierarchy without recompiling every C++ module in the application. This isn't as interesting optimization in a dynamic language. However, a Factory as a way to conceal implementation details still has huge value.

Some patterns are an attempt to drive the compiler and linker. Singleton, for example, exists to create confusing globals but at least encapsulate them. Python singleton classes aren't a pleasant prospect. But Python modules already are singletons, so many of us just use a module and avoid trying to mess with a Singleton class.

7
  • I wouldn't say that "nothing changes" with SOLID. Depending on the language and its object model, the Open-Closed Principle and Liskov Substitution Principle may both be meaningless. (JavaScript and Go both come to mind.) Commented Apr 18, 2011 at 23:15
  • @Mason Wheeler. Open-Closed is language independent in my experience. You'll have to provide some more concrete examples of how open-closed design is "meaningless" with JavaScript or Go. Liskov substitution, perhaps, doesn't apply to JavaScript, but the essential pattern -- polymorphism -- still seems to apply.
    – S.Lott
    Commented Apr 18, 2011 at 23:18
  • @S.Lott: Nice updates in the edit; they were much more interesting than the original answer :P. Thanks for correcting my Python mistake. In general the specific pattern examples and how they tie into dynamic languages, polymorphism, late-binding, etc. are perfect.
    – Domenic
    Commented Apr 18, 2011 at 23:23
  • @S.Lott: Because Open/Closed is about inheritance, which those languages don't have. (Also, the idea of an object being "closed for modification" wouldn't sit well with a lot of Ruby coders...) Commented Apr 19, 2011 at 0:02
  • @Mason Wheeler: Thanks for the clarification on Open/Closed. I think the JavaScript exception is important, but since the question is so open (listing JavaScript, Python and Ruby, as well as a language called ETC) I'm not sure how to address the special case.
    – S.Lott
    Commented Apr 19, 2011 at 0:33
8

Peter Norvig's took on this very question in 1998, read http://norvig.com/design-patterns/ppframe.htm for a set of detailed things he noticed, and http://c2.com/cgi/wiki?AreDesignPatternsMissingLanguageFeatures for further discussion around the point.

The short version is that when your language has more features, then repetitive design patterns tend to become simpler - often to the point of being invisible. He found that this was true for most of the design patterns that the GoF had identified.

8

Programming in a dynamic object oriented language uses many of the same patterns and principles, but there are certain tweaks and differences due to the environment:

Replace Interfaces with Duck Typing -- Where the Gang of Four would tell you to use an abstract base class with pure virtual functions, and you would use an interface in Java, in a dynamic language, you only need an understanding. Since you may use any object anywhere, and it will work just fine if it implements the methods that are actually called, you don't need to define a formal interface. It may be worth documenting one, so that it's clear what is actually required.

Functions are Objects Too -- There are lots of patterns that are about separating decision from action; Command, Strategy, Chain of Responsibility, etc. In a language with first-class functions, it's often reasonable to simply pass a function around instead of making objects with .doIt() methods. These patterns transform into "use a higher order function."

SOLD -- The Interface Segregation Principle takes the biggest hit here, on account of there being no interfaces. You should still consider the principle, but you can't reify it into your code. Only personal vigilance will protect you here. On the up side, the pain caused by violating this principle is much reduced in common dynamic environments.

"...in my own Particular... Idiom!" -- Each language has good practices and bad practices, and you'll have to learn them, and follow them, if you want the best code in those languages. A perfectly written iterator pattern may be laughed off in a language with built in list comprehensions, for example.

3

In my experience, some Patterns are still useful in Python, and even easier to set up than in more static languages. Some Patterns OTOH are just not needed, or even frowned upon, like the Singleton Pattern. Use a module level variable or function instead. Or use the Borg Pattern.

Instead of setting up a Creational Pattern it's often enough to pass a callable around that creates objects. That might be a function, an object with a __call__ method or even a class, since there is no new() in Python, just an invocation of the class itself:

def make_da_thing(maker, other, stuff):
    da_thing = maker(other + 1, stuff + 2)
    # ... do sth
    return da_thing

def maker_func(x, y):
     return x * y

class MakerClass(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
...
a = make_da_thing(maker_func, 5, 8)
b = make_da_thing(MakerClass, 6, 7)

State and Strategy Pattern share a very similar structure in languages like C++ and Java. Less so in Python. Strategy Pattern stays more or less the same, but State Pattern becomes mostly unnecessary. State Pattern in static languages simulates the change of class at runtime. In Python, you can do just that: change the class of an object at runtime. As long as you do it in a controlled, encapsulated way, you should be fine:

class On(object):
    is_on = True
    def switch(self):
        self.__class__ = Off

class Off(object):
    is_on = False
    def switch(self):
        self.__class__ = On
...

my_switch = On()
assert my_switch.is_on
my_switch.switch()
assert not my_switch.is_on

Patterns that rely on Static Type Dispatch will not work, or work quite differently. You don't have to write as much boiler plate code, e.g. Visitor Pattern: in Java and C++ you have to write an accept method in every visitable class, whereas in Python you can inherit that functionality through a mixin class, like Visitable:

class Visitable(object):
    def accept(self, visitor):
        visit = getattr(visitor, 'visit' + self.__class__.__name__)
        return visit(self)
...

class On(Visitable):
    ''' exactly like above '''

class Off(Visitable):
    ''' exactly like above '''

class SwitchStatePrinter(object): # Visitor
    def visitOn(self, switch):
         print 'the switch is on'
    def visitOff(self, switch):
         print 'the switch is off'

class SwitchAllOff(object): # Visitor
    def visitOn(self, switch):
         switch.switch()
    def visitOff(self, switch):
         pass
...
print_state = SwitchStatePrinter()
turn_em_off = SwitchAllOff()
for each in my_switches:
    each.accept(print_state)
    each.accept(turn_em_off)

Many situations that call for the application of a Pattern in a Static Language don't do so as much in Python. Many things can be solved with other thechniques, like higher order functions (decorators, function factories) or meta classes.

1

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.