Page 1 of 18
Design patterns vary in their granularity, level of abstraction, and how they relate to one another.
Key Points:
- Some patterns are often used together.
- Some patterns are alternatives.
- Patterns are organized as (by purpose):
- Creational: Concern the process of object creation.
- Structural: Deals with the composition of classes or objects.
- Behavioral: Focuses on object interaction and distribution responsibility.
OO Basics:
- Abstraction
- Encapsulation
- Polymorphism
- Inheritance
Strategy Pattern (B Type)
Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
Page 2 of 18
Key Concept:
- A class's behavior or its algorithm can be changed at runtime.
Diagram:
classDiagram
class Duck {
- Pull out what varies
}
class DuckBehavior {
- FlyingBehavior
- QuackingBehavior
}
Principle:
- Program to an interface (abstract class/interface), not an implementation.
public abstract class Duck {
QuackBehavior quackBehavior;
public void performQuack() {
quackBehavior.quack();
}
}
public class RubberDuck extends Duck {
public RubberDuck() {
quackBehavior = new Mute();
}
}
Page 3 of 18
Notes:
- This allows changing behaviors at runtime.
- Avoid initializing behaviors in constructors as it can be a slight disadvantage.
Favor Composition Over Inheritance:
- (HAS-A) principle.
Observer Pattern
Key Concept:
- Publishers + Subscribers = Observer Pattern
- Subject → Observer
Definition:
The observer pattern defines a 1-M relationship between a set of objects. When the state of one object changes, all its dependents are notified.
Diagram:
classDiagram
class Subject {
<<interface>>
+ registerObs()
+ removeObs()
+ notifyObs()
}
class Observer {
<<interface>>
+ update()
}
Characteristics:
- Suited for loosely coupled designs between objects that interact.
- Observers can pull full data from Observable. Pull is considered more "correct."
Page 4 of 18
Pull Method Implementation:
public class WeatherData implements Subject {
private ArrayList observers;
private float temp;
public WeatherData() {
observers = new ArrayList<>();
}
public void registerObserver(Observer o) {
observers.add(o);
}
public void removeObserver(Observer o) observers.remove(o);
}
observer.update(arg);
}
}
}
notifyObservers(timeNow);
}
}
Page 5 of 18
ConditionDisplay Implementation
public class ConditionDisplay implements Observer {
private Subject weatherData;
public ConditionDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
public void update(Object obj) {
if (weatherData instanceof WeatherData) {
X = (WeatherData) weatherData; ~~weatherData~~ → corrected variable
System.out.println(X.getTemperature());
}
}
}
Decorator Pattern
Principles:
- Classes should be open for extension, but closed for modification.
Overview:
The decorator pattern attaches additional responsibilities to an object dynamically. It provides a flexible alternative to subclassing for extending functionality.
Page 6 of 18
Decorator Pattern UML Diagram
classDiagram
class Pizza {
<<Interface>>
+getDescription(): String
+getCost(): double
}
class PlainPizza {
+getDescription(): String
+getCost(): double
}
class ToppingDecorator {
<<Abstract>>
-Pizza pizzaType
+getDescription(): String
+getCost(): double
}
class Mozzarella {
+Mozzarella(Pizza pizza)
+getDescription(): String
+getCost(): double
}
class Mayo {
+Mayo(Pizza pizza)
+getDescription(): String
+getCost(): double
}
Pizza <|-- PlainPizza
Pizza <|-- ToppingDecorator
ToppingDecorator <|-- Mozzarella
ToppingDecorator <|-- Mayo
Notes:
- Problem: This pattern creates a large number of small classes that are difficult to understand.
- Often used with Abstract and Composite patterns.
Page 7 of 18
Factory Pattern
Principles:
- We want code to be closed for modification.
Implementation:
Let's create a simple factory to handle the details of object creation:
public class SimplePizzaFactory {
public Pizza createPizza(String type) {
Pizza pizza = null;
if (/* condition */) {
// Logic here
} else if (/* condition */) {
// Logic here
} else {
// Logic here
}
return pizza;
}
}
Notes:
- This simple factory isn't actually a design pattern; it's more of a programming idiom.
Page 8 of 18
Advanced Factory Method
The simple factory (factory method) is:
- Abstract so subclasses are confined to handle object creation.
- Returns a "Product" that is typically used within methods defined in the subclasses.
- Isolates the client.
Factory Method Pattern Description:
- Defines an interface for creating an object, but lets subclasses decide which class to instantiate.
- Factory Method lets a class defer instantiation to subclasses.
Core Principles:
- Depend upon abstractions; do not depend upon concrete classes.
- Provides a framework to work upon.
Example UML Diagram:
classDiagram
class Product {
<<Abstract>>
}
class Pizza {
+NYStyleCheese()
+ChicagoStyleCheese()
+NYStyleVeggie()
}
Product <|-- Pizza
Pizza <|-- NYStyleCheese
Pizza <|-- ChicagoStyleCheese
Pizza <|-- NYStyleVeggie
Page 9 of 18
Abstract Creator (Creator Classes)
Diagram: Abstract Creator
classDiagram
class PizzaStore {
createPizza()
orderPizza()
}
class NYPizzaStore {
createPizza()
}
class ChicagoPizzaStore {
createPizza()
}
PizzaStore <|-- NYPizzaStore
PizzaStore <|-- ChicagoPizzaStore
Code Snippet:
if (item.equals()) {
// code block
} else {
// alternate code block
}
Notes:
-
Concrete Creator:
- Responsible for creating one or more concrete products.
-
Guidelines for Avoiding Dependency Inversion Principle (not strict rules):
- i) No variable should hold a reference to a concrete class.
- ii) No class should derive from a concrete class.
- iii) No method should override an implemented method of any of its base classes.
Page 10 of 18
Abstract Factory Pattern
Definition:
- Provides an interface for creating families of related or dependent objects without specifying their concrete classes.
Diagram: Abstract Factory Pattern
classDiagram
class AbstractFactory {
createProductA()
createProductB()
}
class ProductA {
}
class ProductB {
}
class ConcreteFactory1 {
createA()
createB()
}
class ConcreteFactory2 {
createA()
createB()
}
class AbstractProductA {
}
class AbstractProductB {
}
ConcreteFactory1 <|-- AbstractFactory
ConcreteFactory2 <|-- AbstractFactory
AbstractProductA <|-- ProductA
AbstractProductB <|-- ProductB
Page 11 of 18
Factory Method - Pizza Ingredient Factory
Diagram: Pizza Ingredient Factory
classDiagram
class PizzaIngredientFactory {
createSauce()
createCheese()
}
class NYPizzaStore {
methods implemented with Factory Method
}
class ChicagoPizzaStore {
methods implemented with Factory Method
}
class Sauce {
}
class Cheese {
}
class ThinMarina {
}
class Tomato {
}
class SweetCheese {
}
class GratedCheese {
}
Sauce <|-- ThinMarina
Sauce <|-- Tomato
Cheese <|-- SweetCheese
Cheese <|-- GratedCheese
Notes:
- Product: A product that is produced by the factory method.
Page 12 of 18
Singleton Pattern
Definition:
- Used for ensuring and maintaining one (and only one) instance of a class.
- Commonly used in classes such as thread pools, caches, etc.
Code Snippet: Singleton Implementation
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Page 13 of 18
Approach #2:
- Instantiate private variable as
staticand create theuniqueInstancethere. - There is still a little overhead.
Approach #3:
- Use double-checking:
- Check if the instance is created.
- If not, then synchronize; else, continue.
Page 14 of 18
Command Pattern
Definition:
The Command Pattern encapsulates a request as an object, thereby letting you parameterize other objects with different requests, queue or log requests, and support undoable operations.
Code Example:
// Command Interface
public interface Command {
public void execute();
}
// Light Command Class
public class LightOnCommand implements Command {
Light light;
public LightOnCommand(Light light) {
this.light = light;
}
public light.on();
}
}
Invoker Class:
public class SimpleRemoteControl {
Command slot;
public void setCommand(Command command) {
this.slot = command;
}
public void buttonPressed() {
slot.execute();
}
}
Diagram (Mermaid.js):
flowchart TD
Client -->|Sets Command| Invoker -->|Executes Command| Command
Command --> Receiver -->|Perform Action| Light
Receiver -->|Action Completed| Command
Example Use Case:
- Each slot gets a command.
- When the button is pressed, the
execute()method is called on the corresponding command. - In the
execute()method, actions are initiated on the receiver.
Page 15 of 18
Adapter & Facade Pattern
Adapter Pattern:
The Adapter Pattern converts the interface of a class into another interface the client expects. This pattern allows us to use a client with an incompatible interface by creating an adapter that does the conversion.
Diagram (Object Adapter):
classDiagram
Client --> Target : request()
Target --> Adapter : specificRequest()
Adapter : adapts request to target
Diagram (Class Adapter):
classDiagram
Client --> Target : request()
Target --> Adapter : adapts request
Facade Pattern:
The Facade Pattern hides all the complexity of one or more classes behind a clean, well-defined facade.
Diagram (Mermaid.js):
classDiagram
Facade --> SubsystemA : delegates
Facade --> SubsystemB : delegates
Facade --> SubsystemC : delegates
Page 17 of 18
Adapter Pattern
- Adapter
Director→ corrected: Converts one interface to another.
Decorator Pattern
- Decorator: Doesn't alter the interface, but adds responsibility.
Facade Pattern
- Facade: Makes an interface simpler.
Facade Pattern Explanation
- The Facade Pattern provides a unified interface to a set of interfaces in a subsystem. It defines a higher-level interface that makes the subsystem easier to use.
Additional Notes
- Kind of like Dependency Injection.
- Talk only to your friends.
Template Method Pattern
Explanation
-
The template method defines the steps of an algorithm and allows subclasses to provide implementation for one or more steps.
This pattern defines the skeleton of an algorithm in a method, deferring some steps to subclasses.
UML Diagram
classDiagram
class AbstractClass {
<<template method>>
}
class ConcreteClass
AbstractClass <|-- ConcreteClass
Key Points
- We can add hooks to our primitive operations and thus provide a way for a subclass to implement an optional part of an algorithm.
References & Related Topics
- Gang of Four (GoF) book on design patterns.
- Design Patterns: SOLID Principles