Extract Composite Refactoring
Extract Composite - Refactoring to a Pattern
Refactoring existing code so that it improves and conforms to a design pattern can be a good thing. Your code will be easier to understand since it follows a known ‘pattern’. Of course you have to need the change - your code will probably be uncomfortable to read and be crying out for clarification.
This refactoring is about implementing a one to many relationship in your code more clearly by refactoring to the Composite design pattern.
Presentation
UML - Before and After
Here is the situation before and after the refactoring, in hand drawn UML.
UML Before
The problem with the “before” situation is:
- The child looping logic is duplicated twice - once in the
FormTag
class and again in theLinkTag
class. And we all know that code duplication is evil and hard to maintain.
By refactoring, we remove the code duplication to a common base class.
UML After
The solution:
- We use the “extract composite” refactoring to put the common looping behaviour in the display() method of a common composite class.
- We make the existing classes subclasses of this new composite class.
Code - Before and After
Here is a python example of the above refactoring. Note that the display() method is actually called toPlainTextString() in this example.
Code Before
# Before
class Node(object):
def __init__(self, name):
self.name = name
def toPlainTextString(self):
return self.name
class FormTag(Node):
def __init__(self):
self.allNodesVector = []
def toPlainTextString(self):
result = ""
for node in self.allNodesVector:
result += node.toPlainTextString()
return result
class LinkTag(Node):
def __init__(self):
self.linkData = []
def toPlainTextString(self):
result = ""
for node in self.linkData:
result += node.toPlainTextString()
return result
f = FormTag()
f.allNodesVector.append(Node("a"))
f.allNodesVector.append(Node("b"))
f.allNodesVector.append(Node("c"))
l = LinkTag()
l.linkData += [Node("x"), Node("y"), Node("z")]
print f.toPlainTextString()
print l.toPlainTextString()
Then we apply the following steps…
- Create a Composite Class - compile
- Make each child container (a class in the hierarchy that contains duplicate child-handling code) a subclass of your composite - compile
- For each method with duplicated looping code
- Move & rename the child reference field UP to the composite using “Pull Up Field”
- Move the method UP to the composite using “Pull Up Method”
- Pull up any relevant constructor code too.
- Check interfaces so that client code using the old composites still works.
Code After
# After
class Node(object):
def __init__(self, name):
self.name = name
def toPlainTextString(self):
return self.name
class Composite(Node):
def __init__(self):
self.children = []
def toPlainTextString(self):
result = ""
for node in self.children:
result += node.toPlainTextString()
return result
class FormTag(Composite):
pass
class LinkTag(Composite):
pass
f = FormTag()
f.children.append(Node("a"))
f.children.append(Node("b"))
f.children.append(Node("c"))
l = LinkTag()
l.children += [Node("x"), Node("y"), Node("z")]
print f.toPlainTextString()
print l.toPlainTextString()
Does the refactoring break anything?
A proper refactoring means you don’t break anything and everything behaves as before.
python before.py
abc
xyz
python after.py
abc
xyz
Confirmed - we get the same output in both cases. :-)