Removing inheritance (decorator pattern ?)
Maric Michaud
maric at aristote.info
Mon Jun 16 23:10:57 EDT 2008
Le Monday 16 June 2008 20:35:22 George Sakkis, vous avez écrit :
> On Jun 16, 1:49 pm, Gerard flanagan <grflana... at gmail.com> wrote:
> > George Sakkis wrote:
> > > I have a situation where one class can be customized with several
> > > orthogonal options. Currently this is implemented with (multiple)
> > > inheritance but this leads to combinatorial explosion of subclasses as
> > > more orthogonal features are added. Naturally, the decorator pattern
> > > [1] comes to mind (not to be confused with the the Python meaning of
> > > the term "decorator").
> > >
> > > However, there is a twist. In the standard decorator pattern, the
> > > decorator accepts the object to be decorated and adds extra
> > > functionality or modifies the object's behavior by overriding one or
> > > more methods. It does not affect how the object is created, it takes
> > > it as is. My multiple inheritance classes though play a double role:
> > > not only they override one or more regular methods, but they may
> > > override __init__ as well. Here's a toy example:
> >
> > I don't know if it will map to your actual problem, but here's a
> > variation of your toy code. I was thinking the Strategy pattern,
> > different classes have different initialisation strategies? But then you
> > could end up with as many Strategy classes as subclasses, I don't know.
> > (Also in vaguely similar territory
> > -http://bazaar.launchpad.net/~grflanagan/python-rattlebag/trunk/annota...
> > )
> >
> > class MetaBase(type):
> >
> > def __init__(cls, name, bases, data):
> > cls.strategies = []
> > cls.prefixes = []
> > for base in bases:
> > print base
> > if hasattr(base, 'strategy'):
> > cls.strategies.append(base.strategy)
> > if hasattr(base, 'prefix'):
> > cls.prefixes.append(base.prefix)
> > super(MetaBase, cls).__init__(name, bases, data)
> >
> > class Joinable(object):
> > __metaclass__ = MetaBase
> > strategy = list
> > prefix = ''
> >
> > def __init__(self, words):
> > self._words = words
> > for strategy in self.strategies:
> > self._words = strategy(self._words)
> >
> > def join(self, delim=','):
> > return '%s %s' % (' '.join(self.prefixes),
> > delim.join(self._words))
> >
> > class Sorted(Joinable):
> > strategy = sorted
> > prefix = '[sorted]'
> >
> > class Reversed(Joinable):
> > strategy = reversed
> > prefix = '[reversed]'
> >
> > class SortedReversed(Sorted, Reversed):
> > pass
> >
> > class ReversedSorted(Reversed, Sorted):
> > pass
> >
> > if __name__ == '__main__':
> > words = 'this is a test'.split()
> > print SortedReversed(words).join()
> > print ReversedSorted(words).join()
>
> This doesn't solve the original problem, the combinatorial explosion
> of empty subclasses. At the end of the day, I'd like a solution that
> uses a (mostly) flat, single-inheritance, hierarchy, allowing the
> client say:
>
Yes, and it fails to implement the strategy pattern as well... which would
have solved the problem as it is intended exactly for this purpose.
> j = Joinable(words)
> if sort:
> j = Sorted(j)
> if reverse:
> j = Reversed(j)
> ...
> print j.join()
>
The example given by Gerard is hard to translate into a strategy pattern
because it's more often a use case for a decorator-like design (which is
easily rendered with method decorators in python).
A abstract strategy pattern is basically implemented as follow.
class Strategy(object) :
def do_init(self, *args) : raise NotImplementedError
def do_job(self, *args) : raise NotImplementedError
def do_finalize(self, *args) : raise NotImplementedError
# modules can define their own strategies now
class algo_strategy(Strategy) :
...
class algo1_strategy(Strategy) :
...
# whether this is possible or not depend on the implementation
# and should be documented
class algo2_strategy(algo1_strategy) :
...
class MyClassUsingStrategies(object) :
def __init__(self, meth1_strategies=[], meth2_strategies=[]) :
self._meth1_strategies = meth1_strategies
if [ s for s in meth2_strategies
if not isinstance(s, algo_strategy) ] :
raise RuntimeError("Not a valid strategy !")
self._meth2_strategies = meth2_strategies
def meth1(self, arg) :
for i in self._meth1_strategies :
i.do_init(...)
....
for i in self._meth1_strategies :
i.do_job(...)
....
for i in self._meth1_strategies :
i.do_finalize(...)
...
The class complextiy problem is actually solved by :
inst_with_alg1 = MyClassUsingStrategies((algo1_strategy,), (algo1_strategy,))
inst_with_alg1_alg2 = MyClassUsingStrategies(
(algo1_strategy,),
(algo2_strategy,)
)
inst_with_alg12 = MyClassUsingStrategies(
(algo1_strategy, algo2_strategy),
(algo1_strategy, algo2_strategy)
)
etc...
--
_____________
Maric Michaud
More information about the Python-list
mailing list