From b1d7a44b380ecd5b35f6c3284a38df910cc6ae1b Mon Sep 17 00:00:00 2001 From: Jeffrey Date: Wed, 16 Aug 2017 10:11:35 +0800 Subject: [PATCH] Replace the example of the composite pattern The previous example a little wordy. We can use less code to describe the composite pattern. Less code allows us to focus on the pattern self. --- README.md | 2 +- structural/composite.py | 375 +++++++--------------------------------- 2 files changed, 60 insertions(+), 317 deletions(-) diff --git a/README.md b/README.md index 438b3702a..68f284fb5 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ __Structural Patterns__: | [3-tier](structural/3-tier.py) | data<->business logic<->presentation separation (strict relationships) | | [adapter](structural/adapter.py) | adapt one interface to another using a white-list | | [bridge](structural/bridge.py) | a client-provider middleman to soften interface changes | -| [composite](structural/composite.py) | encapsulate and provide access to a number of different objects | +| [composite](structural/composite.py) | lets clients treat individual objects and compositions uniformly | | [decorator](structural/decorator.py) | wrap functionality with other functionality in order to affect outputs | | [facade](structural/facade.py) | use one class as an API to a number of others | | [flyweight](structural/flyweight.py) | transparently reuse existing instances of objects with similar/identical state | diff --git a/structural/composite.py b/structural/composite.py index 9b8cc56fa..887dcf53b 100644 --- a/structural/composite.py +++ b/structural/composite.py @@ -2,339 +2,82 @@ # -*- coding: utf-8 -*- """ -A class which defines a composite object which can store -hieararchical dictionaries with names. - -This class is same as a hiearchical dictionary, but it -provides methods to add/access/modify children by name, -like a Composite. - -Created Anand B Pillai - +*What is this pattern about? +The composite pattern describes a group of objects that is treated the +same way as a single instance of the same type of object. The intent of +a composite is to "compose" objects into tree structures to represent +part-whole hierarchies. Implementing the composite pattern lets clients +treat individual objects and compositions uniformly. + +*What does this example do? +The example implements a graphic class,which can be either an ellipse +or a composition of several graphics. Every graphic can be printed. + +*Where is the pattern used practically? +In graphics editors a shape can be basic or complex. An example of a +simple shape is a line, where a complex shape is a rectangle which is +made of four line objects. Since shapes have many operations in common +such as rendering the shape to screen, and since shapes follow a +part-whole hierarchy, composite pattern can be used to enable the +program to deal with all shapes uniformly. + +*References: +https://en.wikipedia.org/wiki/Composite_pattern +https://infinitescript.com/2014/10/the-23-gang-of-three-design-patterns/ """ -__author__ = "Anand B Pillai" -__maintainer__ = "Anand B Pillai" -__version__ = "0.2" - - -def normalize(val): - """ Normalize a string so that it can be used as an attribute - to a Python object """ - - if val.find('-') != -1: - val = val.replace('-', '_') - - return val - - -def denormalize(val): - """ De-normalize a string """ - - if val.find('_') != -1: - val = val.replace('_', '-') - - return val - - -class SpecialDict(dict): - - """ A dictionary type which allows direct attribute - access to its keys """ - - def __getattr__(self, name): - - if name in self.__dict__: - return self.__dict__[name] - elif name in self: - return self.get(name) - else: - # Check for denormalized name - name = denormalize(name) - if name in self: - return self.get(name) - else: - raise AttributeError('no attribute named %s' % name) - - def __setattr__(self, name, value): - - if name in self.__dict__: - self.__dict__[name] = value - elif name in self: - self[name] = value - else: - # Check for denormalized name - name2 = denormalize(name) - if name2 in self: - self[name2] = value - else: - # New attribute - self[name] = value - - -class CompositeDict(SpecialDict): - - """ A class which works like a hierarchical dictionary. - This class is based on the Composite design-pattern """ - - ID = 0 - - def __init__(self, name=''): - - if name: - self._name = name - else: - self._name = ''.join(('id#', str(self.__class__.ID))) - self.__class__.ID += 1 - - self._children = [] - # Link back to father - self._father = None - self[self._name] = SpecialDict() - - def __getattr__(self, name): - - if name in self.__dict__: - return self.__dict__[name] - elif name in self: - return self.get(name) - else: - # Check for denormalized name - name = denormalize(name) - if name in self: - return self.get(name) - else: - # Look in children list - child = self.findChild(name) - if child: - return child - else: - attr = getattr(self[self._name], name) - if attr: - return attr - - raise AttributeError('no attribute named %s' % name) - - def isRoot(self): - """ Return whether I am a root component or not """ - - # If I don't have a parent, I am root - return not self._father - - def isLeaf(self): - """ Return whether I am a leaf component or not """ - - # I am a leaf if I have no children - return not self._children - - def getName(self): - """ Return the name of this ConfigInfo object """ - - return self._name - - def getIndex(self, child): - """ Return the index of the child ConfigInfo object 'child' """ - - if child in self._children: - return self._children.index(child) - else: - return -1 - - def getDict(self): - """ Return the contained dictionary """ - - return self[self._name] - - def getProperty(self, child, key): - """ Return the value for the property for child - 'child' with key 'key' """ - - # First get the child's dictionary - childDict = self.getInfoDict(child) - if childDict: - return childDict.get(key, None) - - def setProperty(self, child, key, value): - """ Set the value for the property 'key' for - the child 'child' to 'value' """ - - # First get the child's dictionary - childDict = self.getInfoDict(child) - if childDict: - childDict[key] = value - - def getChildren(self): - """ Return the list of immediate children of this object """ - - return self._children - - def getAllChildren(self): - """ Return the list of all children of this object """ - - l = [] - for child in self._children: - l.append(child) - l.extend(child.getAllChildren()) - - return l - - def getChild(self, name): - """ Return the immediate child object with the given name """ - - for child in self._children: - if child.getName() == name: - return child - - def findChild(self, name): - """ Return the child with the given name from the tree """ - - # Note - this returns the first child of the given name - # any other children with similar names down the tree - # is not considered. - - for child in self.getAllChildren(): - if child.getName() == name: - return child - - def findChildren(self, name): - """ Return a list of children with the given name from the tree """ - - # Note: this returns a list of all the children of a given - # name, irrespective of the depth of look-up. - - children = [] - - for child in self.getAllChildren(): - if child.getName() == name: - children.append(child) - - return children - - def getPropertyDict(self): - """ Return the property dictionary """ - - d = self.getChild('__properties') - if d: - return d.getDict() - else: - return {} - - def getParent(self): - """ Return the person who created me """ - - return self._father - - def __setChildDict(self, child): - """ Private method to set the dictionary of the child - object 'child' in the internal dictionary """ - - d = self[self._name] - d[child.getName()] = child.getDict() - - def setParent(self, father): - """ Set the parent object of myself """ - - # This should be ideally called only once - # by the father when creating the child :-) - # though it is possible to change parenthood - # when a new child is adopted in the place - # of an existing one - in that case the existing - # child is orphaned - see addChild and addChild2 - # methods ! - self._father = father - - def setName(self, name): - """ Set the name of this ConfigInfo object to 'name' """ - - self._name = name - - def setDict(self, d): - """ Set the contained dictionary """ - - self[self._name] = d.copy() - - def setAttribute(self, name, value): - """ Set a name value pair in the contained dictionary """ - - self[self._name][name] = value - def getAttribute(self, name): - """ Return value of an attribute from the contained dictionary """ - return self[self._name][name] +class Graphic: + def render(self): + raise NotImplementedError("You should implement this.") - def addChild(self, name, force=False): - """ Add a new child 'child' with the name 'name'. - If the optional flag 'force' is set to True, the - child object is overwritten if it is already there. - This function returns the child object, whether - new or existing """ +class CompositeGraphic(Graphic): + def __init__(self): + self.graphics = [] - if type(name) != str: - raise ValueError('Argument should be a string!') + def render(self): + for graphic in self.graphics: + graphic.render() - child = self.getChild(name) - if child: - # print('Child %s present!' % name) - # Replace it if force==True - if force: - index = self.getIndex(child) - if index != -1: - child = self.__class__(name) - self._children[index] = child - child.setParent(self) + def add(self, graphic): + self.graphics.append(graphic) - self.__setChildDict(child) - return child - else: - child = self.__class__(name) - child.setParent(self) + def remove(self, graphic): + self.graphics.remove(graphic) - self._children.append(child) - self.__setChildDict(child) - return child +class Ellipse(Graphic): + def __init__(self, name): + self.name = name - def addChild2(self, child): - """ Add the child object 'child'. If it is already present, - it is overwritten by default """ + def render(self): + print("Ellipse: {}".format(self.name)) - currChild = self.getChild(child.getName()) - if currChild: - index = self.getIndex(currChild) - if index != -1: - self._children[index] = child - child.setParent(self) - # Unset the existing child's parent - currChild.setParent(None) - del currChild - self.__setChildDict(child) - else: - child.setParent(self) - self._children.append(child) - self.__setChildDict(child) +if __name__ == '__main__': + ellipse1 = Ellipse("1") + ellipse2 = Ellipse("2") + ellipse3 = Ellipse("3") + ellipse4 = Ellipse("4") + graphic1 = CompositeGraphic() + graphic2 = CompositeGraphic() -if __name__ == "__main__": - window = CompositeDict('Window') - frame = window.addChild('Frame') - tfield = frame.addChild('Text Field') - tfield.setAttribute('size', '20') + graphic1.add(ellipse1) + graphic1.add(ellipse2) + graphic1.add(ellipse3) + graphic2.add(ellipse4) - btn = frame.addChild('Button1') - btn.setAttribute('label', 'Submit') + graphic = CompositeGraphic() - btn = frame.addChild('Button2') - btn.setAttribute('label', 'Browse') + graphic.add(graphic1) + graphic.add(graphic2) - # print(window) - # print(window.Frame) - # print(window.Frame.Button1) - # print(window.Frame.Button2) - print(window.Frame.Button1.label) - print(window.Frame.Button2.label) + graphic.render() ### OUTPUT ### -# Submit -# Browse +# Ellipse: 1 +# Ellipse: 2 +# Ellipse: 3 +# Ellipse: 4