350 likes | 526 Views
Refactoring to Patterns. CSE301 University of Sunderland Harry R Erwin, PhD. Introduction. Want to use a pattern in an existing programme, but don ’ t know how to do it? Don ’ t panic—this lecture will discuss how to refactor a programme to introduce a pattern. Resource:
E N D
Refactoring to Patterns CSE301 University of Sunderland Harry R Erwin, PhD
Introduction • Want to use a pattern in an existing programme, but don’t know how to do it? • Don’t panic—this lecture will discuss how to refactor a programme to introduce a pattern. • Resource: • Kerievsky, J., 2005, Refactoring to Patterns, Addison-Wesley.
Topics • Standard patterns used in refactoring • Creation—creation methods, factory, builder, and singleton • Simplification—composition, strategy, decorator, state, composite, command • Generalization—template, composite, observer, adapter, interpreter • Protection—singleton, null object, generalization • Accumulation (visitor) patterns—at the end if time is available. • Miscellany—utilities
Standard Patterns • These two patterns are used in refactoring, but are never clearly defined: • Method Object or Functor • Reference and Value Objects
Method 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.
Functor Implementation • The class has constructors or factory methods for each possible set of arguments. • You can define default values for arguments if you’re 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.
Reference 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.
Overriding 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.
Overriding hashCode() for Value Objects • The signature of hashCode() is • public inthashCode(); • 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.
Creation 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.
“Replace 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.
“Move 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. You’re done. • Avoid overuse of the Factory pattern. That will overcomplicate your design.
“Encapsulate 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.
“Inline Singleton” • Too many globals are bad, so you’ve become addicted to Singleton classes. Your code needs access to a unique object, but doesn’t need a global point of access. This is a normal situation. • Solution: Move the Singleton’s 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.
Simplification • Most of your code starts out complicated. The goal of these refactorings is to redesign it for simplicity.
“Compose Method” • You can’t understand a method’s 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.
“Replace 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.
“Move Embellishment to Decorator” • Code embellishes a class’s 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.
“Replace State-Altering Conditionals with State” • The conditional expressions controlling an object’s 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!
“Replace 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.
Generalization • Converts specific code to general-purpose code. • Removes duplicated code. • Simplifies and clarifies code.
“Form 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.
“Replace 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.
“Unify 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.
Protection • These refactorings improve the protection of existing code.
“Replace Type Code with Class” • A field’s 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 field—which becomes private. • Create a concrete class to hold the field. • Instantiate in the using class and switch over.
“Limit 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 it’s read-only.
“Introduce 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.
Miscellany • Low-level refactorings • Chain Constructor • Unify Interface • Extract Parameter
“Chain 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.
“Unify 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.
“Extract 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. • It’s 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.
Accumulation • A lot of code accumulates information for a summary report • Move Accumulation to Collecting Parameter • Move Accumulation to Visitor
“Move 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.
“Move 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 it’s the only good solution. It’s needed when you have to run several algorithms on the object structure. Think XML, generating a test report, or logging.