IntroductionDon’t reinvent the wheelWhat you should knowDesign PatternsObject-oriented design experienceWhat are design patterns?Design Principles vs Design PatternsStrategy PatternProblemProblem 2Strategy PatternAdapter PatternUnderstanding the adapter patternThe Adapter pattern definedUsing the Adapter patternChallengeSolutionObserver PatternUnderstanding the Observer patternThe Observer pattern definedUsing the Observer patternThe Observer pattern and loose couplingChallengeSolutionThe Decorator PatternProblemUnderstanding the Decorate patternUsing the Decorator patternChallengeSolutionThe Iterator PatternEncapsulate iterationUnderstanding the Iterator patternUsing the Iterator patternThe iterator pattern as a language featureThe Factory PatternsSimple Factory PatternThe Factory Method patternChallengeSolutionReferences
Introduction
Don’t reinvent the wheel
- Design Patterns are general solutions to common object-oriented problems
- Design Patterns help to create software more flexible, maintainable, and resilient to change or even just easier to communicate with your teammates.
- Central to the Design Patterns are the design principles in the previous post
What you should know
- UML
- Java
Design Patterns
Object-oriented design experience
- Object-oriented design experiences don’t come easy. It can take a lot of trial and error to come up with designs that are flexible and extensible
- Your design can quickly go off the rail if you only have the core OOP concepts (Encapsulate, Abstraction, Inheritance, Polymorphism) in your toolbox
- Design Pattern = Design Insight + Object-Oriented Design Experience
What are design patterns?
- Design Patterns are object-oriented design experiences
- Design Patterns are solutions for common problems
- Design Patterns are not algorithms
- Design Patterns are not coding
Creational Patterns | Structural Patterns | Behavioral Patterns |
Abstract Factory | Adapter | Chain of Responsibility |
Builder | Bridge | Command |
Factory Method | Composite | Interpreter |
Prototype | Decorator | Iterator |
Singleton | Facade | Mediator |
ㅤ | Flyweight | Memento |
ㅤ | Proxy | Observer |
ㅤ | ㅤ | State |
ㅤ | ㅤ | Strategy |
ㅤ | ㅤ | Template |
ㅤ | ㅤ | Visitor |
- There are 23 original design patterns in the Gang of Four catalogs.
- They are grouped into 3 categories: Creational, Structural, Behavioral
- We will focus on 6 common design patterns
- Factory Method
- Adapter
- Decorator
- Iterator
- Observer
- Strategy
Design Principles vs Design Patterns
Design Principles vs. Design Patterns
Design Principles
- Encapsulate what varies
- Favor composition over inheritance
- Loop coupling
- Program to interface
- Single Responsibility
- Open Closed
- Liskov substitution
- Interface segregation
- Dependency Inversion
Strategy Pattern
Problem
- Assume we need to model a Duck object which it can fly, quack, swim, and display
- Duck can be a mallard duck or a red head duck
- To design this system, we use inheritance here
- And Everything works fine
- But we have another request that we need to support a rubber duck as well
- Think about the request, we found that just adding another subclass RubberDuck and extending Duck.
- But a rubber duck can not fly so we need to override fly method
- Also, a rubber duck will squake instead of quacking. So, we need to override quack method as well.
- How about if we need to support a decoy duck?
- We found that a decoy duck can not fly. So, we need to override fly method
- It can not quack, so we need to override quack method
- How about if we have to hundred of ducks like that?
- It seems we have a problem with this design, right?
Problems with this design using inheritance
- Code duplicated, we need to override a lot of methods from the base class
- Explode subclasses
How about if we use interfaces
- We found that there are some ducks that could not fly or quack
- We can separate fly() and quack() into interfaces
- MalladDuck and RedheadDuck will implement both interfaces
- RubberDuck only needs to implement Quackable
- This design is better but code is not re-used
- How about if we have hundreds of Duck classes and they are able fly and quack, we have to implement all them.
- How about using has-a instead of is-a and program to interface?
- Now, Duck HAS-A a FlyBehavior and a FquackBehavior and It depends on an abstraction (program to the interface)
- We can define all kinds of FlyBehavior and QuackBehavior
- Each Duck can set a specific behavior at run-time
- Now, Duck can performFly and performQuack by delegating it to FlyBehavior and QuackBehavior
Problem 2
- Currently, we support to share a photo via text message and via email
- We have another to share photo via social network
- Create an interface ShareBehavior
- Create TextSharing class, EmailSharing class, and SocialNetworkSharing class. All those 3 classes will implement ShareBehavior
- Create HAS-A relationship between CameraApp and ShareBehavior interface.
Strategy Pattern
This pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. This lets the algorithm vary independently from clients that use it
Adapter Pattern
Understanding the adapter pattern
- How can we plug an American-style electrical cord into a European-style outlet?
- It seems we can’t because they have different interfaces
- To make it work we have adapter to adapt The European style outlet into the American-style plug
In computer science
- Assume that Your System working well with Vendor Class
- At some points, you want to use a different Vendor Class. Maybe the new Vendor Class is cheaper or better.
- The problem is that the new Vendor class has a different interface with your existing system.
- So, how to make it work?
- We use an adapter
The Adapter pattern defined
The pattern converts the interface of a class into another interface that clients expect. It allows classes to work together that could not otherwise because of incompatible interfaces
How it work?
- The client makes a request as usual
- The adapter will translate that request into a request which Adaptee can understand.
- When the Adaptee responds to the result, it will translate it back to the client.
Using the Adapter pattern
- Assume we have DuckSimulator which is working with Duck interface with 2 methods quack() and fly()
- How do we make it work with Turkey interface with 2 methods gobble() and fly()
Adapters Use Composition
- The client is composed with the class with the target interface
- The adapter is composed of the adaptee
- The adapter delegates calls to the adaptee and return any needed value
- The client and the adaptee don’t know there’s an adapter in between
Challenge
public interface Drone { public void beep(); public void sprin_rotors(); public void take_off(); } public class SuperDrone implements Drone { public void beep() { System.out.println("Beep beep beep"); } public void spin_rotors() { System.out.println("Rotors are spinning"); } public void take_off() { System.out.println("Taking off"); } }
- Create adapter to adapt a Drone to a Duck
Solution
//DroneAdapter class public class DroneAdapter implements Duck{ private Drone drone; public DroneAdapter(Drone drone){ this.drone = drone; } @Override public void fly() { drone.spin_rotor(); drone.take_off(); } @Override public void quack() { drone.beep(); } } //DuckSimulator class Drone drone = new SupperDrone(); DroneAdapter droneAdapter = new DroneAdapter(drone); testDuck(droneAdapter);
Observer Pattern
Understanding the Observer pattern
- A publisher who publishes magazines
- Subscribers who subscribe with the publisher to receive magazines if there are any magazines published
- A subscriber can unsubscribe at any time to stop receiving the published magazines
- Assume we have a Publisher object
- Any objects can subscribe to be Subscribers
- Obviously, there’ll be objects that aren’t subscribers
- And any subscribers can send a request to the publisher to be non-subscribers
- The publisher typically holds some data of interest. That could be a stock quote or weather temperature,…
- And when the data changes, all subscribers are notified
The Observer pattern defined
This pattern defined a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically.
Using the Observer pattern
//SimpleObserver public class SimpleObserver implements Observer{ private Subject subject; private int value; public SimpleObserver(Subject subject){ this.subject = subject; System.out.println("Register observer " + this.toString()); this.subject.registerObserver(this); } @Override public void update(int value) { this.value = value; display(); } public void display(){ System.out.println("The updated value of " + this.toString() + " is " + value); } } //SimpleSubject public class SimpleSubject implements Subject{ private ArrayList<Observer> observers; private int value; public SimpleSubject(){ this.observers = new ArrayList<>(); } public void setValue(int value){ this.value = value; notifyObservers(); } @Override public void notifyObservers() { for (Observer observer : observers) { observer.update(value); } } @Override public void registerObserver(Observer observer) { observers.add(observer); } @Override public void removeObserver(Observer observer) { if(observers.indexOf(observer) != -1){ observers.remove(observer); } } }
The Observer pattern and loose coupling
Loose Coupling
- Subjects and observers are loosely coupled
- They interact but have little knowledge of each other
- The subject knows only that the observer implements a specific interface
- The subject doesn’t need to know the concrete class of the observers
- The subject relies on a list of observers
- Observers can be added, removed, or replaced at any time
Challenge
- The weather station has a set of sensors that are used to measure temperature, wind speed, and pressure.
- The user interface, Logger, and AlertSystem are all interested in the data and want to be notified when the weather station gets new data from the sensors.
Solution
The Decorator Pattern
Problem
- Assume we need to build an order system for a coffee shop that will serve and take payments for beverages
- The shop has 4 main types of coffee on the menu. Each of which has a description and a cost
- And for each beverage, you can add a number of condiments like soy, milk, whip, or mocha.
- Each of these condiments has a small cost which needs to be added to the cost of the coffee.
Design
- We create an abstract Beverage class
- And 4 main types of coffee HouseBlend, DarkRoast, Decaf, Espresso will extend Beverage class and override cost method
- This design looks nice so far
- How about Condiments? We have a lot of variants of Condiments
- One way to do that, we can use subclasses to add Condiments
- But there are a lot of Condiments and there will be a lot of sub-classes for these kinds of combinations
- This design is simpler than the one using sub-classes
- But what about if the price of Condiments changes?
- Or if we requested to add more Condiments?
- It seems we need to have to open the class for modification and we could not support double condiments
- This design is not flexible and maintainable
Extending behavior with composition
- How about if we add some attributes in the superclass to track Condiments?
//main HouseBlend drink = new HouseBlend() drink.setSoy(); drink.setWhip(); float cost = drink.cost() //HouseBlend class public float cost() { float cost = super.cost(); if (hasMilk()) { cost += .10; } if (hasSoy()) { cost += .20; } if (hasWhip()) { cost += .10; } if (hasMocha()) { cost += .15; } return cost; }
- First, we create a DarkRoast object
- Then we create Mocha object, and wrap DarkRoast object inside it
- And we can do the same for Whip
- So, the cost of DarkRoast with Mocha and with Whip will be 0.99 + 0.20 + 0.10
- This design is very flexible. We can add a number of condiments we want for a beverage
Understanding the Decorate pattern
This pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
The coffee shop design with Decorator pattern
Using the Decorator pattern
Starbuzz Coffee
//Beverage abstract class package src.patterns.decorator; public abstract class Beverage { protected String description; public abstract float cost(); public String getDescription(){ return description; } } //HouseBlend class package src.patterns.decorator; public class HouseBlend extends Beverage{ public HouseBlend(){ this.description = "HouseBlend coffee"; } @Override public float cost() { return 1.5f; } } //CondimentDecorator class package src.patterns.decorator; public abstract class CondimentDecorator extends Beverage{ public abstract String getDescription(); } //Milk package src.patterns.decorator; public class Milk extends CondimentDecorator{ private Beverage beverage; public Milk(Beverage beverage){ this.beverage = beverage; this.description = this.beverage.getDescription() + " with Milk"; } @Override public float cost() { return this.beverage.cost() + 0.10f; } @Override public String getDescription() { return description; } } //Starbuzz Coffee package src.patterns.decorator; public class StarbuzzCoffee { public static void main(String[] args) { Beverage beverage = new HouseBlend(); beverage = new Milk(beverage); beverage = new Mocha(beverage); beverage = new Soy(beverage); System.out.println("Your order:" + beverage.getDescription()); System.out.println("Your bill:" + beverage.cost()); } }
Challenge
- Design a system using Decorator pattern for the pizza store
Solution
The Iterator Pattern
Encapsulate iteration
Array of objects
ArrayList in Java
String[] menuItems = new String[MAX_ITEMS]; menuItem[0] = "Regular Pancake Breakfast"; menuItem[1] = "Blueberry Pancakes"; public void print(items) { for(int i; i < items.length; i++) { String item = items[i]; System.out.println(item); } }
ArrayList<String> menuItems = new ArrayList<String>(); menuItems.add("Regular Pancake Breakfast"); menuItems.add("Blueberry Pancakes"); public void print(items) { for(int i; i < items.size; i++) { String item = items.get(i); System.out.println(item); } }
- The way we iterate through array and ArrayList is different.
- Do we have a way to iterate through objects so that we don’t need to change the code for each kind of collection of objects?
- That is the iterator pattern
Understanding the Iterator pattern
This pattern provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation
Aggregate Objects
- Array
- Java Collection classes, like ArrayList
- Lists
- Maps
- Sets
- Dictionaries
To Iterate over an Aggregate Object
- Ask the object for its iterator
- Use the iterator to iterate through the items in the aggregate
- Iteration code now works with any kind of aggregate object
Using the Iterator pattern
The iterator pattern as a language feature
Built-In Iterators
- Java’s enhanced for statement
- Python’s for/in statement
- Javascript’s for/of statement
for el in [9, 8, 7, 6, 5]: print(el)
for (Animal a: animals) { a.describe(); a.makeSound(); }
for (let value of aggregate) { console.log(value); }
The Factory Patterns
Simple Factory Pattern
Pizza Store
Simple Factory Pattern
The Factory Method pattern
The Factory Method pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.
package src.patterns.factory_method; public class FactoryMethodApp { public static void main(String[] args) { PizzaStore pzStore; Pizza pizza; //Chicago style pzStore = new ChicagoStylePizzaStore(); pizza = pzStore.orderPizza("Cheese"); System.out.println("Delivery pizza " + pizza.getName()); //NY Style pzStore = new NYStylePizzaStore(); pizza = pzStore.orderPizza("Veggie"); System.out.println("Delivery pizza " + pizza.getName()); } } //ChicagoStylePizzaStore class package src.patterns.factory_method; public class ChicagoStylePizzaStore extends PizzaStore{ @Override public Pizza createPizza(String name) { Pizza pizza = null; switch(name){ case "Cheese": pizza = new ChicagoStyleCheesePizza(); break; case "Clam": pizza = new ChicagoStyleClamPizza(); break; case "Veggie": pizza = new ChicagoStyleVeggiePizza(); break; case "Pepperoni": pizza = new ChicagoStylePepperoniPizza(); break; default: break; } return pizza; } }
Challenge
- Write code for Zone, its subclasses and the ZoneFactory class that will create the correct zone based on the zone id that you pass to its createZone() method
Solution
package src.patterns.simple_factory; public class ZoneFactory { public Zone createZone(String zoneId){ Zone zone; switch(zoneId){ case "Central": zone = new ZoneUSCentral(); break; case "Mountain": zone = new ZoneUSMountain(); break; case "Pacific": zone = new ZoneUSPacific(); break; case "Eastern": zone = new ZoneUSEastern(); break; default: zone = null; break; } return zone; } } //ZoneUSPacific class package src.patterns.simple_factory; public class ZoneUSPacific extends Zone { public ZoneUSPacific() { this.displayName = "Pacific US"; this.offset = -7; } } //Calendar package src.patterns.simple_factory; public abstract class Calendar { protected Zone zone; public void print() { System.out.println("Display name: " + zone.displayName + ", offset: " + zone.offset); } public abstract void createCalendar(); }