<<

F. Tip and M. Weintraub PATTERNS

Thanks go to Andreas Zeller for allowing incorporation of his materials WHERE THE SOFTWARE TERM COMES FROM

Inspired by reusable elements of design (“patterns”) in the field of

Christopher Alexander, et al. A Language: Towns, Buildings, Construction, 1977 Presents 253 patterns, covering advice on use of materials, physical arrangements of architectural elements, etc.

Examples 159. LIGHT ON TWO SIDES OF EVERY ROOM 173. GARDEN WALL 174. TRELLISED WALK 180. WINDOW PLACE

2 180. WINDOW PLACE

Everybody loves window seats, bay windows, and big windows with low sills and comfortable chairs drawn up to them

In every room where you spend any length of time during the day, make at least one window into a “window place”

3 ONE DIFFERENCE BETWEEN EXPERIENCED AND INEXPERIENCED SOFTWARE

Experienced designers know from experience what works and what doesn’t

Often recognize “standard” design problems and apply “proven” solutions to them

4 PATTERNS IN

The “Gang of Four” catalogued a number of widely used patterns in software design.

GoF = Gamma, Helm, Johnson, Vlissides

5 REUSING EXPERIENCE: DESIGN PATTERNS

Descriptions of communicating objects and classes adapted to solve a design problem in a specific context.

6 REUSING EXPERIENCE: DESIGN PATTERNS

Descriptions of communicating objects and classes adapted to solve a design problem in a specific context.

In other words:

A is a generalized and reusable solution to a similar set of problems.

Design patterns are abstract and must be tailored or adapted to each situation.

7 DESIGNING FOR CHANGE 1. Creating an object by specifying a class explicitly 2. Dependence on specific operations 3. Dependence on hardware/software platform Many design patterns introduce flexibility 4. Dependence on object representations or implementations to avoid common causes of redesign such as: 5. Algorithmic dependencies 6. Tight coupling 7. Extending functionality by sub-classing 8. Inability to alter classes conveniently

8 A DESIGN PATTERN HAS FOUR ELEMENTS

1. Name Key considerations: e.g, Abstract Factory or Visitor ▪ problem & solution have been observed in practice 2. Problem that the pattern addresses ▪ choice of implementation language is 3. Solution the program constructs significant that are part of the pattern

4. Consequences the results and tradeoffs of applying the pattern

9 CLASSIFYING DESIGN PATTERNS

Purpose: what a pattern does Scope 1. Class- 1. Creational concerned with relationship between concerned with creation of objects classes and their subclasses 2. Structural 2. Object-level related to composition of classes or concerned with object relationship objects (more dynamic, may be changed at run- 3. Behavioral time) related to interaction and distribution of responsibility

10 GOF DESIGN PATTERNS CLASSIFIED

creational structural behavioral Interpreter class Factory Method Adapter (class) Template Method

Chain of Resp. Adapter (object) Command Bridge Iterator Abstract Factory Composite Mediator Builder object Decorator Memento Prototype Façade Observer Singleton Flyweight State Proxy Strategy Visitor

11 CREATIONAL PATTERNS

Purpose Abstract the process of creating objects Make a system unaware of how objects are created, composed, and represented

What they do Encapsulate knowledge about which concrete classes a system uses ▪ access created objects via interfaces Hide how instances are created

Provide flexibility for Types of created objects Responsibility for creation How and when objects are created

12 FIVE CLASSIC CREATIONAL PATTERNS

1. Abstract Factory

2. Builder

3. Factory Method

4. Prototype

5. Singleton

13 MOTIVATION: OVER TIME, CLASS MODELS GROW AND INSTANTIATION BECOMES CUMBERSOME.

For example, suppose you have a that have the same construction. sandwich shop that offers a few types of sandwich 1. Prepare ingredients 2. Apply condiments 1. BLT 3. Apply cheese 2. Veggie burger 4. Cook it 3. Reuben 5. Wrap it

This example is adapted from Head First Design Patterns, Freeman and Robson, O'Reilly Media A DESIGN FOR A SANDWICH

Sandwich

prepareIngredients(); applyCondiments(); applyCheese(); cook(); wrapIt();

Veggie BLT Reuben Burger A DESIGN FOR SANDWICH SHOP

Sandwich

SandwichShop prepareIngredients(); applyCondiments(); orderSandwich(); applyCheese(); cook(); wrapIt();

Veggie BLT Reuben Burger ORDERING PROCESS: CREATING SANDWICHES

Sandwich orderSandwich (String myOrder) {

Sandwich sandwich;

if (myOrder.equals("BLT") { sandwich = new BLT(); } else if (myOrder.equals("VeggieBurger") { sandwich = new BLT(); } else if (myOrder.equals("Reuben") { sandwich = new Reuben(); }

sandwich.prepareIngredients(); sandwich.applyCondiments(); Sandwich.applyCheese(); sandwich.cook(); sandwich.wrapIt(); } OVER TIME, THE MENU CHANGES. SANDWICHES COME. SANDWICHES GO.

Sandwich orderSandwich (String myOrder) {

Sandwich sandwich;

if (myOrder.equals("BLT") { 1. BLT sandwich = new BLT(); } else if (myOrder.equals("VeggieBurger") { 2. Veggie burger sandwich = new BLT(); 3. Reuben } else if (myOrder.equals("Reuben") { 4. Turkey Club sandwich = new Reuben(); 5. Falafel }

sandwich.prepareIngredients(); sandwich.applyCondiments(); Sandwich.applyCheese(); sandwich.cook(); sandwich.wrapIt(); } OVER TIME, THE MENU CHANGES. SANDWICHES COME. SANDWICHES GO.

Sandwich orderSandwich (String myOrder) {

Sandwich sandwich;

if (myOrder.equals("BLT") { sandwich = new BLT(); } else if (myOrder.equals("VeggieBurger") {

sandwich = new BLT(); This changes This } else if (myOrder.equals("Reuben") { sandwich = new Reuben(); }

sandwich.prepareIngredients(); sandwich.applyCondiments(); Sandwich.applyCheese(); sandwich.cook();

This doesn't This sandwich.wrapIt(); } SO CREATION ADJUSTS

Sandwich orderSandwich (String myOrder) { Sandwich orderSandwich (String myOrder) {

Sandwich sandwich; Sandwich sandwich;

if (myOrder.equals("BLT") { if (myOrder.equals("BLT") { sandwich = new BLT(); sandwich = new BLT(); } else if (myOrder.equals("VeggieBurger") { } else if (myOrder.equals("VeggieBurger") { sandwich = new BLT(); sandwich = new BLT();

This changes This } else if (myOrder.equals("Reuben") { } else if (myOrder.equals("Reuben") { sandwich = new Reuben(); sandwich = new Reuben(); } } else if (myOrder.equals("TurkeyClub") { sandwich = new TurkeyClub(); sandwich.prepareIngredients(); } else if (myOrder.equals("Falafel") { sandwich.applyCondiments(); sandwich = new Falafel(); Sandwich.applyCheese(); } sandwich.cook(); sandwich.prepareIngredients (); sandwich.wrapIt(); sandwich.applyCondiments ();

This doesn't This } sandwich.cook (); Sandwich.wrapIt (); SO CREATION ADJUSTS

Sandwich orderSandwich (String myOrder) { Sandwich orderSandwich (String myOrder) {

Sandwich sandwich; So you encapsulate what changes if (myOrder.equals("BLT") { sandwich = new BLT(); } else if (myOrder.equals("VeggieBurger") { FACTORY handles sandwich = new BLT(); This changes This } else if (myOrder.equals("Reuben") { object creation sandwich = new Reuben(); } else if (myOrder.equals("TurkeyClub") { sandwich = new TurkeyClub(); } else if (myOrder.equals("Falafel") { sandwich = new Falafel(); } sandwich.prepareIngredients (); sandwich.applyCondiments (); sandwich.cook (); Sandwich.wrapIt (); SIMPLE SANDWICH FACTORY public class SimpleSandwichFactory {

public Sandwich createSandwich (String sandwichType) {

Sandwich sandwich = null;

if (sandwichType.equals("BLT") { sandwich = new BLT(); } else if (sandwichType.equals("VeggieBurger") { sandwich = new BLT(); } else if (sandwichType.equals("TurkeyClub") { sandwich = new TurkeyClub(); } else if (sandwichType.equals("Falafel") { sandwich = new Falafel(); } return sandwich; } } SANDWICH SHOP public class SandwichShop {

SimpleSandwichFactory myFactory;

public SandwichShop(SimpleSandwichFactory factory) { myFactory = factory; }

public Sandwich orderSandwich (String myOrder) {

Sandwich sandwich;

sandwich = myFactory.createSandwich(myOrder);

sandwich.prepareIngredients (); sandwich.applyCondiments (); Sandwich.applyCheese (); sandwich.cook (); sandwich.wrapIt (); return sandwich; } } A DESIGN FOR SANDWICH SHOP

Sandwich

SandwichShop SimpleSandwichFactory prepareIngredients(); applyCondiments(); orderSandwich(); createSandwich(); applyCheese(); wrapIt();

Veggie BLT Reuben Burger A DESIGN FOR SANDWICH SHOP

Sandwich

SandwichShop SimpleSandwichFactory Sandwich is an prepareIngredients(); abstract class or applyCondiments(); an interface orderSandwich(); createSandwich(); applyCheese(); wrapIt();

Materials and methods for each type of sandwich are given (or Veggie over-ridden) here BLT Reuben Burger NOW LET’S GO NATIONAL, BUT ALLOW REGIONAL FLEXIBILITY

Suppose we franchise the sandwich shop to open stores in Seattle, Silicon Valley and Charlotte.

Each area might want to specialize sandwiches to reflect local tastes. Shops will create their own version of sandwiches, which means createSandwich() will need to become shop specific.

The steps to make a sandwich are the same everywhere, but 1. the ingredients may differ 2. how these steps are implemented may differ. public abstract class Sandwich { String bread; String[] ingredients; WHICH LEADS TO… String[] cheese; String[] condiments; public abstract class SandwichShop { String[] toppings;

abstract Sandwich createSandwich(); // default behaviors } prepareIngredients(); applyCondiments(); applyCheese(); public class BOSSandwichShop extends SandwichShop { cook(); wrapIt(); Sandwich createSandwich(String sandwichType) { } if (sandwichType.equals("BLT")) { return new BOSBLT(); } else if (sandwichType.equals("VeggieBurger" { return new BOSVeggieBurger(); public class BOSBLT { } // and so on Public BOSBLT() { bread = "wonder"; sandwich.prepareIngredients(); String[] ingredients = { "beefsteak", sandwich.applyCondiments(); "armour", sandwich.cook(); "iceberg" }; sandwich.wrapIt(); String[] condiment = { "mayo" } ... and the way you make a BLT in Boston } } } public abstract class Sandwich { String bread; String[] ingredients; WHICH LEADS TO… String[] cheese; String[] condiments; public abstract class SandwichShop { String[] toppings;

abstract Sandwich createSandwich(); // default behaviors } prepareIngredients(); applyCondiments(); applyCheese(); public class BOSSandwichShop extends SandwichShop { cook(); wrapIt(); public class SVSandwichShop extends SandwichShop { Sandwich createSandwich(String sandwichType) { } Sandwichif (sandwichType.equals createSandwich(String("BLT")) sandwichType { ) { ifreturn (sandwichType.equals new BOSBLT(); ("BLT")) { } elsereturn if (sandwichType.equals new SVSBLT(); ("VeggieBurger" { public class BOSBLT { }return else ifnew ( sandwichType.equalsBOSVeggieBurger();("VeggieBurger" { } return new SVVeggieBurger(); public class SVBLT { Public BOSBLT() { // }and so on bread = "wonder"; // and so on Public SVBLT() { String[] ingredients = { "beefsteak", sandwich.prepareIngredients(); bread = "Whole Grain"; "armour", sandwich.sandwich.applyCondimentsprepareIngredients(); (); String[] ingredients = { "Abracazebra", "iceberg" }; sandwich.sandwich.cookapplyCondiments(); (); "tofu", String[] condiment = { "mayo" } sandwich.wrapsandwich.cookIt();(); "romaine" }; ... and the way you make a BLT in Boston sandwich.wrapIt(); String[] condiment = { "garlic aoli" } } ... and the way you make a BLT in CA } } } } } FACTORY METHOD

Define an interface for creating an object, but let subclasses decide which class to instantiate Encapsulates instantiation of those objects The creator class is written without knowledge of the specific products that will be created. The creator subclasses are written with this knowledge. In other words, the Factory Method uses inheritance to decide which object to instantiate.

Factory method lets you create objects in a separate operation so that they can be overridden by subclasses

Use factory method when: A class can’t anticipate the class of objects it must create A class wants its subclasses to specify the objects it creates

29 FACTORY METHOD DEFINED

Product Defines the interface of objects created by the factory method ConcreteProduct Implements the Product interface Creator Declares the factory method, which returns a product May define default implementation that returns a default ConcreteProduct object May call factory method to create a product ConcreteCreator Overrides the factory method to return a ConcreteProduct

30 CAN WE ABSTRACT THIS FURTHER?

The materials and methods used to create a sandwich are what vary per region

We can use the factory concept to abstract sandwich creation. CAN WE ABSTRACT THIS FURTHER?

The materials and methods used to create public interface BLTSandwichFactory { a sandwich are what vary per region. public Bread createBread(); public Ingredients[] createIngredients(); public Cheese[] createCheese(); We can use the factory concept to abstract public Condiments[] createCondiments(); sandwich creation. public Toppings[] createToppings(); } We first define the factory interface. CAN WE ABSTRACT THIS FURTHER?

The materials and methods used to create public interface BLTSandwichFactory { a sandwich are what vary per region. public Bread createBread(); public Ingredients[] createIngredients(); public Cheese[] createCheese(); We can use the factory concept to abstract public Condiments[] createCondiments(); sandwich creation. public Toppings[] createToppings(); } We first define the factory interface and then implement this interface using public class BOSBLTSandwichFactory implements BLTSandwichFactory factories that produce a variant of the public class CLTBLTSandwichFactory implements } public class SVBLTSandwichFactory implements product . } public class SEABLTSandwichFactory implements } } IMPLEMENTING THE BOSTON FACTORY public class BOSBLTSandwichFactory implements BLTSandwichFactory {

public Bread createBread() { return new WonderBread(); } public Ingredients[] createIngredients() { Ingredients[] ingredientList = new Ingredient[3]; ingredientList[1] = new Ingredient("armour"); ingredientList[2] = new Ingredient("ice berg"); ingredientList[3] = new Ingredient("beefsteak"); return ingredientList; } public Cheese[] createCheese() { return null; // no one in Boston puts cheese on a BLT } public Condiments[] createCondiments() { Condiments[] condimentList = new Condiments[1]; condimentList[1] = new Condiment("mayo"); } SOMETHING SIMILAR HAPPENS FOR CA public class SVBLTSandwichFactory implements BLTSandwichFactory {

public Bread createBread() { return new WholeGrain(); } public Ingredients[] createIngredients() { Ingredients[] ingredientList = new Ingredient[3]; ingredientList[1] = new Ingredient("tofu-based bacon"); ingredientList[2] = new Ingredient("romaine"); ingredientList[3] = new Ingredient("abracazebra"); return ingredientList; }

public Condiments[] createCondiments() { Condiments[] condimentList = new Condiments[1]; condimentList[1] = new Condiment("garlic aoli"); } } WE NEED TO ABSTRACT THE SANDWICH public abstract class Sandwich {

String name; String bread; Ingredients ingredients[]; Cheese cheese[]; Condiments condiments[]; Toppings[] toppings;

abstract void prepIngredients(); // this is where the factory will come in

abstract void cook() { // default behavior is to do nothing because it's a cold sandwich }

wrapIt() { // wrap sandwich in butcher paper } } AND REDEFINE THE BLT public class BLT extends Sandwich {

BLTSandwichFactory myFactory;

public BLT(BLTSandwichFactory myFactory) { this.myFactory = myFactory; }

void prepIngredients() { bread = myFactory.createBread(); ingredients = myFactory.createIngredients(); condiments = myFactory.createCondiments(); } } AND REDEFINE THE SANDWICH SHOPPE (THE CLIENT) public class BOSSandwichShop extends SandwichShop {

Sandwich createSandwich(String sandwichType) {

Sandwich sandwich = null;

if (sandwichType.equals("BLT")) {

BLTSandwichFactory myFactory = new BOSBLTSandwichFactory(); Sandwich = new BLT(myFactory);

} else if (sandwichType.equals("VeggieBurger") { // and so on

return sandwich;

} } ABSTRACT FACTORY

Provides an interface for creating families of related or dependent objects without specifying their concrete classes

Use AbstractFactory when 1. A system should be independent of how its products are created, composed, represented 2. A system should be configured with one or multiple families of products 3. A family of related product objects is designed to be used together and you need to enforce this constraint 4. You want to provide a class library of products, and you want to reveal just their interfaces, not their implementations

In other words, the Abstract Factory uses association to decide which object to instantiate.

39 ABSTRACT FACTORY: PARTICIPANTS

AbstractFactory declares interface for operations that create abstract products

ConcreteFactory implements operations to create concrete products

AbstractProduct declares an interface for a type of product object

ConcreteProduct defines the product object created by concrete factory implements the AbstractProduct interface

Client Uses only interfaces of AbstractFactory/AbstractProduct

40 PROTOTYPE

A prototypical instance defines the kinds of objects to create and you create new objects by copying this prototype.

Use prototype when A system should be independent of how its products are created, composed, or represented

One of the following holds: 1. The classes to instantiate are specified at run-time. 2. To avoid building a class hierarchy of factories that parallels the class hierarchy of products. 3. Instances of a class have only a few different combinations of state. 4. It is expensive to instantiate an object versus make a copy.

41 PROTOTYPE: PARTICIPANTS

Prototype declares an interface for cloning itself

ConcretePrototype implements an interface for cloning itself

Client creates a new object by asking a prototype to clone itself

42 PROTOTYPE: BENEFITS

Similar to abstract factory and builder Hides concrete product classes from the client Let client work with application-specific classes without modification

Additional benefits You can add products at run-time Especially important for applications that rely on dynamic loading to add classes after start of execution Reduced need for sub-classing

43 PROTOTYPE EXAMPLE public class Employees implements Cloneable { private List empList; public List getEmployeeList() { return employeeList; public Employees() { } employeeList = new ArrayList(); } @Override public Object clone() throws CloneNotSupportedException { public Employees(List employeeList) { List temp = new ArrayList(); this.employeeList = employeeList; for (String s : this.getEmpList()) { } temp.add(s); } public void fetchFromDatabase() { return new Employees(temp); // read all employees from database and put into the list } } } PROTOTYPE EXAMPLE public class Employees implements Cloneable { private List empList; public List getEmployeeList() { return employeeList; public Employees() { A client} can get copies of the data rather going to the database employeeList = new ArrayList(); each time. } @Override // set-publicup: one Object time pullclone() from BD Employees employees =throws new Employees(); CloneNotSupportedException { public Employees(List employeeList) { employees.loadData(); List temp = new ArrayList(); this.employeeList = employeeList; for (String s : this.getEmpList()) { } // Later, use thetemp.add clone(s); method to get the Employee // object for }the cost of a copy public void fetchFromDatabase() { Employees aCopy = (Employees) employees.clone(); return new Employees(temp); // read all employees from database and put into the list } } } SINGLETON

Singleton ensures

A class has only one instance.

This instance is globally accessible.

Considerations:

Use singleton for classes that should have only one instance (e.g., scheduler)

Lets you avoid parameter-passing of the singleton object

46 SINGLETON: PARTICIPANTS

Singleton

Defines an operation that lets clients access its unique instance. This operation is static.

May be responsible for creating its own unique instance

47 SIMPLE CODE EXAMPLE

public final class Singleton { // Static variables have a single value for all instances of a class private static Singleton instance = null;

// constructor is PRIVATE so it cannot be called from outside the class private Singleton() {}

public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } SINGLETON: CONSIDERATIONS

Unfortunately, there is no good solution in Java that allows singletons to be sub-classed Make the constructor protected instead of private But you cannot override the static instance() method

Possible solutions: Global variable approach: let instance() method read information from an environment variable what kind of sandwich it should build. Requires rewriting the instance() method every time a subclass is added. Introspection approach: instance() uses a parameter with the name of the factory (as a string, for example), and uses reflection to create an object.

Other languages have built-in support for singletons E.g., Scala lets you declare an object

49 CREATIONAL PATTERNS: SUMMARY

Creational patterns make more flexible and extensible by instantiating classes in certain stylized ways

1. AbstractFactory 2. Builder 3. FactoryMethod 4. Prototype 5. Singleton

50