Software design patterns
Overview of Last Lectures • Control Flow Graphs
• If Statements • Loop Statements
• Code Coverage • JaCoCo
• UML • Use Case Diagram • Activity Diagram • Sequence Diagram • Class Diagram
• different relations
• Only your first Attempt counts
• Average of Quiz 3 is 85.8%
Quiz 3
84.4
84.6
84.8
85
85.2
85.4
85.6
85.8
86
86.2
Quiz 1 Quiz 2 Quiz 3
Average
Average
Acknowledge
• Some of the covered materials are based on SE463 at UW, SENG321 at UVic, CSE331 at University of Washington and previous EECS3311 offerings: • Jo Atlee, Dan Berry, Daniela Damian, Marty Stepp, Mike
Godfrey, Jonathan S. Ostroff, M. Ernst, S. Reges, D. Notkin, R. Mercer, Davor Svetinovic, Jack Jiang, Jackie Wang
Outlines
▪ Introduction to Design Patterns ▪ Pattern’s Elements ▪ Types of Design Patterns
▪ Java Design Patterns ▪ The Singleton Pattern ▪ The Factory Pattern ▪ The Builder Pattern ▪ The Prototype Pattern ▪ The Adapter Pattern ▪ The Bridge Pattern ▪ The Composite Pattern ▪ Visitor Design Pattern ▪ Iterator Pattern ▪ State Design Pattern ▪ Event-driven pattern ▪ Observer design patterns
Design patterns • Design pattern: A standard solution to a common
software problem in a context.
• describes a recurring software structure or idiom • is abstract from any particular programming language • identifies classes and their roles in the solution to a
problem
• In 1990 a group called the Gang of Four or "GoF" (Gamma, Helm, Johnson, Vlissides) compile a catalog of design patterns • 1995 book Design Patterns:
Elements of Reusable Object-Oriented Software is a classic of the field
In general, a pattern has four essential elements.
▪ The pattern name
▪ The problem
▪ The solution
▪ The consequences
Pattern’s Elements
The pattern name is a handle we can use to describe a design problem, its solutions, and consequences in a word or two.
▪ Naming a pattern immediately increases the design vocabulary. It lets us design at a higher level of abstraction.
▪ Having a vocabulary for patterns lets us talk about them.
▪ It makes it easier to think about designs and to communicate them and their trade-offs to others.
Pattern’s Elements – The Pattern Name
The problem describes when to apply the pattern.
▪ It explains the problem and its context.
▪ It might describe specific design problems such as how to
represent algorithms as objects.
▪ It might describe class or object structures that are
symptomatic of an inflexible design.
▪ Sometimes the problem will include a list of conditions that
must be met before it makes sense to apply the pattern.
Pattern’s Elements – The Problem
The solution describes the elements that make up the design, their
relationships, responsibilities, and collaborations.
▪ The solution doesn't describe a particular concrete design or
implementation, because a pattern is like a template that can be
applied in many different situations.
▪ Instead, the pattern provides an abstract description of a design
problem and how a general arrangement of elements (classes
and objects in our case) solves it.
Pattern’s Elements – The Solution
The consequences are the results and trade-offs of applying the
pattern.
▪ The consequences for software often concern space and time
trade-offs.
▪ They may address language and implementation issues as well.
▪ Since reuse is often a factor in object-oriented design, the
consequences of a pattern include its impact on a system's
flexibility, extensibility, or portability.
▪ Listing these consequences explicitly helps you understand and
evaluate them.
Pattern’s Elements – The Consequences
Benefits of using patterns
• Patterns give a design common vocabulary for software design: • Allows engineers to abstract a problem and talk about that
abstraction in isolation from its implementation.
• A culture; domain-specific patterns increase design speed.
• Capture expertise and allow it to be communicated: • Promotes design reuse and avoid mistakes.
• Makes it easier for other developers to understand a system.
• Improve documentation (less is needed): • Improve understandability (patterns are described well, once).
Gang of Four (GoF) patterns Erich Gamma, Richard Helm, Ralph Johnson and John Vlisides in their Design Patterns book define 23 design patterns divided into three types:
▪ Creational patterns are ones that create objects for you, rather than having you instantiate objects directly. This gives your program more flexibility in deciding which objects need to be created for a given case.
▪ Structural patterns help you compose groups of objects into larger structures, such as complex user interfaces or accounting data.
▪ Behavioral patterns help you define the communication between objects in your system and how the flow is controlled in a complex program.
Gang of Four (GoF) patterns (cont.)
• Creational Patterns (abstracting the object-instantiation process) • Singleton Factory
• Builder Prototype …
• Structural Patterns (how objects/classes can be combined) • Adapter Bridge Composite
• Decorator Facade Flyweight
• Proxy …
• Behavioral Patterns (communication between objects) • Command Interpreter Iterator
• Observer State
• Visitor Even-driven …
Pattern: Singleton A class that has only a single instance
Singleton pattern
• singleton: An object that is the only object of its type. (one of the most known / popular design patterns)
• Ensuring that a class has at most one instance.
• Providing a global access point to that instance. • e.g. Provide an accessor method that allows users to see the instance.
• Benefits: • Takes responsibility of managing that instance away from the
programmer (illegal to construct more instances).
• Saves memory.
• Avoids bugs arising from multiple instances.
Restricting objects
• One way to avoid creating objects: use static methods • Examples: Math, System
• Is this a good alternative choice? Why or why not?
• Disadvantage: Lacks flexibility. • Static methods can't be passed as an argument, nor
returned.
• Disadvantage: Cannot be extended. • Static methods can't be subclassed and overridden like an
object's methods could be.
• Make constructor(s) private so that they can not be called from outside by clients.
• Declare a single private static instance of the class.
• Write a public getInstance() or similar method that allows access to the single instance.
• May need to protect / synchronize this method to ensure that it will work in a multi-threaded program.
Implementing Singleton
Singleton sequence diagram
• Class RandomGenerator generates random numbers.
public class RandomGenerator {
private static final RandomGenerator gen =
new RandomGenerator();
public static RandomGenerator getInstance() {
return gen;
}
private RandomGenerator() {}
...
}
Singleton example
• Can wait until client asks for the instance to create it:
public class RandomGenerator {
private static RandomGenerator gen = null;
public static RandomGenerator getInstance() {
if (gen == null) {
gen = new RandomGenerator();
}
return gen;
}
private RandomGenerator() {}
...
}
Lazy initialization
Pattern: Factory
The Factory pattern returns an instance of one of several possible
classes depending on the data provided to it.
▪ Here, x is a base class and classes xy and xz are derived from it.
▪ The Factory is a class that decides which of these subclasses to
return depending on the arguments you give it.
▪ The getClass() method passes in some value abc, and returns
some instance of the class x. Which one it returns doesn't matter to the programmer since they all have the same methods, but different
implementations.
How does it Work?
▪ Let's consider a simple case where we could use a Factory class. Suppose we
have an entry form and we want to allow the user to enter his name either as
“firstname lastname” or as “lastname, firstname”(a comma in-between). ▪ Let’s make the assumption that we will always be able to decide the name
order by whether there is a comma between the last and first name.
class Namer { //a class to take a string apart into two names
protected String last; //store last name here
protected String first; //store first name here
public String getFirst() {
return first; //return first name
}
public String getLast() {
return last; //return last name
}
}
The Base Class
In the FirstFirst class, we assume that everything before the last
space is part of the first name.
class FirstFirst extends Namer {
public FirstFirst(String s) {
int i = s.lastIndexOf(" "); //find separating space
if (i > 0) {
first = s.substring(0, i).trim(); //left = first name
last =s.substring(i+1).trim(); //right = last name
} else {
first = “” // put all in last name
last = s; // if no space
}
}
}
The First Derived Class
In the LastFirst class, we assume that a comma delimits the last
name.
class LastFirst extends Namer { //split last, first
public LastFirst(String s) {
int i = s.indexOf(","); //find comma
if (i > 0) {
last = s.substring(0, i).trim(); //left= last name
first = s.substring(i + 1).trim(); //right= first name
} else {
last = s; // put all in last name
first = ""; // if no comma
}
}
}
The Second Derived Class
The Factory class is relatively simple. We just test for the existence
of a comma and then return an instance of one class or the other.
class NameFactory {
//returns an instance of LastFirst or FirstFirst
//depending on whether a comma is found
public Namer getNamer(String entry) {
int i = entry.indexOf(","); //comma determines name order
if (i>0)
return new LastFirst(entry); //return one class
else
return new FirstFirst(entry); //or the other
}
}
Building the Factory
NameFactory nfactory = new NameFactory();
String sFirstName, sLastName;
….
private void computeName() {
//send the text to the factory and get a class back
namer = nfactory.getNamer(entryField.getText());
//compute the first and last names using the returned class
sFirstName = namer.getFirst();
sLastName = namer.getLast();
}
Using the Factory
You should consider using a Factory pattern when:
▪ Create objects dynamically
▪ A class can’t anticipate which kind of class of objects it must create.
▪ A class uses its subclasses to specify which objects it creates.
▪ You want to localize the knowledge of which class gets created.
There are several similar variations on the factory pattern to recognize:
▪ The base class is abstract and the pattern must return a complete working class.
▪ The base class contains default methods and is only subclassed for cases where
the default methods are insufficient.
▪ Parameters are passed to the factory telling it which of several class types to
return. In this case the classes may share the same method names but may do
something quite different.
When to Use a Factory Pattern
The Abstract Factory pattern is one level of abstraction higher than the factory
pattern. This pattern returns one of several related classes, each of which can
return several different objects on request. In other words, the Abstract Factory
is a factory object that returns one of several factories.
One classic application of the abstract factory is the case where your system
needs to support multiple “look-and-feel” user interfaces, such as Windows, Motif
or Macintosh:
▪ You tell the factory that you want your program to look like Windows and it
returns a GUI factory which returns Windows-like objects.
▪ When you request specific objects such as buttons, check boxes and
windows, the GUI factory returns Windows instances of these visual
interface components.
The Abstract Factory Pattern How does it Work?
Suppose you are writing a program to plan the layout of gardens. These could be
annual gardens, vegetable gardens or perennial gardens. However, no matter
which kind of garden you are planning, you want to ask the same questions:
▪ What are good border plants?
▪ What are good center plants?
▪ What plants do well in partial shade?
We want a base Garden class that can answer these questions:
public abstract class Garden {
public abstract Plant getCenter();
public abstract Plant getBorder();
public abstract Plant getShade();
}
A Garden Maker Factory
The Plant class simply contains and returns the plant name:
public class Plant {
String name;
public Plant(String pname) {
name = pname; //save name
}
public String getName() {
return name;
}
}
The Plant Class
A Garden class simply returns one kind of each plant. So, for example, for the
vegetable garden we simply write:
public class VegieGarden extends Garden {
public Plant getShade() {
return new Plant("Broccoli");
}
public Plant getCenter() {
return new Plant("Corn");
}
public Plant getBorder() {
return new Plant("Peas");
}
}
A Garden Class
We create a series of Garden classes - VegieGarden, PerennialGarden, and
AnnualGarden, each of which returns one of several Plant objects. Next, we
construct our abstract factory to return an object instantiated from one of these
Garden classes and based on the string it is given as an argument:
class GardenMaker {
//Abstract Factory which returns one of three gardens
private Garden gd;
public Garden getGarden(String gtype) {
gd = new VegieGarden(); //default
if(gtype.equals("Perennial"))
gd = new PerennialGarden();
if(gtype.equals("Annual"))
gd = new AnnualGarden();
return gd;
}
}
A Garden Maker Class – The Abstract Factory
▪ One of the main purposes of the Abstract Factory is that it
isolates the concrete classes that are generated.
▪ The actual class names of these classes are hidden in the factory
and need not be known at the client level at all.
▪ Because of the isolation of classes, you can change or interchange
these product class families freely.
▪ Since you generate only one kind of concrete class, this system
keeps you for inadvertently using classes from different families
of products.
▪ While all of the classes that the Abstract Factory generates have
the same base class, there is nothing to prevent some derived
classes from having additional methods that differ from the
methods of other classes.
Consequences of Abstract Factory
Pattern: Builder
The Builder Pattern separates the construction of a complex object from its
representation so that the same construction process can create different
representations.
▪ Builder - specifies an interface for creating parts of a Product object.
▪ ConcreteBuilder - constructs and assembles parts of the product by
implementing the Builder interface. Also, it defines and keeps track of the
representation it creates and provides an interface for retrieving the product .
▪ Director - constructs an object using the Builder interface.
▪ Product - represents the complex object under construction.
How does it Work?
▪ The client creates the Director object and configures it with the desired
Builder object.
▪ Director notifies the builder whenever a part of the product should be built.
▪ Builder handles requests from the director and adds parts to the product.
▪ The client retrieves the product from the builder.
The following interaction diagram illustrates how Builder and Director cooperate
with a client.
How does it Work?
Use the Builder pattern when:
▪ The algorithm for creating a complex object should be
independent of the parts that make up the object and how they
are assembled.
▪ The construction process must allow different representations for
the object that is constructed.
Applicability of Builder Pattern
▪ A Builder lets you vary the internal representation of the product it builds. It
also hides the details of how the product is assembled.
▪ Each specific builder is independent of the others and of the rest of the
program. This improves modularity and makes the addition of other builders
relatively simple.
▪ Because each builder constructs the final product step-by-step, depending on
the data, you have more control over each final product that a Builder
constructs.
▪ A Builder pattern is somewhat like an Abstract Factory pattern in that
both return classes made up of a number of methods and objects. The main
difference is that while the Abstract Factory returns a family of related
classes, the Builder constructs a complex object step by step depending on the
data presented to it.
Consequences of Builder Pattern
/** "Product" */
class Pizza {
private String dough = ""; //basic element
private String sauce = ""; //basic element
private String topping = ""; //basic element
public void setDough(String dough) {
this.dough = dough; }
public void setSauce(String sauce) {
this.sauce = sauce; }
public void setTopping(String topping) {
this.topping = topping; }
}
Pizza Builder
/** "Abstract Builder" */
abstract class PizzaBuilder {
protected Pizza pizza;
public Pizza getPizza() {
return pizza; }
public void createNewPizzaProduct() {
pizza = new Pizza(); }
public abstract void buildDough();
public abstract void buildSauce();
public abstract void buildTopping();
}
Pizza Builder
/** "ConcreteBuilder" */
class HawaiianPizzaBuilder extends PizzaBuilder {
public void buildDough() {
pizza.setDough("cross"); }
public void buildSauce() {
pizza.setSauce("mild"); }
public void buildTopping() {
pizza.setTopping("ham+pineapple"); }
}
/** "ConcreteBuilder" */
class SpicyPizzaBuilder extends PizzaBuilder {
public void buildDough() {
pizza.setDough("pan baked"); }
public void buildSauce() {
pizza.setSauce("hot"); }
public void buildTopping() {
pizza.setTopping("pepperoni+salami"); }
}
Pizza Builder
/** "Director" */
class Waiter {
private PizzaBuilder pizzaBuilder;
public void setPizzaBuilder(PizzaBuilder pb) {
pizzaBuilder = pb; }
public Pizza getPizza() {
return pizzaBuilder.getPizza(); }
public void constructPizza() {
pizzaBuilder.createNewPizzaProduct();
pizzaBuilder.buildDough();
pizzaBuilder.buildSauce();
pizzaBuilder.buildTopping();
}
}
Pizza Builder
/** A customer ordering a pizza. */
class BuilderExample {
public static void main(String[] args) {
Waiter waiter = new Waiter();
PizzaBuilder hawaiianPizzaBuilder = new HawaiianPizzaBuilder();
PizzaBuilder spicyPizzaBuilder = new SpicyPizzaBuilder();
waiter.setPizzaBuilder( hawaiianPizzaBuilder );
waiter.constructPizza();
Pizza pizza = waiter.getPizza();
waiter.setPizzaBuilder(spicyPizzaBuilder);
waiter.constructPizza();
Pizza pizza2 = waiter.getPizza();
}
}
Pizza Builder
Pattern: Prototype
▪ A prototype is a template of any object before the actual object is constructed.
▪ The Prototype pattern specifies the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.
▪ A Protoype pattern is used when creating an instance of a class is very time- consuming or complex in some way. Then, rather than creating more instances, you make copies of the original instance and modify them as appropriate.
Example:
▪ Let’s consider the case of an extensive database where you need to make a number of queries to construct an answer. Once you have this answer as a table or ResultSet, you might like to manipulate it to produce other answers without issuing additional queries.
Definition & Applicability
You can make a copy of any Java object using the clone method.
Jobj j1 = (Jobj)j0.clone();
The clone method always returns an object of type Object. You must
cast it to the actual type of the object you are cloning. There are
three other significant restrictions on the clone method:
▪ It is a protected method and can only be called from within the
same class or the module that contains that class.
▪ You can only clone objects which are declared to implement
the Cloneable interface.
▪ Objects that cannot be cloned throw the CloneNotSupported
Exception.
Cloning in Java - I
This suggests packaging the actual clone method inside the class where it can
access the real clone method:
public class SwimData implements Cloneable {
public Object clone()
{
try{
return super.clone();
}
catch(Exception e) {
System.out.println(e.getMessage());
return null;
}
}
}
Cloning in Java - II
▪This implementation has the advantage of encapsulating the try-catch block
inside the public clone method.
▪Note that if you declare this public method to have the same name “clone,” it
must be of type Object, since the internal protected method has that signature. We
could, however, change the name and do the typecasting within the method
instead of forcing it onto the user:
public SwimData cloneMe() {
try{
return (SwimData)super.clone();
}
catch(Exception e) {
System.out.println(e.getMessage());
return null;
}
}
Cloning in Java - III
Shallow vs. Deep (Object) Copy in Java
Thanks: https://dzone.com/articles/java-copy-shallow-vs-deep-in-which-you-will-swim
A reference copy, as the name implies, creates a copy of a reference variable pointing to an object.
An object copy creates a copy of the object itself.
Say we have a Person object. Person object is in fact composed of other objects, as you can see in Example. Person contains a Name object and an Address object. The Name in turn, contains a FirstName and a LastName object; the Address object is composed of a Street object and a City object.
Shallow Copy
Say we have a Person object. Person object is in fact composed of other objects, as you can see in Example. Person contains a Name object and an Address object. The Name in turn, contains a FirstName and a LastName object; the Address object is composed of a Street object and a City object.
Shallow Copy
Shallow Copy
A shallow copy of an object copies the ‘main’ object, but doesn’t copy the inner objects.
…
//Overriding clone() method
protected Object clone() throws CloneNotSupportedException
{
Person p1 = (Person) super.clone();
return p1;
}
…
Deep Copy is a fully independent copy of an object. If we copied the Person object, we would copy the entire object structure.
Deep Copy in Java
…
//Overriding clone() method
protected Object clone() throws CloneNotSupportedException
{
Person p1 = (Person) super.clone();
p1.name = (Name) name.clone();
p1.address = (Address) address.clone();
p1.name.firstname = (FirstName) name.firstname.clone();
p1.name.lastname = (LastName) name.lastname.clone();
p1.adress.street = (Street) address.street.clone();
p1.adress.city = (City) address.city.clone();
return p1;
}
…
Shallow vs. Deep
Let’s write a simple program that reads data from a database and then clones the
resulting object. In our example program, SwimInfo, we just read these data from
a file, but the original data were derived from a large database as we discussed
above.
We create a class called Swimmer that holds one name, club name, sex and time:
class Swimmer {
String name;
int age;
String club;
float time;
boolean female;
public String getName () {return name};
public int getAge () {return age};
public float getTime () {return time};
}
Using the Prototype - I
We create a class called SwimData that maintains a vector of the Swimmers we read in
from the database.
public class SwimData implements Cloneable {
Vector<Swimmer> swimmers;
public Swimmer getSwimmer(int i) {
return swimmers.get(i);};
public SwimData(String filename) {
String s = "";
swimmers = new Vector();
InputFile f = new InputFile(filename); //open file
s= f.readLine(); //read in and parse each line
while(s != null) {
swimmers.addElement(new Swimmer(s));
s= f.readLine();
}
f.close();
}
Using the Prototype - II
We clone this class and sort the data differently in the new class. Again, we clone
the data because creating a new class instance would be much slower, and we
want to keep the data in both forms.
SwimData sdata = new SwimData();
sdata.sortByName(); //sort by name
…
sxdata = (SwimData)sdata.clone();
sxdata.sortByTime(); //re-sort by time
for(int i=0; i< sxdata.size(); i++)
//display sorted values from clone
{
Swimmer sw = sxdata.getSwimmer(i);
System.out.println(sw.getName()+" "+sw.getTime());
}
In the original class, the records
are sorted by name, while in the
cloned class, they are sorted by
time.
Using the Prototype - III
Using the Prototype pattern:
▪ You can add and remove classes at run time by cloning them as
needed.
▪ You can revise the internal data representation of a class at run
time based on program conditions.
▪ You can also specify new objects at run time without creating a
proliferation of classes and inheritance structures.
Consequences of the Prototype Pattern - I
Difficulties:
▪ One difficulty in implementing the Prototype pattern in Java is
that if the classes already exist, you may not be able to change
them to add the required clone or deep Clone methods. The deep
Clone method can be particularly difficult if all of the class
objects contained in a class cannot be declared to implement the
Serializable interface.
▪ Classes that have circular references to other classes cannot
really be cloned.
▪ The idea of having prototype classes to copy implies that you
have sufficient access to the data or methods in these classes to
change them after cloning. This may require adding data access
methods to these prototype classes so that you can modify the
data once you have cloned the class.
Consequences of the Prototype Pattern - II