Refactoring Software
Refactoring Software
Refactoring Software
Masatomo Noborikawa
Under the direction of Dr. Eugene Wallingford
Research Project for Masters of Science in Computer Science
University of Northern Iowa
May 5, 2003
Table of Contents
1. Introduction..............................................................................................................3
2. Overview of Tools ...................................................................................................5
2.1
2.1
4.2
6.2
6.2.1
6.2.2
6.2.3
.....................................................42
7. Conclusion .............................................................................................................52
References....................................................................................................................54
Chapter 1
Introduction
Designing a software system prior to implementation often results in designs
that are incomplete or incorrect. Creating a good design is difficult because designers
do not always know everything they need to know about the solution before
writing the program. Often, the programmer must correct mistakes in the design as he
comes to understand the problem and come up with better solution while writing the
program.
Even when the programmer comes to know that something is not right in the
design, he might not often be able to come up with the correct solution right
away. The process of "refactoring" guides the programmer as he identifies
weaknesses in the design and code and provides methods to improve them. This
research project investigated the use of design patterns and the application of various
refactoring techniques as a means to trigger modifications to designs and programs
that preserve the functionality of the system while improving the quality of the
software. This approach offers a powerful set of tools for programmers to use as they
evolve their designs throughout the writing of a program.
In this research project software called Basic Facts was developed from
scratch. Varieties of refactoring techniques and design patterns were applied to
enhance its functionality. The details of the sequences of these developments are
illustrated throughout the paper which is sub-sectioned in the form of chapters.
Chapter two provides an overview of what design patterns and refactoring are,
and what are their advantages and disadvantages. They are essential tools for this
development. Chapter three explains the fundamentals of Basic Facts and its very
original software. Chapter four describes how the initial design was created and how
design patterns were applied along with modification to the system. Refactoring in
this chapter is used in a different context compared with the way of refactoring in
chapter six: It is done in a more intuitive way. Chapter five talks about the different
types of pattern usage and how they may affect the development of software. Chapter
six shows step-by-step refactoring applied to the second design. Various refactoring
techniques were implemented to enhance the usability of the design patterns that lead
to the final design of the software. Chapter seven provides the conclusive thoughts of
the research.
3
In this paper, italicized words are used to describe abstract classes, bold type
represents normal classes, and words enclosed with <> signify interfaces. Methods (or
message to which owning objects respond) are underlined.
There is a note for code explanation to be mentioned. This paper uses codes
extracted from source to explain refactoring in step-by-step basis. In code tables that
the reader will see in chapter 6, refers to repetitive codes that are previously
mentioned. In addition, codes in bold type highlight changes as compared with ones
in the previous table.
Italicized words followed by a number enclosed with parentheses (e.g. Extract
Method (110)) mean that it is a name of refactoring techniques cataloged on the page
of the given number in Fowlers book (2000).
Chapter 2
Overview of Tools
2.1
Design patterns give novice developers access to the best practices proven by
expert developers.
Design patterns allow developers to think about their designs at a higher level
of abstraction. For example, instead of focusing on low-level details, such as
how to use inheritance, a developer can approach complex systems through a
collection of design patterns that already make the best use of inheritance.
[Disadvantages]
Design patterns will benefit developing software and make it flexible, modular,
and reusable. However, knowing design patterns does not always mean that they will
be usable. Because of the nature of Object-Oriented development, the design tends to
be changed and refined time to time. It is not rare that the designer finds it useful to
apply some design patterns a while after the development starts. Yet compared with
very early development stage, it is often harder to apply patterns in midst of the
development. Design patterns do not fit right away. Program codes require some kind
of cleaning up or transforming for the use of the patterns. Refactoring then comes into
play. Refactoring cleans up codes and help a target design pattern fit in the program
better. Refactoring is a powerful tool to increase the chances of using patterns and
improving the quality of software. The following section will explain refactoring.
2.2
What is Refactoring?
for. The addition of new functionality during refactoring may require severalsteps of backtracking because adding new functionality by itself entails a
change in the behavior of the system and requires other sets of testing
procedures. This is not at the best interest of the programmer who presumably
tries to maintain the very behavior that might change as a result of the new
functionality being inserted. The obvious consequence might waste many
hours of developments.
Refactoring helps spot bugs since it makes the software more comprehensible.
Refactoring turns an adverse design into a good design, which in tern allow for
rapid software development.
Chapter 3
Basic Facts
Basic Facts is a program that helps students learn fundamental math
operations such as addition and subtraction. The software is intended to help teachers
identify the type of mistakes students tend to make and combinations of numbers in
an equation that slow down students calculations. All questions consist of two
numbers and one operation. There are two types of users in this program.
As a student user, the student first identifies himself by typing his name in a
given textbox and kicks off the program. As the identification is done, the program
makes his portfolio (a folder/directory) to store his work behind scenes. The user then
selects a quiz to play from a menu. After his play, the result, such as how much time
was taken to solve each question and which questions were missed, are stored in the
users portfolio.
A teacher user can select any of his students results and view it in a table
format. Each result shows the number of seconds taken to solve each question, how
many questions were missed, and timeouts if the student spent too much time
attempting to answer a question (Figure 1). A decimal number in a grid represents
seconds for the student to complete a particular question. For example, a 1.37 across
from the 4 and under the 3 means that it took the student 1.37 seconds to complete the
problem 4 + 3. By observing the outcome, the teacher may diagnose which problems
the student needs more practice on.
This program has a simple structure and easy-to-use interface. For student users, there
are two operation menus to select from: Addition or Subtraction. Each menu has five
subcategories that become more difficult as the level increases. In order to play,
students select a sub category. They click the buttons on the screen or type the number
keys on the keyboard to provide a solution to the question. After each question is
answered, the program provides feedback and states whether the answer is right or
wrong (Figure 2).
Every time a student plays, his results are kept in files within the directories
that are created when he build his portfolio. To switch to a teacher mode, the user
selects switch to teacher from the file menu, or teacher when the program is run.
The user must enter a password to gain access to the students portfolios. Selecting
Open File from the file menu to view students work pops up a file dialog box. The
teacher user searches for a desired file in the target students folder and clicks to open.
This program was first written in C++ in a Mac OS environment. It has
appealing features and many potential ways to extend its uses. The only stumbling
problem existed was that its source code including its documentation did not exist
limiting future enhancements to it. I then wrote an application in Java mainly because
Java supports Object Oriented programming. My first mission for the development of
this application was to make a Basic Facts application that works as close to the
original as possible.
10
Chapter 4
Initial Prototype
4.1
diagrams provided in this paper have been illustrated using Unified Modeling
Language (UML). Some trivial classes used in this program have been omitted for
convenience.
12
User class is an abstract class that holds behaviors common to both Student and
Teacher (Fig. 3). Both Student and Teacher are extended from User. Each of them
specialize User class. Because Teacher must know the password to gain access to
students play results, the isValidPassword() method is implemented in Teacher
(specification d). The setMenuBar() method is an abstract template method that defers
the creation of menu objects to its subclasses. It was made to meet specifications e),
f), and g). This class hierarchy models the structure described in specification a).
All Menus: FileMenu, AdditionMenu, and SubtractionMenu are set to a
java.awt.Frame instance variable in User when User responds to a setMenuBar()
message. FileMenu class has menu items that are responsible for changing one user
mode to another (the specification c)), showing/hiding a users identity (role or user
name), opening a play result if the user is in teacher mode, and termination of the
program. The AdditionMenu and SubtractionMenu classes have choices that allow the
user to select different math programs (the specification h)). In the response to
changeMode() method in these menu classes, some menu items are inactivated
according to a boolean parameter to meet the specification f) and g).
PasswordDialog is a java.awt.Dialog that asks for a password to create a
Teacher object. UserNameDialog is also an extended java.awt.Dialog to get a unique
user name to create Student object.
Basic Facts in Figure 4 is a class to kick off this program. It creates a
SelectUser object that allows the user to select either the student or teacher modes,
and create a corresponding User object to play. The Sheet class is derived from a
java.awt.Container class that provides a graphical user interface (GUI) allowing the
user to interact with the program or view its contents. In response to a
makeGUIContainer message, Sheet creates GUI objects and returns itself with the
objects to its client. The AnswerSheet class specializes its super class Sheet for a
student user to complete math problems.
All protected methods (starting with #) in the AnswerSheet class are used
for creating and aligning the GUI objects displayed in Figure 2. Three private
methods (starting with -) are needed to control interactions between a <Question>
object and the GUI components needed when a user causes an action event to occur
while solving a math problem. The ResultSheet class contains StatisticTable,
ResultChart, FileNameBox to display a students play result as shown in Figure 1.
Both the AnswerSheet and ResultSheet classes use a <QuestionFactory> object to
create <Question> objects.
13
14
15
4.2
First Refactoring
In my initial design of Basic Facts the program met the specifications outlined
in the previous section and was able to perform its basic working functions. However
when looking at the degree of reusability, this design needed better organization. I
began to look for parts of the code that could be modified for improvement.
While looking through the class diagrams in Figure 3, it is noticed that
AdditionMenu and SubtractionMenu consist of the same methods that behave in the
same way to produce slightly different menus. How can their overlap be extracted and
put into one class to gain generalization? The createMenu() and actionPerformed()
methods in the class AdditionMenu are the key to that question.
As it is obvious, each menus title is hard-coded in the instantiation of
MenuItems in createMenu() and in the creation of AnswerSheets class in
actionPerformed() methods. Repeated code has a potential to cause problems and
should be eliminated if possible. In the actionPerformed() method an AnswerSheet
object is created by passing as a parameter an instance of QuestionFactory with
slightly different arguments. Those arguments should be merged into one because
they belong to the property of quizzes.
16
17
18
19
Figure 12. setMenuBar() method in User class before and after the modification
FileMenu class code has problems similar to the old AdditiomMenu class.
Here a command pattern is used to solve the problem. A Command pattern is an
object behavioral pattern that allows complete decoupling between the user interface
objects and the actions they initiate. Each subclass of <Command> in Figure 13 are
extended from java.awt.MenuItem and implement the <Command> interface.
Figure 14 shows settings for various menu items. As it can be seen, some
menu commands have two parameters passed in their constructors. A this object is
used in StudentMenuCommand, TeacherMenuCommand, to set an action listener. A
currentUser is an instance of the User class whose services are needed for a specific
task to be performed. The execute() method in each of the Command subclasses that
is the interface of Command implemented to execute its own code when an action
occurs on that object. The execute() interface is used to reduce the burden of the
actionPerformed method. Since all instances of menu items added in the createMenu()
method of FileMenu are a Command, an actionPerformed method can get an instance
of command through the e.getSource() method call and perform cmd.execute()
without knowing whose excute() method is being performed.
21
Figure 14. createMenu() and actionPerformed() methods modified using Command pattern.
The initial design utilized only one factory pattern. A Factory method defines
the interface for creating an object while retaining control of which class to
instantiate. After this first refactoring, the program now consists of a combination of
two factory patterns used in this modified program. A new Quiz class that is
introduced in Figure 15 is a composition of Question objects. QuizFactory class is a
factory of Quiz objects, which makes use of QuestionFactory to construct a Quiz
object. The interaction of objects to produce a Quiz object using two factory method
patterns is shown in Figure 16.
In response to a makeQuiz(2) method call from a client of the QuizFactory
object, a QuestionFactory object instantiates a QuestionFactory object and sends a
message makeQuestion() to it. Each time the QuestionFactory object responds to a
message, it creates a Question and returns the question to the sender. When creation
of all questions is complete, QuestionFactory creates a Quiz object with the questions
and returns that object to the client.
22
Figure 15. Class diagram of Quiz and Question with its factory
23
Although many parts of the program have been redesigned and refactored
using several design patterns, there are still more pieces to refine. Several classes
dealing with the graphical user interface (GUI) need modification. The Sheet class
hierarchy could be completely redesigned. For instance, in the AnswerSheet class,
there are too many private and protected methods to create GUI components. This
suggests that this entity needs to be decomposed and split into several classes.
In the following 2 chapters, I will discuss the development stages from making
a many designs as I could to the new addition to Basic Facts, which caused me to
redesign the software. I will also show how the refactoring techniques will
systematically improve the quality of software design and make the design clear
allowing a design pattern to be applied more effectively.
24
Chapter 5
Development Episode
As Kerievsky (2002) mentioned in his draft, Refactoring To Patterns, there are
two ways of using patterns: using them for up-front design and refactoring to patterns.
Using the patterns for up-front design seems right choice, but all the hassle on the use
of patterns may get wasted.
Why may patterns for up-front design tend not to work as intended? Designing
object-oriented (OO) software is hard and it is harder to make it reusable. Crafting
good OO software often requires several times of modifications for defining rightweighted classes and establishing key associations among them (Gamma, et al 1995).
In another word, it takes a while for OO software design to mature. If the up-front
design goes wrong, therefore, the design patterns used at early time has a risk of
wasting development time.
When I first learned design patterns, they sounded a lot promising. So I tried
hard to make use of as many patterns as possible in the first design. It took a long time
to find a place where a certain pattern fit in my code. However, many of them were
not worth usable as much time as I spent to apply them. Often time it made my design
complicated: I ended up doing over-engineering.
I got confused with design patterns when this happened. I have learned design
patterns because I wanted to be a good software engineer. While being frustrated with
the result, I encountered Martin Fowlers book, Refactoring: Improving the Design of
Existing Code. As mentioned in chapter two, it documents a rich catalog of methods
of refactoring, each of which explains a common use for an improvement and the
steps for making that improvement. This book changed the way I do programming
drastically.
With following the catalog of different kinds of refactoring (merciless
refactoring) and test-first programming, I refactored my code and then have started
seeing things that I could not have found out before. As I refactored the code along
with tests, my program got cleaner. The refined program started to show me another
part of the code to refactor. And then the more refined program introduced the use of
patterns naturally. The process of refactoring basically indicated to me the necessity
of patterns in the right place at the right time: I started refactoring to patterns. Instead
25
of thinking about a design that would work for every nuance of a system, test-first
programming enabled me to make a primitive piece of behavior work correctly before
evolving it to the next necessary level of sophistication. Kerievsky (2002)
In an attempt to show feeling the power of merciless, disciplined, or
systematic refactoring, the next chapter deals in detail a step-by-step refactoring with
several case study examples that I encountered during the second design of Basic
Facts.
26
Chapter 6
Modified Prototype
6.1
The first design of Basic Facts met the requirement found in the analysis
phase. However, as mentioned before, it was not flexible enough to evolve and grow.
In addition there were several problems with the first design.
One problem of the first design is that it was difficult to add a new type of a
quiz: specifically multiplication and division. The algorithm of quiz creation in the
first version could not accommodate a set of quizzes with many restrictions. For
instance, x/y where x and y are interchangeable and the answer of x/y must be 0,2,4,8.
That turned out to be more complicated than predicted.
In addition, many objects such as User and AnswerSheet had more
responsibilities than they should have. They were implemented as a mixture of user
interface code and the domain logics in one class. This complicated the entire system
and made it harder to add new functionality. Therefore, the user interface code needed
to be separated from the domain logic. These conflicts led me to redesign the system.
I chose to recreate the project from scratch, using the first design rather then
refactoring. I did this because I came up with the first design without knowing much
about object-oriented programming. My old mind set, which is procedural
programming, got in the way. The class diagram for the second Basic Facts design is
shown in Figure 17.
In the second design quizzes are created using a formatted file that contains
quiz properties rather than using an algorithm to create a specific quiz. The format is
shown in Table 1. In this manner it is much easier to create specific quizzes with more
control. Although there is overhead requiring that all questions be created in the list,
this approach simplifies the design drastically.
This change made the construction of Quiz systematic: as long as the format is
followed, QuizMenuProperty can remain unchanged. QuizPropertyReader reads in
data stored in the formatted quiz file, and BasicProblemParser parses the extracted
data and stores it in QuizProperty objects. With this quiz construction process, any
type of quiz may be constructed so long as it follows the defined format.
27
28
Example:
==== A page starts here =====
A1
1
+
Addition
Count on and zero generalization
1+1;2+3;
3+1;3+2;4+3;
5+5;
5+2;6+2;
======= End of page ======
The names of many classes have remained the same as in version 1 of Basic
facts, but their behaviors have been changed. Problem class is simplified version of
the MathQuestion class implemented in the first design. An instance of Problem
class holds the question of a problem and its answer as String. It also records the
answer and the response time given by the student. My first goal of the second design
was to simplify the design, so the interface <Question> is removed and the Problem
class is introduced as a concrete class.
Quiz class is a collection of Problem objects stored in an instance of Vector.
It implements <Enumeration> to iterate through the collection. The problems method
returns the Vector. The reset method is defined to reset an exhausted iteration back to
the beginning, so a student can iterate through the collection again. QuizResult is a
subclass of Quiz class. The takeStatistics method returns an instance of Statistics
class that defines the computation of average correction rate and response time for all
the Problem objects held in an instance of Quiz.
29
Portfolio class works as a folder that holds the history of quizzes solved by a
user. It defines several methods for adding, removing, and displaying a QuizResult
instance. In Portfolio class, the clear method delegates to a Hashtable instance
variable which maps a quiz type to a Vector instance.This vector holds a collection of
the portfolios QuizResult instances. The count method returns the number of specific
types of QuizResult instances dependent upon the quiz type passed in as a parameter.
The method countAll returns the total number of quizzes in a Portfolio object. The
getResults method returns as a list a given type of QuizResults as List type. The
removeAllResults method remove all instances a given type of QuizResult class,
whereas removeResult method removes a given individual instance.
This Portfolio class is defined to keep a record of quizzes taken, provide a
method to gather a specific set of Problems solved and compute the corresponding
statistics. Yet in order to solve the computation, Portfolios client needs access to
several other classes for example, Statistics and QuizResult. In a subsequent section,
a composite pattern will be introduced and applied to Problem-Quiz-Portfolio
hierarchy to simplify the computation.
User class defines a user that has a Portfolio instance and stores his name and
password. The first design of the User class was implemented as a subclass of
java.awt.Window. This version of User class is much simpler and represents only the
logic of a user.
UserIO is a utility class that reads an instance of User objects and writes to a
file using the java.io.ObjectInputStream/ObjectOutputStream class. The first
design of Basic Facts stored a students work in the file. This change was
implemented to extend usability so a user object could be passed through the network
to store in and retrieve from a server if needed in future use.
BasicFacts class subclasses javax.swing.JFrame class and conducts all
creations of objects such as JMenuBar delegating to FileMenuFactory and
QuizMenuFactory instances.
The BasicFactsMediator class coordinates the interactions of various visual
components (buttons and text fields) and data models (Quiz, Problem, User). Even
though, there are only a few visual objects, the interactions between the visual
controls tends to be rather complex. This is because each visual object needs to know
about other visual objects in order to update the related visual contents and data
models dynamically. A Mediator pattern simplifies such a system by the creation of a
mediator which is the only class that is aware of the other classes in the system.
30
This keeps the GUI components from referring to each other explicitly and isolates
interactions into the only one instance of BasicFactsMediator.
Each control component the Mediator communicates with is called a
colleague. AnswerSheet is a set of colleagues (gui components and quizzes) that
provides access to each colleague. AnswerSheet class is, in a sense, a Facade pattern.
Its intent is to simplify the complexity of the sub system by providing a simplified
interface to other subsystems. AnswerSheet eases the communication of the visual
components by providing a number of simple methods.This helps simplify the
implementation of BasicFactsMediator.
The QuizPropertyLookup class holds instances of the QuizProperty class.
Whenever the instance is created, the getInstance method returns only one instance of
QuizPropertyLookup which keeps the global point of access to the object. This
QuizPropertyLookup has changed very little since the last version of Basic facts.
31
6.2
Systematic Refactoring
access to the public static fields, their access modifiers were changed to private and
then all collaborating programs are compiled starting from the root class,
BasicFactsApp.java as shown in Table 3.
$ javac BasicFactsApp.java
.\BasicFactsMediator.java:117: defaultScreenSize has private access in BasicFact
s
_board.setSize(BasicFacts.defaultScreenSize);
^
1 error
Table 3. Step to find who has access to the public static fields in BasicFacts class
The result indicated that the line 117 in BasicFactsMediator class attempted to
access the private field defaultScreenSize as public (Table 3). As suspected, only one
public static field, defaultScreenSize was accessed as intended, but the others were
unnecessarily publicized. With this in my mind, the modifiers of the fields in basic
facts class were changed back to public. Next I applied the replace temp with query
(120) refactoring technique to the defaultScreenSize variable. In Replace temp with
query (120), temp refers to a temporary local variable, which is assigned a simple
expression. Sometimes this temp gets in the way of other refactoring.
The problem with temps is that they are temporary and local. They can be seen
only in the context of the method in which they are used; temps tend to encourage
longer methods. Temps also opt to keep programmers from seeing duplicate codes.
By replacing the temp with a query method, any method in the class can get to the
information. This results in cleaner code for the class. Even with this issue, Replace
Temp with Query (120) often is a vital step before using Extract Method (110)
refactoring. Extract Method (110) is another refactoring technique that takes a clump
of code and turns it into its own method. The use of local variables can make it
difficult to extract the code, so it is better to replace as many local variables as
possible with queries.
After the changes were made (Table 4) I compiled and tested the code. I then
replaced all the instance of defaultScreenSize variable in the BasicFacts class and the
BasicFactsMediator class with getFrameSize() method. After compiling and testing I
could now apply replace temp with query(120) to the centerPoint variable and refactor
again. Eventually I was able to eliminate all public static fields as shown below in
Table 5:
33
}
public static Dimension getFrameSize(){
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
screenSize.setSize(screenSize.width*3/5,screenSize.height*3/5);
return screenSize;
}
Table 4. BasicFacts after replace temp with query performed on defaultScreenSize field
Public class BasicFacts
public BasicFacts(){
User currentUser
= readUser();
AnswerSheet ansSheet
= new AnswerSheet(getFrameSize());
BasicFactsMediator mediator = new BasicFactsMediator(this);
mediator.registerAnswerSheet(ansSheet);
mediator.registerUser(currentUser);
QuizMenuFactory qmFactory = new QuizMenuFactory(mediator);
FileMenuFactory fmFactory = new FileMenuFactory(currentUser,this);
Enumeration menus = qmFactory.makeMenu();
JMenuBar mb = new JMenuBar();
mb.add(fmFactory.makeMenu());
while(menus.hasMoreElements()){
JMenu m = (JMenu) menus.nextElement();
mb.add(m);
}
setJMenuBar(mb);
setLocation(getCenterPoint());
setSize(getFrameSize());
repaint();
setVisible(true);
}
protected Point getCenterPoint(){
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
return new Point((screenSize.width-getFrameSize().width)/2,
(screenSize.height-getFrameSize().height)/2);
}
public static Dimension getFrameSize(){
34
The end result is that each method reveals its intent without referring to actual
code in it. Now the code was ready for Extract Method (110) refactoring. But, before
taking that step, there were still three local variables in the constructor that needed to
be dealt with. I turned the local variables: currentUser, ansSheet and mediator into
fields as described on Table 6:
public class BasicFacts
private User currentUser;
private AnswerSheet ansSheet;
private BasicFactsMediator mediator;
public BasicFacts(){
currentUser = readUser();
ansSheet = new AnswerSheet(getFrameSize());
mediator = new BasicFactsMediator(this);
mediator.registerAnswerSheet(ansSheet);
mediator.registerUser(currentUser);
QuizMenuFactory qmFactory = new QuizMenuFactory(mediator);
FileMenuFactory fmFactory = new FileMenuFactory(currentUser,this);
Enumeration menus = qmFactory.makeMenu();
JMenuBar mb = new JMenuBar();
mb.add(fmFactory.makeMenu());
while(menus.hasMoreElements()){
JMenu m = (JMenu) menus.nextElement();
mb.add(m);
}
setJMenuBar(mb);
setLocation(getCenterPoint());
setSize(getFrameSize());
repaint();
setVisible(true);
}
Then after applying Extract Method (110), a sizeable chunk of the code turned
into a method whose name clearly indicated its function: setupMenuBar(). There are
several reasons to use extract method refactoring to refine your code:
1) It generates well-named methods and well-named methods increase the
chances of method reuseablity.
2) It created finely grained methods. Thus making the code more programmerfriendly.
3) It allows the higher-level methods to read more like a series of comments.
4) Overriding is easier when the methods are finely grained.
This is achieved by first applying the Extract method (110) refactoring technique
to the code of JMenuBar Construction as shown Table 7:
35
Next in Table 8, Extract method (110) is applied to the method that set up the
mediator code.
public class BasicFacts
public BasicFacts(){
currentUser = readUser();
ansSheet = new AnswerSheet(getFrameSize());
mediator = new BasicFactsMediator(this);
setupMediator();
setupJMenuBar();
setLocation(getCenterPoint());
setSize(getFrameSize());
repaint();
setVisible(true);
}
protected void setupMediator(){
mediator.registerAnswerSheet(ansSheet);
mediator.registerUser(currentUser);
}
36
37
Figure 18. The comparison of before and after refactoring to QuizProperty class.
38
39
Now using Self Encapsulate Field (171) refactoring, all of the problem field
variables used in the makeQuiz() method of QuizProperty class were replaced with
their equivalent getter method, getAllproblems() as shown in Table 11.
Table 11. makeQuiz method in QuizProperty class after self encapsulate field refactoring
40
I added a this reference to each of the getter method calls to make it easier to
change later when Move Method (142) would be applied in the QuizMenu class.
Finally, the code is moved in the makeQuiz() method in the QuizFactory class and the
required parameter list is updated for makeQuiz.
41
Again, I compiled and tested the QuizProperty class. Now, the makeQuiz
method of QuizFactory class is ready to be used, instead of the makeQuiz method of
QuizProperty class. This means that we need to change all the corresponding
references in the code. For example, change a passing parameter in PrepareQuiz()
method call in BasicFactsMediator class as illustrated in table 14.
BasicFactsMediator class
After the changes were made, the code was compiled and tested. Next, the
makeQuiz() method in the QuizProperty class was removed. See the class diagram on
the bottom in figure 18.
Figure 19. Class diagram for Portfolio, Statistics, QuizResult, Quiz, and Problem classes.
43
44
Figure 20. The class diagram for Portfolio-Quiz-Statistics-QuizResult relationship after Move Method
45
46
The end result clearly shows that the three classes Portfolio, Quiz and
Problem reside in the same hierarchy: Portfolio has instances of quizzes which in
turn hold Problem objects. This relationship naturally triggers the use of composite
patterns. Composite pattern arranges objects into tree structures to represent partwhole hierarchies. Composite lets clients treat individual objects and compositions of
objects uniformly. Actual tests or exams are considered to represent a composite
relationship. One quiz may contain one or more quizzes. This is the perfect place to
apply a composite pattern.
The first quiz composite structure was implemented as shown in figure 22.
QuizComponent defines all methods needed for QuizLeaf and QuizComposite that
in turn subclass the super class and specialize necessary behaviors.
In order to accommodate the quiz composite into the system, Adaptor pattern
is applied. The intent of Adaptor pattern is to provide the interface a client expects,
using the services of a class with a different interface (Gamma et al. 1995). Using
Adaptor pattern, I made Problem a subclass of QuizLeaf as shown figure 23.
47
Figure 23. After adaptor pattern applied to make Problem a subclass of QuizLeaf
> rm *.class
> javac BasicFactsApp.java
.\BasicFactsMediator.java:86: getProblemDescription() has private access in Problem
_sheet.setProblem(_currentProblem.getProblemDescription());
^
1 error
> rm *.class
> javac BasicFactsApp.java
>
Only one getProblemDescription() method call had taken place. This method
call was then replaced with getProblem(). I removed the definition of the
getProblemDescription() method from the Problem class. Then I compiled and tested.
Now all Problem types can be replaced with QuizLeaf if needed. There is no reason
to change, so I decided to leave it as it is.
In the same manner, I applied Adaptor pattern to Quiz class. The result is
shown in Figure 24.
48
49
Figure 26. Class diagram for the second design of basic facts after all the refactoring
50
The new design, according to Fig 26, is very flexible and balanced. Since the
hierarchy of quiz classes supports framework, with most of the classes remaining
untouched, it is a simple matter to add a new quiz type. The new quiz type would not
be required to be math related application since that behavior has been abstracted out
of the quiz class. As long as the new applications quiz follows the format described
before, most of the classes will remain untouched and be easily reused, except for
QuizFactory, BasicFactsMediator and AnswerSheet that are specific to each
application. Compared to the first design of Basic Facts, this latest version is greatly
reusable and easily accommodates new requirements.
51
Conclusion
Through this research project, I learned design patterns and refactorings along
with the development of Basic Facts. I applied several patterns and systematic
refactoring techniques, which lead to several design attempts. I observed their benefits
and drawbacks through out the evolution of the software. I discovered that applying
design patterns blindly to the codes makes the design complicated. I came to the
conclusion that it is better to focus on simple and concise design with well-disciplined
refactoring rather than unnecessarily sophisticated design with design patterns. I also
found that design patterns work greatly if they are applied after refactoring. The other
discoveries and difficulties in my research are noted as follows.
[From my experience]
One of the biggest mistakes I made in designing Basic Facts was in struggling
to accommodate a design pattern that did not fit the situation. I discovered that unless
a design pattern absolutely makes tomorrows job easier, one should not bother
struggling to apply patterns to ones system just for the sake of flexibility. Instead,
one should try to make a design simple. As the programming goes, the developer may
refactor when his code starts smelling. Unnecessarily anticipating what may happen in
the future will cause a lot of problems. My experience in this regard taught me that if
a design pattern comes naturally, use it, otherwise let it go. I also learned not to
obsessively think, I have to use a design pattern to make my design more reusable.
Refactoring is always right there for you.
I have found it is much easier to apply design patterns to a program after
refactoring the code. The refactoring process helped me to see things that were not
visible prior to refactoring. Refactoring often triggers modification to the system
through the use of design patterns. Instead of thinking, What pattern may be used
here? it is better to wait for an inspiration to occur during the refactoring process. In
this way a suitable pattern appears more naturally and fits better with the code.
New functionality should not be added during refactoring. Sometimes, even
though we know this principal, we are tempted to add new functionality to the code
during refactoring. Adding new functionality to code while refactoring is dangerous in
that if errors occur it can take a long time and much effort to return to the position
52
where initial refactoring began. Though it can be hard to resist the temptation, rather
than modifying the code, write down any ideas for new functions. In this way you do
not have to lose your idea but also will not break the refactoring principle and
complicate the refactoring process.
I realized that when using search and replace in my refactoring, I need do it
with a great care. It is better to do such edits one by one instead of all at once. There
always is a possibility that non-intended words can be replaced with a keyword,
which is a possibility one with a different letter case (upper or lower case) or one that
appears within a method or variable name. Replacing a wrong key word may not only
slow down the refactoring process but also produce unnecessary bugs.
I used the common sense principle that if I see duplicate code occur three or
more times within a program, it is time to refactor the code. Sometimes a programmer
may favor development speed over conciseness of code. Even though he knows that
duplicate code is a bad practice, he may tend to cut and copy some code when under
the stress of a dead line. If duplicate sets occur several times throughout the program,
refactoring becomes frustrating. Most importantly, duplication complicates code and
makes it harder to maintain.
The refactoring techniques, Extract method and replace temp with query are
simple to use but are the most effective tools in the refactoring process. One powerful
effect of replace temp with query is that its use reduces temporary variables within the
code and improves the readability of each method.
It is difficult to always come up with short and descriptive names for methods
that explain what they are doing. Whenever I encountered that problem, I first named
the function as best I could, then wait for an inspiration derived from refactoring.
With the occurrence of changes to methods and with the growth of my understanding
of the problem, naming methods becomes much simpler and obvious.
And last but not least, thinking too far ahead may not work well. Throughout
the development of this project, I found it more productive to solve a problem which I
currently face unless a solution to the problem is visible that might occur in the near
future. Otherwise, it is easy to loose focus and introduce unnecessary interfaces or
classes to the software.
53
References
Fowler, Martin Refactoring: Improving The Design of Existing Code Reading, Mass.:
Addison-Wesley, 2000.
Cooper, James W. Java Design Patterns: A Tutorial Reading, Mass.: Addison-Wesley,
2000.
Gamma, Erich, R. Helm, R. Johnson, and J. Vlissides. Design Patterns: Elements of
Reusable Object-Oriented Software. Reading, Mass.: Addison-Wesley, 1995.
Kerievsky, Joshua.(2003) DRAFT of Refactoring To Patterns. [On-line resource].
Retrieved May 5, 2003, from the World Wide Web: <
http://industriallogic.com/papers/rtp017.pdf >
Metsker, Steven J. Design Patterns Java Workbook Reading, Mass.: Addison-Wesley,
2002.
54