Software Design Patterns

Computer Science 340
Software Design & Testing
Inheritance
1
Class reuse
• Two forms of class reuse:
– Class inheritance
– Object composition
A
B
A
B
Composition
Inheritance
2
Class Inheritance
•
This class is like that class except for
these differences …
–
Specialize superclass behavior by
overriding methods
•
•
–
A
Totally replace a superclass operation
Add pre/post processing before/after
superclass operation
B
Extend superclass by adding new
variables/operations
3
Class Inheritance
•
Inheritance establishes a subtyping
relationship with between subclass and
superclass, thus enabling polymorphism
–
–
Polymorphism = Subtyping + Dynamic
method binding
A
B
Subclass instances may be used
anywhere superclass instances are
expected
•
Liskov Substitution Principle
4
Inheritance-based reuse example
•
Microsoft Foundation Classes (MFC)
•
Visual Studio generates code for a functional Windows application
•
MFC base classes provide a lot of standard Windows functionality, and
define specific integration points for app-specific code
•
Subclasses provide app-specific functionality
MFCApplication
MFCWindow
1
NetworkApplication
MFCView
1
1
NetworkWindow
1..*
*
DiscoveryView
MFCDocument
BrowseView
TopologyView
1
NetworkDocument
5
Object Composition
B
A
• Client class creates an internal instance of existing class
and invokes its functionality through regular method calls
• Generally results in looser coupling than the inheritance
relationship
– With inheritance, changes to A are more likely to break B than
with composition
• No subtyping relationship is established between the two
classes, thus preventing polymorphism
• Extra levels of indirection in method calls can reduce
efficiency, but this usually isn’t a problem
6
Composition-based reuse
example
• Order Shipper
FedExShipper
OrderShipper
UPSShipper
USMailShipper
7
Choosing between
Composition and Inheritance
• What type of relationship is being
modeled?
– B “has-a” A => composition
– B “uses-a” A => composition
– B “is-a” A => inheritance (usually)
8
Choosing between
Composition and Inheritance
• “Favor object composition over class
inheritance.” [Design Patterns, pg. 20]
• Composition is:
– More flexible than inheritance
– Leads to lower coupling than inheritance
– Allows control over which delegate
features are exposed, and what the API
looks like
9
Choosing between
Composition and Inheritance
• Inheritance:
– Supports polymorphism, while composition
does not
– Is easier if you want to expose many of the
superclass’ features
• Although Eclipse has a handy “Generate
Delegate Methods” option for exposing
delegate features if you’re using composition
10
Choosing between
Composition and Inheritance
• Dynamic “is-a” relationships should be
implemented using composition
– Inheritance represents a static relationship
between two classes that cannot be changed at
runtime
– Composition relationships can be changed at
runtime (more flexible than inheritance)
11
Choosing between
Composition and Inheritance
• Dynamic “is-a” relationships should be implemented
using composition
• Example: Vocations
– This inheritance-based design is inflexible because once a
person has been created, their vocation cannot change
Person
Engineer
Doctor
Lawyer
SalesPerson
12
Choosing between
Composition and Inheritance
• Dynamic “is-a” relationships should be implemented
using composition
• Example: Vocations
– This design uses a combination of composition and
inheritance to allow a person’s vocation to change over
their lifetime
Person
Engineer
Vocation
Doctor
Lawyer
SalesPerson
13
Choosing between
Composition and Inheritance
• Another example of a
dynamic “is-a”
relationship
Employee
Hourly
Employee
Employee
Hourly
Employee
Commissioned
Employee
Salaried
Employee
Payment
Classification
Commissioned
Employee
Salaried
Employee
14
Choosing between
Composition and Inheritance
• Multi-valued “is-a” relationships should also be
implemented using composition
– i.e., each instance can be a member of multiple
subclasses
– Example: Test Tracker users can have multiple roles
User
Test Manager
Dev Manager
Administrator
Tester
Developer
15
Choosing between
Composition and Inheritance
• Multi-valued “is-a” relationships should also be
implemented using composition
– Could create a subclass for every possible combination
– Doesn’t scale, and won’t work if relationship is both
multi-valued and dynamic
User
Test Manager
...
AdministratorTester
Dev Manager
Administrator
AdministratorTesterDeveloper
Tester
Developer
AdministratorDeveloper
TesterDeveloper
...
16
Choosing between
Composition and Inheritance
• Multi-valued “is-a” relationships should also be
implemented using composition
– Example: Use a combination of composition and
inheritance
User
Role
*
Test Manager
1..*
Dev Manager
Administrator
Tester
Developer
17
Specialization: a set-based perspective
• A class is a “set of instances”
• A class’ instances share some common properties
and operations
A
18
Specialization: a set-based perspective
• A “specialization” of a class is a subset of its
instances that have even more properties and
operations in common
A
X
19
Specialization: a set-based perspective
• Example: Dog is a specialization of Animal
– In addition to all Animal features, Dogs also have a bark
operation
• Example: Rectangle is a specialization of Polygon
– In addition to all Polygon features, Rectangles also have a
diagonal property
Animal
Dog
20
Specialization: a set-based perspective
• Specialization is frequently implemented using
inheritance
A
Z
A
X
X
Y
Z
Y
21
Specialization: a set-based perspective
• If instances can move between specializations at runtime,
composition must be used because inheritance relationships
are static (i.e., dynamic “is-a” relationships)
A
Z
A
X
X
Y
Z
Y
22
Specialization: a set-based perspective
• If instances can be members of multiple specializations at the
same time, composition should be used (i.e., multi-valued “isa” relationships)
A
A
*
Z
X
X
1..*
Y
Z
Y
23
Inheritance-based reuse
•
Inheritance is a more tightly coupled relationship than composition
– Inheritance is sometimes called “white box reuse”, and composition is
called “black box reuse”
•
To reduce this coupling, the “subclass interface” between a base class
and its subclasses should be carefully designed
•
Some programmers, when they realize they need to subclass an
existing class, make all the “private” features “protected”, make all the
methods “virtual”, and say, “There, now it’s a base class!”.
•
This approach results in extremely high coupling between a base class
and its subclasses, and results in a fragile base class (i.e., a base class
that is difficult to change without breaking its subclasses)
•
Information hiding is still a good practice, even between super- and
sub-classes
24
Designing the subclass interface
• Subclasses need to:
– Access base class features in
ways not available through
the public interface
– Specialize base class
behavior
Client Client
Public Interface
Base Class
Subclass Interface
Sub Class
25
Designing the subclass interface
• Base classes should keep their
variables private when possible
• Base classes should provide
protected methods that allow
subclasses to access or modify
their state only in necessary,
controlled ways
• Make a variable protected only if
there is a very good reason to do
so (other than being in a hurry)
Client Client
Public Interface
Base Class
Subclass Interface
Sub Class
26
Designing the subclass interface
•
•
Keep methods private when
subclasses don’t need to access or
override them
Base classes should define
polymorphic methods for aspects of
their behavior that subclasses can
specialize
– Virtual methods in C++
– All methods in Java
•
•
Make a method polymorphic only if
you expect subclasses to specialize it
Specifically prevent specialization of
methods that subclasses should not
override
Client Client
Public Interface
Base Class
Subclass Interface
Sub Class
– Non-virtual methods in C++
– “final” methods in Java
27
Designing the subclass interface
• Keeping the subclass interface
as simple as possible has two
positive outcomes:
– There is more freedom to
change the internal
implementation of the base class
without breaking subclasses
(i.e., base classes are less
fragile)
– It is easier for subclass authors
to understand how to specialize
the base class (i.e., they have
less freedom, but more
guidance)
Client Client
Public Interface
Base Class
Subclass Interface
Sub Class
28
Subclass interface examples
• Logger class
• Test Tracker Base Panel class
– see Javadocs for a description of
BasePanel’s subclass interface
29
Patterns for designing the
subclass interface
• Template Method pattern
• Factory Method pattern
30
Template Method pattern
• Common Wisdom: Code that is duplicated in multiple places
should be centralized in one place (i.e., avoid duplication)
– Composition: Put common code in a method on a class to which
multiple clients will delegate
– Inheritance: Put the common code in a method on a super-class,
and make the clients sub-classes (i.e., clients inherit common code)
• What if an algorithm is duplicated in several places, but the
copies are SIMILAR rather than IDENTICAL?
• Use the Template Method pattern
• Put the common algorithm in a super-class
• Clients inherit common code from super-class
• Some steps of the algorithm are delegated to subclasses
through polymorphic method calls
• Subclasses customize the algorithm by implementing the
delegated steps
31
/**
* An abstract class that is common to several games in
* which players play against the others, but only one is
* playing at a given time.
*/
//Now we can extend this class in order
//to implement actual games:
class Monopoly extends Game {
abstract class Game {
/* Implementation of necessary concrete methods */
void initializeGame() {
// Initialize players
// Initialize money
}
void makePlay(int player) {
// Process one turn of player
}
boolean endOfGame() {
// Return true if game is over
// according to Monopoly rules
}
void printWinner() {
// Display who won
}
/* Specific declarations for the Monopoly game. */
protected int playersCount;
abstract void initializeGame();
abstract void makePlay(int player);
abstract boolean endOfGame();
abstract void printWinner();
/* A template method : */
public final void playOneGame(int playersCount) {
this.playersCount = playersCount;
initializeGame();
int j = 0;
while (!endOfGame()) {
makePlay(j);
j = (j + 1) % playersCount;
}
printWinner();
}
}
// ...
}
32
Factory Method pattern
• Factory Method pattern
– A super-class contains useful functionality that can be
inherited by sub-classes
– The super-class needs to instantiate an object to do its work,
but it doesn’t know the concrete class of the object it needs,
so it can’t call new
– Instantiation of the object is delegated to sub-classes, which
do know which concrete class to instantiate
33
Factory Method Pattern
34
interface Vehicle{
public void drive();
public void clean();
}
class Car implements Vehicle{
@Override
public void drive(){
System.out.println("Driving a car...");
}
@Override
public void clean(){
System.out.println("Cleaning a car...");
}
}
class Bus implements Vehicle{
@Override
public void drive(){
System.out.println("Driving a Bus...");
}
@Override
public void clean(){
System.out.println("Cleaning a Bus...");
}
abstract class VehicleDriver{
public abstract Vehicle getVehicle();
public void driveVehicle(){
getVehicle().drive();
}
public void cleanVehicle(){
getVehicle().clean();
}
}
class CarDriver extends VehicleDriver{
@Override
public Vehicle getVehicle(){
return new Car();
}
}
class BusDriver extends VehicleDriver{
@Override
public Vehicle getVehicle(){
return new Bus();
}
}
}
35
public class FactoryMethodPattern {
public static void main(String[] args) {
handleVehicle(new CarDriver());
handleVehicle(new BusDriver());
}
static void handleVehicle(VehicleDriver2 vDriver){
System.out.println("Handling a new vehicle.");
vDriver.driveVehicle();
vDriver.cleanVehicle();
}
}
Handling a new vehicle.
Driving a car...
Cleaning a car...
Handling a new vehicle.
Driving a Bus...
Cleaning a Bus...
36
public class MazeGame {
public MazeGame() {
Room room1 = makeRoom();
Room room2 = makeRoom();
room1.connect(room2);
this.addRoom(room1);
this.addRoom(room2);
}
protected Room makeRoom() {
return new OrdinaryRoom();
}
}
public class MagicMazeGame extends MazeGame {
@Override
protected Room makeRoom() {
return new MagicRoom();
}
}
37