Post on 13-Jan-2016
transcript
Apr 21, 2023
Design Patterns and Refactoring
CSC 2053
2
Buzzwords
Design Patterns describe the describe the higher-level organizatihigher-level organization on of of solutions to common problemssolutions to common problems
Design Patterns are a current hot topic in O-O design
Design Patterns
Design Patterns are always described in UML notation
UML is a diagramming language designed for Object-Oriented programming
Design Patterns are used to describe refactorings
3
4
Design Patterns
Refactoring is restructuring code in a series of small, semantics-preserving transformations
(i.e. keeps the code working) in order to make the code easier to maintain and modify
Refactoring often modifies or introduces Design Patterns
5
UML
UML stands for Unified Modeling Language
UML is a complicated diagramming language designed for Object-Oriented programming
UML
UML comprises at least seven or eight different kinds of diagrams that can be used to describe:
the organization of a program how a program executes how a program is used how a program is deployed over a network …and more
We will just deal here with the class diagram
6
7
UML class diagrams
Key: + means public visibility # means protected visibility - means private visibility <blank> means default (package) visibility static variables are underlined
Name of the class
Variables [optional]
Methods
Card
cardId:int-copy:boolean=false«constructor» Card(int id)+isKind(desiredKind:int)+isSharable():boolean+toString():String
Example:
8
UML relationships
A
B
Class Bextendsclass A
C
D1..4
Class Ccontains
1 to 4 objectsof class D
Factory
Product
creates
Other kinds ofrelations
A
B
Class Bimplement
sinterface A
9
Design patterns
It is important in designing object-oriented software that you develop:
a good design, that is reusable,
able to address future problems.
Design Patterns
Many design patterns have been established over the years that can help you do this.
Good software developers reuse solutions that have worked in the past.
So you will find recurring patterns of classes and communicating objects in many systems.
10
11
Problem I: Uncertain delegation
Through polymorphism you can just send a message to an object,
and the object does the right thing, depending on its type
However, you must check first that the object is not null:
if (myObject != null) myObject.doSomething();
EXAMPLES OF NULL OBJECTS
You have an Ocean, represented by a sparse array containing a few Fish
You have a TrafficGrid, some cells of which contains Cars and Trucks and others are empty
You want to send output to some port potentially closed
12
NULL OBJECTS
If you use these types of objects a lot:
your code can end up cluttered with tests for null
We need a We need a Design Pattern Design Pattern to help usto help us
13
14
Solution: Null Object
So create another kind of object, a “null object,”
representing the absence of any other kind of object
E. G.: An Ocean might contain Inhabitants
So class Inhabitant can be sub classed by
BigFish, LittleFish, Algae, and NothingButWater
INHABITANTS HIERARCHY
151515
ABSTRACT CLASS INHABITANTS
LittleFish Big FishNothing But Water
abstract void REPRODUCE();
REPRODUCE(){ SOME CODE}
REPRODUCE(){ SOME CODE};
REPRODUCE() ;NO CODE;
So class Inhabitant has three sub classes:
1. BigFish, 2. LittleFish, Algae, and NothingButWater
Null Object
This way, no location in the Ocean is null
If Inhabitant class contains a method reproduce(),
the subclass NothingButWater could implement this method with an empty method body
16
17
Refactoring: Introduce Null Objects
The general idea is simple, refactor:
Instead of having some variables (locations in the array) be null, have them be “null objects”
However, this may require numerous changes in the code
Refactoring
Changing working code is hazardous —
you introduce bugs that it can take days to find .
Solution: REFACTOR
18
19
Refactoring: to make it safe do: Refactoring has three pillars:
Introduce the Null Objects in small steps,
having an automated set of unit tests, and
running unit tests frequently, so when errors occur you can pinpoint it immediately
20
Introduce Null Object: In detail
1. Create a subclass of the source class to act as a null version of the class.
Create an isNull() method in the source class and in the Null class.
For the source class it should return false, for the Null class it should return true.
All other subclasses of the source will inherit its version
21
Source Class
<<abstract>>isNull()
Null Class
isNull()return True
IsNull() as an Abstract method
isNull()return False
Normal class
22
IsNull() is not an abstract method
Source Class
boolean isNull()return false
Null Class
isNull()return True
isNull()Is inherited
Normal class
The Null class overrides the parent class isNull() method
Null Object
After creating the Null Object class:
1. Compile.
2. Find all places that produce a null when asked for a source object.
3. Replace them with a null object instead.
4. Replace one source and its clients at a time
23
24
Introduce Null Object: In detail, II1. When the result is null for an operation,
2. do some alternative behavior instead.
3. E.g. return a string “Nothing But Water”.
4. For each of these cases, override the operation in the Null class with the alternative behavior.
5. Compile, and test.
25
Source Class
<<abstract>>isNull()
Null ClassNull Class
isNull()return True
isNull()Return False
Normal class
INSTEAD OF THIS CODE :
isNull()Return False
Normal class
26
boolean isNull()return false
isNull()
return “Nothing but Water”
Null ClassNormal Class
isNull()Is inherited
USE AN ALTERNATIVE BEHAVIOR
27
Refactoring details
IMPORTANT:
Use “baby steps”
Do the refactoring a little at a time,
so that it’s very easy to catch and correct errors
USE TESTS THAT ARE already implemented.
IMPORTANT - USE a good set of :
totally automated tests — already implemented.
Otherwise the testing is just too much work, Otherwise the testing is just too much work, and you won’t do itand you won’t do it
JUnit is a great start
28
29
Scenario: Big fish and little fish
The scenario: “big fish” and “little fish” move around in an “ocean”
Fish move about randomly
A big fish can move to where a little fish is (and eat it)
A little fish will not move to where a big fish is
30
BigFish
move()
Fish
<<abstract>>move()
LittleFish
move()
We implement move methods in sub classes which are almost the same
31
Problem: Similar methods in subclasses
We have a Fish class with two subclasses, BigFish and LittleFish
The two kinds move the same way
except a little fish won’t move near a big fish.
To avoid code duplication, the move method ought to be in the superclass Fish
Problem -
Since a LittleFish won’t move to some locations where a BigFish will move
The test for whether it is OK to move really ought to be in the move method in the sub classes
More generally, you want to have almost the same method in two or more sibling classes
32
33
The move() method General outline of the method:
public void move() {
choose a random direction; // same for both
find the location in that direction; // same for both
check if it’s ok to move there; // different
if it’s ok, make the move; // same for both}
34
The Fish refactoring – Phase 1
BigFish
move()
Fish
<<abstract>>move()
LittleFish
move()
Note how first version works:
When a BigFish tries to move,
it overrides the move() method it inherits from Fish
35
Solution - Template Method
Write the oktoMove() method as an abstract method in the super class
This in turn requires that the super class be abstract
ReFactoring -step by step To refactor:
Extract the check on whether it’s ok to move
In the Fish class, put the actual (template) move() method
Create an abstract okToMove() method in the Fish class
Implement okToMove() differently in each subclass 36
37
The Fish refactoring: Phase II
BigFish
move()
Fish
<<abstract>>move()
LittleFish
move()
BigFish
okToMove(locn):boolean
Fishmove() : implemented
<<abstract>>okToMove(locn):boolean
LittleFish
okToMove(locn):boolean
Note how revised version works:
When a BigFish tries to move, it uses the move() method it inherits from Fish
BigFish and LittleFish implement their own okToMove(locn)
38
Template Design
The Design Pattern is called “Template Method”;
The refactoring is called “Form Template Method”.
39
Problem: Constructors create objects
Constructors make objects.Constructors make objects.
OnlyOnly constructors can make objects constructors can make objects..
When you call a constructor of a class, When you call a constructor of a class,
you you willwill get an get an instance of that class.instance of that class.
Singleton classes
Sometimes you want more flexibility than that—
YOU WANT TO:o guarantee that you can never have more than one object
of a given class
o create an object only if you don’t already have an equivalent object
o create an object without being sure exactly what kind of object you want
40
41
Constructors
Although only constructors make objects
You don’t have to call constructors directly—
you can call a method that calls the constructor for you
Several “creational” Design Patterns are based on this observation
42
Singleton Design Pattern – A Creational Design Pattern
A Singleton Design pattern provides a model for building a class that can have only one instance
You may want just one instance of a null object, which you use in many places
Or - to create just one AudioStream, so you can only play one tune at a time
Singleton Design Pattern
You may want to have many printers but one spooler
You might want to have one copy of alphabetic characters in a document
43
Singleton classes
The method :
define a static reference variable of the class type which serves as the single instance of the class.
Make the constructor private so a user can not instantiate the class
Use a static public method that returns the reference to the object
44
45
Singleton Design Pattern
class Singleton { // create a single instance of the class
private static Singleton instance = new Singleton();
// don’t let Java give you a default public constructor private Singleton() { }
// Have a method that returns the instance of the classpublic static Singleton getInstance()
{ return instance; } ...}
See Dice.java
46
Singleton Design Patternpublic class Dice{
// static reference dice identifies the single class instanceprivate static Dice dice = null;private Random rnd;private int dice1, dice2;
// private constructor is called by the method getDice()// to create a single instance of the classprivate Dice(){
// create a random number generatorrnd = new Random();
}
47
Singleton Design Pattern
/*if no object currently exists, the method calls the private constructor to create an instance;
if an object exists, method returns the static reference variable*/
public static Dice getDice(){
if (dice == null){ dice = new Dice(); // create a single dice object}return dice; // the single reference to the class
}
// other methods in the class
48
The Factory Method Design Pattern
Suppose you write a class that works with several different kinds of objects
You can do this if the classes all have a common interface
You may want to be able to create objects, without being dependent on the kind of object
A factory method can create instances of different classes, depending (say) on its parameters
FACTORY DESIGN PATTERN
Example:
public Image createImage (String ext) {
if (ext.equals("gif")) return new GIFImage();
if (ext.equals("jpg")) return new JPEGImage(); ...}
Note that it is not known beforehand what KIND of object will be created.
49
50
The Immutable Design Pattern
There are many benefits to objects that cannot be changed after they have been created
Such objects are called immutable
Objects that refer to an immutable object never have to worry about whether that object has been changed
Immutable objects are thread-safe—this is a significant efficiency concern, because synchronization is expensive
51
immutable objects
Example: Strings in Java
It’s easy to make immutable objects in Java:
Make all instance variables private, and
Provide no methods that change those variables.
Therefore you will have no setter methods
52
Delegation (or, when not to use inheritance)
When you create a subclass, you agree to inherit all its (non-private) fields and methods
What if you don’t want them all?
Example: A Vector can do everything that a Stack should be able to do, and much, much more
Delegation
You may want to inherit just some of the functionality, and probably add some of your own
Inheritance doesn’t let you do that—at least, not easily
53
54
Delegation
If your class wants to hide variables or methods inherited from a superclass, it shouldn’t inherit from that superclass
If an object needs to be a different subclass at different times (say, a LittleFish turning into a BigFish),
then it shouldn’t be a subclass of that class in the first place
Delegation
Instead of inheriting, just use an instance of that class, and delegate to it what needs to be done
In the next example, the stack class should not extend Vector – why????
55
56
Example: Stacks
class Stack {
Vector contents = new Vector();
public void push(Object o) { contents.add(o); // delegate to the Vector }
public Object pop() { return contents.remove(contents.size() – 1); } ...}
57
Design vs. coding
“Design” is the process of determining, in detail,
what the finished product will be and
how it will be put together
“Coding” is following the plan
DESIGN
In traditional engineering (building bridges), design is perhaps 15% of the total effort
In software engineering, design is 85-90% of the total effort
By comparison, coding is cheap
58
59
The refactoring environment
Traditional software engineering is modeled after traditional engineering practices
(design first, then code)
Assumption:
The desired end product can be determined in advance
60
Refactoring
Refactoring is:
restructuring (rearranging) code...
...in a series of small, semantics-preserving changes (i.e. the code keeps working)...
...in order to make the code easier to maintain and modify
61
Refactoring
Refactoring is not just any old restructuring
You need to keep the code working
You need small steps that preserve semantics
You need to have unit tests to prove the code works
There are numerous well-known refactoring techniques
62
When to refactor
When can you refactor?
You should be in a supportive environment (agile programming team, or doing your own work)
You should have an adequate set of automatic unit tests
63
When to refactor
You should not refactor:
Stable code (code that won’t ever need to change)
64
The refactoring environment
“Agile” software engineering is based on different assumptions:
Requirements (and therefore design) change as users become acquainted with the software
65
The refactoring environment
Refactoring is fundamental to agile programming
Refactoring is necessary when the design is found to be flawed
It is advisable to refactor the design - designs should not be immutable
66
A personal view
Design,
because it is a lot more creative than simple coding, is also a lot more fun
Most programming methodologies do not encourage good programming.
Very good programmers find ways to improve the methodologies
67
Back to refactoring
When should you refactor?
Any time you find that you can improve the design of existing code
You detect a “bad smell” (an indication that something is wrong) in the code
Refactor?
When can you refactor?
You should be in a supportive environment (agile programming team,
or doing your own work)
You should have an adequate set of automatic unit tests
68
69
Example 1: switch statements switch statements are very rare in properly designed code
Therefore, a switch statement is a simple and easily detected “bad smell”
Of course, not all uses of switch are bad
A switch statement should not be used to distinguish between various kinds of object
The simplest refactoring for this case is the creation of subclasses
70
Example 1, continued
class Animal {
final int MAMMAL = 0, BIRD = 1, REPTILE = 2; int myKind; // set in constructor ... String getSkin()
{ switch (myKind)
{ case MAMMAL: return "hair"; case BIRD: return "feathers"; case REPTILE: return "scales"; default: return "integument"; } }}
71
Example 1, improved
class Animal {
String getSkin() { return "integument"; }}
class Mammal extends Animal {
String getSkin() { return "hair"; }}
class Bird extends Animal {
String getSkin() { return "feathers"; }}
class Reptile extends Animal {
String getSkin() { return "scales"; }}
72
How is this an improvement?
a new animal type, such as Amphibian,
does not require revising and recompiling existing code
Mammals, birds, and reptiles are likely to differ in other ways,
we’ve separated them out (so no more switch statements)
REfactoring
We’ve gotten rid of the flags we needed
to tell one kind of animal from another
We’re now
using Objects the way they were meant to be used
73
74
JUnit tests As we refactor, we need to run JUnit tests to ensure that we
haven’t introduced errors, e.g.
public void testGetSkin() {
assertEquals("hair", myMammal.getSkin());
assertEquals("feathers", myBird.getSkin());
assertEquals("scales", myReptile.getSkin());
assertEquals("integument", myAnimal.getSkin());}
This should work equally well with either implementation
75
Bad Smell Examples
We should refactor any time we detect a “bad smell” in the code
Examples of bad smells include: Duplicate Code
Long Methods
Large Classes
Long Parameter Lists
Multi -location code changes
76
The End