Le Monday 16 June 2008 20:35:22 George Sakkis, vous avez écrit : > On Jun 16, 1:49 pm, Gerard flanagan <[EMAIL PROTECTED]> 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 -- http://mail.python.org/mailman/listinfo/python-list