Title: Refactoring to Patterns
1Refactoring to Patterns
- CSE301
- University of Sunderland
- Harry R Erwin, PhD
2Introduction
- Want to use a pattern in an existing programme,
but dont know how to do it? - Dont panicthis lecture will discuss how to
refactor a programme to introduce a pattern. - Resource
- Kerievsky, J., 2005, Refactoring to Patterns,
Addison-Wesley.
3Topics
- Standard patterns used in refactoring
- Creationcreation methods, factory, builder, and
singleton - Simplificationcomposition, strategy, decorator,
state, composite, command - Generalizationtemplate, composite, observer,
adapter, interpreter - Protectionsingleton, null object, generalization
- Accumulation (visitor) patternsat the end if
time is available. - Miscellanyutilities
4Standard Patterns
- These two patterns are used in refactoring, but
are never clearly defined - Method Object or Functor
- Reference and Value Objects
5Method Object or Functor
- It is possible to make a method into a class
instance. This allows you to - pass it to other methods as an object,
- store it in a jump table,
- save it in a log file,
- save it in an undo queue, and
- prioritise it for execution in a priority queue.
6Functor Implementation
- The class has constructors or factory methods for
each possible set of arguments. - You can define default values for arguments if
youre careful. - It also has an execute()/do()/perform()/etc.
method for performing the function. This method
can accept additional arguments. - You can define other methods for converting the
results to a string, etc.
7Reference and Value Objects
- A reference object is a business object like
customer or account. - Each reference object stands for something in the
real world. You use object identity () to test
for equality. (equals() defaults to this test.)
You may have to use a factory method to ensure
non-duplication of reference objects. Consider
this in designing your system. - A value object stands for something like money,
defined entirely through its data values (usually
immutable and declared final). You override
equals() and hashCode() and use equals() to test
for equality.
8Overriding equals() for Value Objects
- The signature of equals() is
- public boolean equals(Object obj)
- You should do the following
- Test obj for instanceof(YourClass). This will
return true if obj belongs to your class or a
subclass. You may need to watch out for
subclassing here. - Typecast obj to YourClass
- Compare the field values
- Return true if everything matches, else return
false.
9Overriding hashCode() for Value Objects
- The signature of hashCode() is
- public int hashCode()
- If two objects are equal(), they must have the
same hashCode() values. - There are at least three approaches
- Compute the hashCode() of the component fields
(you may need to autobox primitive types) and xor
them together. - Compute the hashCode() of toString() applied to
the component fields (you may need to autobox
primitive types) and xor them together. - Compute a hashCode() arithmetically. See Bloch,
Effective Java Programming Guide (Addison-Wesley)
for help.
10Creation Patterns
- These refactorings address some of the common
problems in this area. - The Creation Method pattern is new. It describes
the replacement of a collection of constructors
with static methods that do the same thing more
clearly. - Factory classes usually implement one or more
Creation Methods.
11Replace Constructors with Creation Methods
- Multiple constructors for a class make it hard to
choose the right one. Perhaps the class instance
is supposed to be a reference object. - Solution replace the constructors with static
(or non-static) creation methods that are easier
to understand. Make the constructors private. - These are often called factory methods.
- It does make object creation for that class
non-standardized, since the new operator is not
available.
12Move Creation Knowledge to Factory
- Data and code to instantiate a class is sprawled
across many classes. - Move the creation knowledge into a single Factory
class to encapsulate the creation logic and
client preferences. Start by writing a static
creation method. Then create a Factory class and
move the creation method into it. Chase down all
the compilation problems that result. Youre
done. - Avoid overuse of the Factory pattern. That will
overcomplicate your design.
13Encapsulate Classes With Factory
- Clients directly instantiate classes that reside
in one package and implement a common interface. - Solution Make the class constructors non-public
and introduce a Factory class to create them.
(See java.util.Collections.) Improves
encapsulation. - First use Extract Method on constructor calls and
then Move Method to move them to the Factory
class. Make the constructors non-public. - Negatives new creation methods are needed for
new kinds of instances. Also limits client
customization as source code is unavailable to
clients.
14Inline Singleton
- Too many globals are bad, so youve become
addicted to Singleton classes. Your code needs
access to a unique object, but doesnt need a
global point of access. This is a normal
situation. - Solution Move the Singletons features to a
class that stores and provides access to the
object. Delete the Singleton. - Use Move Method and Move Field to do this.
- This does complicate a design when objects need
to be passed down chains.
15Simplification
- Most of your code starts out complicated. The
goal of these refactorings is to redesign it for
simplicity.
16Compose Method
- You cant understand a methods logic.
- Solution Transform the logic into a small number
of intention-revealing steps at the same level of
detail. Will communicate and simplify what the
method does. - I use Extract Method heavily to do this,
particularly for GUIs. - Leads to many small methods of 5-10 lines. Can
make debugging a chore.
17Replace Conditional Logic with Strategy
- Sign lots of conditional logic in a method.
- Solution Create a Strategy for each variant and
delegate the computing to the Strategy instance.
The Strategies encapsulate the various possible
conditions. Think about using the Prototype
pattern to avoid creation overhead. - It can complicate a design or algorithm.
18Move Embellishment to Decorator
- Code embellishes a classs core responsibility.
- Solution Move it to a Decorator. Note that the
Decorator class has to implement the public
interface of the decorated class, so you may want
to refactor towards this solution, rather than
all the way. - First create the Decorator class (mucho work).
Then find where the choice of embellishment is
made and instead construct a Decorator around the
base class. Note Decorators may be constructed
around Decorators.
19Replace State-Altering Conditionals with State
- The conditional expressions controlling an
objects state are complex. - Solution Replace the conditionals with State
classes that represent specific states. - Find where the state fields are maintained and
encapsulate them in an abstract State class. Use
Extract Subclass to get concrete States. Use Move
Method to delegate responsibilities to the
subclasses. Identify state transitions and switch
the State instance there. Consider using
Prototype. - NB State and Strategy patterns are different!
20Replace Conditional Dispatcher with Command
- Conditional logic (if then else or switch) is
used to dispatch requests and execute actions. - Solution Create a command for each action. Store
in a collection and replace the conditional logic
with code to fetch and execute commands. - This is done using Extract Method over and over
again. Then you Extract Class on each method.
Create a Command interface and an execute()
method for that interface. Build a CommandMap.
Finally replace the conditional dispatcher with
logic that selects the Command from the
CommandMap and executes it.
21Generalization
- Converts specific code to general-purpose code.
- Removes duplicated code.
- Simplifies and clarifies code.
22Form Template Method
- Two methods in subclasses perform similar but not
identical steps in the same order. - Solution Generalize the two methods by
extracting their steps into methods with
identical signatures. Use Extract Method to do
this. - Then pull up the generalized methods to form a
template method in their base class.
23Replace Hard-Coded Notifications with Observer
- Subclasses are defined and hard-coded to notify
an instance of another class of their changes.
The notified class varies with the subclass
selected. - Solution Remove the subclasses by making their
superclass capable of notifying any class that
implements an Observer pattern. The receiver
classes implement that pattern and register for
updates. - You may need to move logic from the subclass to
its receiver to allow this to work.
24Unify Interfaces with Adapter
- Clients interact with two alternative classes,
one of which has a preferred or mandatory
interface. - Solution Unify the interfaces with an adapter.
- Apply Extract Interface to the class with the
preferred interface. - Apply Extract Class to the class that uses the
alternatives. That creates a primitive Adapter. - Use the Adapter instead of the hidden classes.
Use Move Method to make this happen. - Have the Adapter implement the common interface.
25Protection
- These refactorings improve the protection of
existing code.
26Replace Type Code with Class
- A fields type (primitive type or String) does
not protect it from unsafe assignments and
invalid equality comparisons. This can be a
particular problem for enumerations. - Solution Make the type of the field a class (or
enum) so you can constrain those unsafe
operations. - Apply Self Encapsulate Field so you use getters
and setters to access the fieldwhich becomes
private. - Create a concrete class to hold the field.
- Instantiate in the using class and switch over.
27Limit Instantiation with Singleton
- Your code creates multiple instances of an object
and then uses too much memory or slows down. - Solution Replace the multiple instances with a
Singleton - Make sure the state of the object can be shared.
- Replace the constructor with a creation method.
Then insert the standard Singleton logic. - You also might do this with a Map if you still
need more than one object. The Singleton contains
the Map. Note you can decorate the Map with
immutable when you return it so that its
read-only.
28Introduce Null Object
- Your code has lots of logic for dealing with a
null field or variable. - Solution Replace the null value with a Null
Object, which has all the necessary methods
defined and simply does nothing. There are a lot
of examples of this in javax.swing. - Create a NullObject class by applying Extract
Subclass and/or Extract Interface. - Find out what the null checks do in your code and
have the NullObject class do them. - Whenever the field is set to null, set it to an
instance of the NullObject. - You might save a copy of the NullObject in the
superclass and either share it or clone it to cut
execution time. - Eventually, you will remove the null checks.
29Miscellany
- Low-level refactorings
- Chain Constructor
- Unify Interface
- Extract Parameter
30Chain Constructor
- You have multiple constructors with duplicate
code. - Solution Chain them together
- Look for duplication and figure out ways that the
constructors can call each other to eliminate it. - Make any constructors that are internal to this
process non-public. - See the BankingDataReader for an example.
31Unify Interface
- You need a superclass or superinterface to have
the same interface as a subclass. - Solution Find all public methods in the subclass
and put null versions in the superclass. - Often a station on a road to someplace else.
32Extract Parameter
- A method or constructor assigns a field to a
locally instantiated value. - Solution Assign the field to a parameter
supplied by a client by extracting one-half of
the assignment statement to a parameter. - The client supplies a null variable to fill in,
and the constructor or method sets it to the
value. - Its like a website cookie. If the variable is
final, this can even ensure that it is preserved
unchanged until it is needed later. - Often used in swap algorithms.
33Accumulation
- A lot of code accumulates information for a
summary report - Move Accumulation to Collecting Parameter
- Move Accumulation to Visitor
34Move Accumulation to Collecting Parameter
- A bulky method exists that accumulates
information in a local variable. What to do? - A Collecting Parameter is an object that is
passed to methods to accumulate information from
them. - Often used with Compose Method.
- Often used for composites.
- Used in JUnit to accumulate test result
information.
35Move Accumulation to Visitor
- Like the previous refactoring, but the classes
are heterogeneous. - Move the accumulation task to a Visitor.
- Operates on an object structure, each object
providing a double dispatch. The visitor is
passed to the object using an accept(Visitor v)
method defined by the object, and the object
calls v.visitType(this), passing itself to the
appropriate visit method provided by the Visitor. - Visitor is rarely needed, but when needed its
the only good solution. Its needed when you have
to run several algorithms on the object
structure. Think XML, generating a test report,
or logging.