Refactoring to Patterns - Object-Oriented Analysis and Design | CSCI 6448, Study notes of Computer Science

Material Type: Notes; Professor: Anderson; Class: OO ANALYSIS & DESIGN; Subject: Computer Science; University: University of Colorado - Boulder; Term: Unknown 1989;

Typology: Study notes

Pre 2010

Uploaded on 02/13/2009

koofers-user-c4z
koofers-user-c4z 🇺🇸

9 documents

1 / 21

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
Lecture 22: Refactoring to
Patterns
Kenneth M. Anderson
Object-Oriented Analysis and Design
CSCI 6448 - Spring Semester, 2005
1
March 31, 2005 © University of Colorado, Boulder, 2005
2
Credit and Goals
Credit where credit is due
Some of the material for this lecture is taken from “Refactoring to
Patterns” by Joshua Kerievsky; as such some of this material is
copyright © Pearson Education, Inc., 2005
Goals of this Lecture
Present the idea of refactoring to patterns
Cover several examples
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15

Partial preview of the text

Download Refactoring to Patterns - Object-Oriented Analysis and Design | CSCI 6448 and more Study notes Computer Science in PDF only on Docsity!

Lecture 22: Refactoring to

Patterns

Kenneth M. Anderson Object-Oriented Analysis and Design CSCI 6448 - Spring Semester, 2005

Credit and Goals

Credit where credit is due Some of the material for this lecture is taken from “Refactoring to Patterns” by Joshua Kerievsky; as such some of this material is copyright © Pearson Education, Inc., 2005 Goals of this Lecture Present the idea of refactoring to patterns Cover several examples

March 31, 2005 © University of Colorado, Boulder, 2005

Refactoring to Patterns

Refactoring is the process of transforming code such that functionality is maintained while improving the code’s structure Refactoring advocates small/safe transformations easy to learn/apply Design Patterns are solutions to recurring design problems that can be “rendered” into code in a straightforward way In existing software systems, design patterns also transform code i.e., the code was in state A before the pattern is applied and in an improved state, state B, after the pattern is applied As such, design patterns represent “targets” for refactoring Not only improving a code’s structure but adding a time-tested solution to a common design problem to the code

Refactoring Directions

Viewed in this way, a refactoring can be viewed as taking code to, towards, or away from a particular design pattern For example, some refactorings replace one pattern with another Such a refactoring simultaneously moves away from the original pattern and to the new pattern An example is a refactoring called “Move Accumulation to Visitor” which replaces the use of the Iterator pattern with the use of the Visitor pattern The towards direction is interesting; this occurs when you start a refactoring that leads to a particular design pattern but you only complete a few of the steps. The author, Joshua Kerievsky, states “... this book contains numerous refactorings that provide acceptable design improvements whether you go towards or all the way to [the target pattern].”

March 31, 2005 © University of Colorado, Boulder, 2005

Replace Conditional Logic with Strategy

Conditional logic in a method controls which of several variants of a calculation are executed. Create a Strategy for each variant and make the method delegate the calculation to a Strategy instance Strategy is a design pattern that separates an object and its behavior for a particular method (the behavior is put into its own object; the original object delegates to this new object) Mechanics Create a strategy class; name it after the behavior being performed by the calculation; optionally add the word “Strategy” to the class name Apply Move Method to move the calculation method to the strategy; the original method now delegates to this new method (compile/test) Allow clients of the original class to choose a strategy (compile/test) Apply “Replace Conditional With Polymorphism” to produce strategy subclasses that remove the conditional logic from the original method

Example

Consider a Loan class that needs to calculate capital public class Loan... public double capital() { if (expiry == null && maturity != null) return commitment * duration() * riskFactor(); if (expiry != null && maturity == null) { if (getUnusedPercentage() != 1.0) return commitment * getUnusedPercentage() * duration() * riskFactor(); else return (outstandingRiskAmount() * duration() * riskFactor()) + (unusedRiskAmount() * duration() * unusedRiskFactor()); } return 0.0; }

March 31, 2005 © University of Colorado, Boulder, 2005

Create a strategy class

We are strategizing the capital method, so we create the following: public class CapitalStrategy { public double capital() { return 0.0; } } Recall that refactoring advocates taking small, safe steps Now, we will use “Move Method” to move the capital() method from Loan to CapitalStrategy resulting in...

Move Method

The biggest change is the addition of the loan parameter public class CapitalStrategy... public double capital(Loan loan) { if (loan.getExpiry() == null && loan.getMaturity() != null) return loan.getCommitment() * loan.duration() * loan.riskFactor(); if (loan.getExpiry() != null && loan.getMaturity() == null) { if (loan.getUnusedPercentage() != 1.0) return loan.getCommitment() * loan.getUnusedPercentage() * loan.duration() * loan.riskFactor(); else return (loan.outstandingRiskAmount() * loan.duration() * loan.riskFactor()) +(loan.unusedRiskAmount() * loan.duration() * loan.unusedRiskFactor()); } return 0.0; }

March 31, 2005 © University of Colorado, Boulder, 2005

Structure Before/After

Loan

Before capital(): double

After

Loan capital(): double newTermLoan(): Loan CapitalStrategy capital(Loan): double TermLoanStrategy capital(Loan): double RevolverStrategy capital(Loan): double

Replace Implicit Tree with

Composite

Description You implicitly form a tree structure, using a primitive representation, such as a String. Replace your primitive representation with a Composite Example String expectedResult = “” + “” + “” + “8.95” + “Fire Truck” + “” + “230.0” + “Toy Porshe Convertible” + “” + “”;

March 31, 2005 © University of Colorado, Boulder, 2005

String as Tree

orders order product product price product

Mechanics

Identify an implicit leaf, a part of the implicit tree that could be modeled with a new class. Create a leaf node class with instance variables for keeping track of the implicit leaf’s contents and attributes; Compile and Test Replace every occurrence of the implicit leaf with an instance of the new leaf node; Compile and Test Repeat steps 1 and 2 for any additional implicit leafs Identify an implicit parent and create a parent node class for it; it needs to implement the “child management” functions of the Composite pattern; Compile and Test Replace every occurrence of the implicit parent with an instance of the new parent node; Compile and Test Repeat steps 4 and 5 until done

March 31, 2005 © University of Colorado, Boulder, 2005

TagNode Class

public class TagNode { private String name = “”; private String value = “”; private StringBuffer attributes; public TagNode(String name) { this.name = name; attributes = new StringBuffer(“”); } public void addAttribute(String name, String value) { attributes.append(“ “+attribute+”=’”+value+”’”); } public void addValue(String vlaue) { this.value = value; } public String toString() { return “<”+name+attributes+”>”+value+”</”+name+”>”; } }

Update writePriceTo

Now that we have a class for the price “implict leaf” we can update the code that creates that portion of the string private void writePriceTo(StringBuffer xml, Product product) { TagNode priceNode = new TagNode(“price”); priceNode.addAttribute(“currency”, product.getCurrency()); priceNode.addValue(product.getPrice()); xml.append(priceNode.toString()); } This class handles all of our implicit leaves; and it can handle our implicit parents too, if we add child management functions to it This is a case where a single node plays all of the roles in the Composite pattern: Component, Leaf, and Composite

March 31, 2005 © University of Colorado, Boulder, 2005

Updates to TagNode

We need to add a collection class to hold a node’s children private List children; We need to add a method to get a list of our children private List children() { if (children == null) { children = new LinkedList(); } return children; } Note: this is an example of “lazy creation” with respect to an instance variable; children remains null until the first time we ask for a list of a node’s children; we do not initialize the instance variable until we need it

Updates to TagNode continued

We need a method to add a child to a node public void add(TagNode child) { children().add(child); } Finally, we need to modify the toString() method to handle a node’s children public String toString() { String result = “<”+name+attributes+”>” Iterator itr = children.iterator(); while (itr.hasNext()) { TagNode node = (TagNode)itr.next(); result += node.toString(); result += value; result += ”</”+name+”>”; return result; }

March 31, 2005 © University of Colorado, Boulder, 2005

Repeat until done!

To complete this refactoring, you would create similar methods for order and orders nodes of the tree we showed previously Your program now explicitly creates a tree structure using the Composite pattern and can output the XML for that tree with a single call: System.out.println(root.toString()); The advantages of doing this refactoring is that you can now easily add new types of leaf nodes and parent nodes Plus, our approach to building the tree allows us to create different XML representations of the tree if needed; we simply build a different type of tree, perhaps using different nodes/attributes

Move Embellishment to

Decorator

Description Code provides an embellishment to a class’s core responsibility; Move the embellishment code to a decorator Background When adding new features to a system, it is common to add new code to old classes; the new code is said to “embellish” the old code with new functionality The problem with this approach is that the embellishment adds new fields, methods, and logic, all of which exists for special-case behavior Motivating Idea Try to place the new functionality in a decorator and then wrap the decorator around the original object at runtime when the new behavior is needed

March 31, 2005 © University of Colorado, Boulder, 2005

Litmus Test

This refactoring should not be used when the target class has a lot of public methods (where “a lot” depends on context) The reason? The Decorator pattern requires transparent enclosures: decorators must implement the entire public interface of the target class Also, this refactoring is discouraged in situations where client code must be aware of the decorators, that is the client code checks the run-time types of the objects that it points at For instance, beware client code that looks like this: if (variable instance of SomeClass) then If you dynamically wrap an instance of SomeClass with a decorator, the above code will fail

Mechanics

Identify or create an enclosure type, an interface or class that declares the public methods needed by clients of the target class Find the conditional logic that adds the embellishment to the target class and remove that logic by applying “Replace Conditional with Polymorphism”; Compile and Test. Step 2 produced one or more subclasses of the embellished class. Transform these subclasses into delegating classes by applying “Replace Inheritance with Delegation”; Compile and Test Each delegating class now assigns its delegate to a new instance of the target class; Ensure that this assignment logic exists in the delegating class’s constructor and gets access to the delegate via a parameter; Compile and Test

March 31, 2005 © University of Colorado, Boulder, 2005

Problem, continued

Then, when a StringNode was asked for its contents, it would check whether it should decode the text string before returning it to the client public class StringNode... public String toPlainTextString() { String result = textBuffer.toString(); if (shouldDecode) result = Translate.decode(result); return result; } } This approach to embellishing StringNode will not scale well; requiring a new flag in toPlainTextString(), a new method in Parser, and a new parameter in StringNode’s constructor for each embellishment

Applying the Refactoring

Identifying an enclosure type The HTML Parser framework had the following class hierarchy Node! AbstractNode! StringNode After analysis, the author selects Node as the enclosure type (the class defining the public interface shared by the target class, StringNode, and our new decorator, DecodingNode) The key factor was finding a class that did not define any instance variables (to avoid having decorators from needlessly inheriting them) First Step: create new subclass, DecodingNode Node! AbstractNode! StringNode! DecodingNode Second Step: make Decoding Node a delegating class Node! DecodingNode ! AbstractNode! StringNode

March 31, 2005 © University of Colorado, Boulder, 2005

Second Step: Replace

Conditional with Polymorphism

Our “conditional” in this instance is the code that looked like this in toPlainTextString() : if (shouldDecode) result = Translate.decode(result); First, we encapsulate this field within StringNode, like so Change constructor public StringNode(..., boolean shouldDecode) { ... setShouldDecode(shouldDecode) Change toPlainTextString() if (shouldDecode()) { result = Translate.decode(result); Add instance variable, getter and setter methods (not shown) private boolean shouldDecode;

Step 2, continued

Now we create our subclass public class DecodingNode extends StringNode { public DecodingNode(...) { super(...); } protected boolean shouldDecode() { return true; -- Decoding Node always decodes } } We update StringNode to no longer require the shouldDecode parameter to its constructor and update its shouldDecode() method to always return false; we also delete the shouldDecode instance variable and its associated setter method

March 31, 2005 © University of Colorado, Boulder, 2005

Step 2 completed

In DecodingNode, we add public String toPlainTextString() { return Translate.decode(super.toPlainTextString()); } and we can delete the shouldDecode() methods in both classes And we are now done with Step 2. We compile and test to make sure that everything still works We failed to show one step, which was having the Parser call the new factory method that we added to StringNode We are now ready to convert DecodingNode to a decorator We start by using the refactoring “Replace Inheritance with Delegation”

Step 3: Replace Inheritance wit

Delegation

First, we add a field to DecodingNode that points to itself private Node delegate = this; The enclosure type is used to set-up the Decorator pattern We now replace any calls to StringNode methods with calls to the delegate public class DecodingNode extends StringNode... public String toPlainTextString() { return Translate.decode(delegate.toPlainTextString()); } } This code will compile but not run, since it causes an infinite loop; The delegate object currently points to the calling object!

March 31, 2005 © University of Colorado, Boulder, 2005

Step 3, continued

We now break the inheritance relationship between the two classes public class DecodingNode implements Node DecodingNode now implements the enclosure type interface rather than being a direct subclass of StringNode We do this to keep our factory method code happy! We now set up the delegate instance variable to point to an instance of a StringNode public class DecodingNode implements Node... private Node delegate = null; public DecodingNode(...) { delegate = new StringNode(...); }

Step 3 completed

In DecodingNode, we now implement all of Node’s public methods Each one simply delegates the task to the delegate instance variable public void accept(NodeVisitor visitor) { delegate.accept(visitor); } Finally, to make DecodingNode a decorator, we change its constructor to accept a Node variable to define its delegate public class DecodingNode implements Node... public DecodingNode(Node delegate) { this.delegate = delegate;} And we update our factory method to use the new constructor public class StringNode... public static Node createStringNode(..., boolean shouldDecode) { if (shouldDecode) return new DecodingNode(new StringNode(...)); return new StringNode(...); }}