OBSERVER PATTERN
Step 1 — Observer Interface
java
interface Observer {
void update(double price); // common contract for all observers
}
Step 2 — Subject Interface
java
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
Step 3 — Concrete Subject
java
class Stock implements Subject {
private List<Observer> observers = new ArrayList<>();
private double price;
public void setPrice(double price) {
[Link] = price;
notifyObservers(); // just notify, don't care who's listening
}
// register / remove / notify implementations...
}
Step 4 — Concrete Observers
java
class MobileDisplay implements Observer {
public void update(double price) {
[Link]("Mobile: " + price);
}
}
class WebDisplay implements Observer {
public void update(double price) {
[Link]("Web: " + price);
}
}
Now adding a new display = just create a new class + register it. Zero changes to Stock.
Simple Coding Example (Java)
Let’s simulate a weather station 🌦️
1. Observer Interface
interface Observer {
void update(float temperature);
}
2. Subject Interface
interface Subject {
void addObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
3. Concrete Subject (WeatherStation)
import [Link].*;
class WeatherStation implements Subject {
private List<Observer> observers = new ArrayList<>();
private float temperature;
public void setTemperature(float temp) {
[Link] = temp;
notifyObservers(); // notify when state changes
}
public void addObserver(Observer o) {
[Link](o);
}
public void removeObserver(Observer o) {
[Link](o);
}
public void notifyObservers() {
for (Observer o : observers) {
[Link](temperature);
}
}
}
4. Concrete Observer (Display Device)
class PhoneDisplay implements Observer {
public void update(float temperature) {
[Link]("Phone Display: Temperature updated to " + temperature);
}
}
5. Main Class
public class Main {
public static void main(String[] args) {
WeatherStation station = new WeatherStation();
Observer phone = new PhoneDisplay();
[Link](phone);
[Link](30.5f);
[Link](35.0f);
}
}
🧾 Output
Phone Display: Temperature updated to 30.5
Phone Display: Temperature updated to 35.0
FACTORY PATTERN
Good Design (With Factory Method Pattern)
Step 1 — Product Interface
java
public interface Report {
void generate(); // common contract for all reports
}
Step 2 — Concrete Products
java
public class PDFReport implements Report {
public void generate() { [Link]("Generating PDF"); }
}
public class WordReport implements Report {
public void generate() { [Link]("Generating Word"); }
}
Step 3 — Abstract Factory (Creator)
java
public abstract class ReportFactory {
public abstract Report createReport(); // subclasses decide what to create
}
Step 4 — Concrete Factories
java
public class PDFReportFactory extends ReportFactory {
public Report createReport() { return new PDFReport(); }
}
public class WordReportFactory extends ReportFactory {
public Report createReport() { return new WordReport(); }
}
Step 5 — Client Code
java
ReportFactory factory = new PDFReportFactory();
Report report = [Link]();
[Link](); // client never touches concrete classes
Step 1: Create Factory
ShapeFactory factory = new ShapeFactory();
👉 Factory object created
🔹 Step 2: Request Object
Shape s1 = [Link]("circle");
👉 Instead of:
new Circle()
➡️
We ask factory:
“Give me a circle”
🔹 Step 3: Factory Decides
Inside factory:
if ([Link]("circle")) {
return new Circle();
}
👉 Factory creates object and returns it
🔹 Step 4: Use Object
[Link]();
👉 Calls method without caring about actual class
Shape Interface
interface Shape {
void draw();
}
👉 Common method for all shapes
2. Concrete Classes
class Circle implements Shape {
public void draw() {
[Link]("Drawing Circle");
}
}
class Rectangle implements Shape {
public void draw() {
[Link]("Drawing Rectangle");
}
}
👉 Different implementations of Shape
3. Factory Class (Main Logic 🔥)
class ShapeFactory {
public Shape getShape(String type) {
if (type == null) {
return null;
}
if ([Link]("circle")) {
return new Circle();
}
else if ([Link]("rectangle")) {
return new Rectangle();
}
return null;
}
}
👉 This is the core of Factory Pattern
● Takes input (circle, rectangle)
● Decides which object to create
● Returns it
💡 User doesn’t use new Circle() directly!
4. Main Class (Execution)
public class Main {
public static void main(String[] args) {
ShapeFactory factory = new ShapeFactory();
Shape s1 = [Link]("circle");
[Link]();
Shape s2 = [Link]("rectangle");
[Link]();
}
}
🧾 Output
Drawing Circle
Drawing Rectangle
SINGLETON PATTERN
The Singleton Class
java
public class AppConfig {
private static AppConfig instance; // step 1: private static instance
private AppConfig() { // step 2: private constructor
[Link]("Loading configuration...");
}
public static AppConfig getInstance() { // step 3: controlled access
if (instance == null) {
instance = new AppConfig(); // only created ONCE
}
return instance;
}
public String getDatabaseURL() {
return "jdbc:mysql://localhost/appdb";
}
}
Client Code
java
public class ModuleA {
AppConfig config = [Link](); // same object
}
✅
public class ModuleB {
AppConfig config = [Link](); // same object
}
Both modules now share the exact same instance — loaded once, consistent
everywhere.
⚠️ Thread Safety Issue
In a multi-threaded environment, two threads could hit getInstance() at the
same time when instance is still null — creating two instances accidentally.
Fix — Synchronized Method
java
public static synchronized AppConfig getInstance() {
if (instance == null) {
instance = new AppConfig();
}
return instance;
}
The synchronized keyword ensures only one thread at a time can run this
method.
Trade-off: Slight performance overhead, but guarantees safety.
BUILDER PATTERN
Good Design (With Builder Pattern)
Step 1 — Main Class with Private Constructor
java
public class UserProfile {
private String username, email, phone, address; // fields
private UserProfile(Builder builder) { // only Builder can call this
[Link] = [Link];
[Link] = [Link];
[Link] = [Link];
[Link] = [Link];
}
}
Step 2 — Builder Class (lives inside UserProfile)
java
public static class Builder {
private String username, email; // mandatory
private String phone, address; // optional
public Builder(String username, String email) { // mandatory fields here
[Link] = username;
[Link] = email;
}
public Builder setPhone(String phone) {
[Link] = phone;
return this; // returns Builder for method chaining
}
public Builder setAddress(String address) {
[Link] = address;
return this;
}
public UserProfile build() {
return new UserProfile(this); // final step — create the object
}
}
Step 3 — Client Code
java
UserProfile profile = new [Link]("Ali", "ali@[Link]")
.setPhone("03001234567")
.setAddress("Karachi")
✅
.setDateOfBirth("01-01-2000")
.build(); // clean, readable, step-by-step
PROTOTYPE PATTERN
ood Design (With Prototype Pattern)
Step 1 — Prototype Interface
java
public interface Prototype {
Prototype clone(); // every prototype must know how to copy itself
}
Step 2 — Concrete Prototype
java
public class Report implements Prototype {
private String title, headerStyle, chartType, dataFormat;
public Report(String headerStyle, String chartType, String dataFormat) {
[Link]("Heavy initialization... (runs ONCE)");
[Link] = headerStyle;
[Link] = chartType;
[Link] = dataFormat;
}
@Override
public Prototype clone() {
✅
return new Report([Link], [Link], [Link]);
// copies config — NO heavy init repeated
}
public void setTitle(String title) { [Link] = title; }
}
Step 3 — Client Code
java
Report prototype = new Report("Modern", "Bar Chart", "PDF"); // init ONCE
Report report1 = (Report) [Link](); // fast copy ✅
[Link]("Sales Report");
Report report2 = (Report) [Link](); // fast copy ✅
[Link]("Finance Report");
Heavy initialization happens only once — all clones are instant.
⚠️ Shallow Copy vs Deep Copy
This is a critical concept in the Prototype Pattern.
Shallow Copy
java
// Both original and clone share the SAME address object
❌
Employee clone = (Employee) [Link]();
// if [Link] changes → [Link] ALSO changes!
Deep Copy
java
// Clone gets its OWN independent copy of nested objects
✅
Employee cloned = (Employee) [Link]();
✅
[Link] = [Link](); // separate copy
// changes to clone don't affect original
Prototype Registry (Advanced)
For systems with many prototype types, store them in a central registry:
java
public class PrototypeRegistry {
private static Map<String, Prototype> registry = new HashMap<>();
public static void addPrototype(String key, Prototype p) {
[Link](key, p);
}
public static Prototype getPrototype(String key) {
return [Link](key).clone(); // always returns a fresh clone
}
}
// Usage
[Link]("Financial", new Report("Classic", "Pie",
✅
"Excel"));
Report r = (Report) [Link]("Financial"); // instant
Think of it as a template library — store once, clone anytime.
STRATEGY PATTERN
Bad Design (Before Pattern)
java
public class PaymentProcessor {
public void pay(String type, double amount) {
if ([Link]("CreditCard")) {
[Link]("Processing credit card...");
} else if ([Link]("PayPal")) {
[Link]("Processing PayPal...");
} else if ([Link]("BankTransfer")) {
[Link]("Processing bank transfer...");
😬
}
// add new method? modify this class AGAIN
}
}
Problems:
● Adding a new payment method = modifying this class every time
● Violates Open-Closed Principle
● Hard to read, hard to test
● All behaviors tightly coupled in one place
● Grows uncontrollably as system evolves
✅ Good Design (With Strategy Pattern)
Step 1 — Strategy Interface
java
public interface PaymentStrategy {
void pay(double amount); // common contract for all payment methods
}
Step 2 — Concrete Strategies (one class per behavior)
java
public class CreditCardPayment implements PaymentStrategy {
public void pay(double amount) {
[Link]("Paid " + amount + " using Credit Card");
}
}
public class PayPalPayment implements PaymentStrategy {
public void pay(double amount) {
[Link]("Paid " + amount + " using PayPal");
}
}
public class BankTransferPayment implements PaymentStrategy {
public void pay(double amount) {
[Link]("Paid " + amount + " using Bank Transfer");
}
}
Step 3 — Context Class
java
public class PaymentProcessor {
private PaymentStrategy strategy; // holds whichever strategy is active
✅
public void setStrategy(PaymentStrategy strategy) {
[Link] = strategy; // swap strategy at runtime
}
public void processPayment(double amount) {
[Link](amount); // delegates to the active strategy
}
}
Step 4 — Client Code
java
PaymentProcessor processor = new PaymentProcessor();
[Link](new CreditCardPayment());
[Link](5000); // Paid 5000 using Credit Card
[Link](new PayPalPayment());
[Link](3000); // Paid 3000 using PayPal
Adding a new payment method? Just create a new class — zero changes to
existing code.
DECORATOR PATTERN
❌ Bad Design (Before Pattern)
SimpleCoffee
CoffeeWithMilk
CoffeeWithSugar
CoffeeWithMilkAndSugar
CoffeeWithChocolate
CoffeeWithChocolateAndMilk
😬
CoffeeWithChocolateAndMilkAndSugar
...
Problems:
● Class count explodes with every new feature
● Every new combination = a brand new class
● Massive code duplication
● Impossible to maintain or scale
✅ Good Design (With Decorator Pattern)
Step 1 — Component Interface
java
interface Coffee {
double getCost();
String getDescription();
}
Step 2 — Concrete Component (the base object)
java
class SimpleCoffee implements Coffee {
public double getCost() { return 100; }
public String getDescription() { return "Simple Coffee"; }
}
Step 3 — Abstract Decorator
java
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee; // holds reference to wrapped object
public CoffeeDecorator(Coffee coffee) {
[Link] = coffee; // wraps whatever Coffee is passed in
}
}
Step 4 — Concrete Decorators
java
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) { super(coffee); }
public double getCost() { return [Link]() + 30; }
public String getDescription() { return [Link]() + ", Milk"; }
}
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) { super(coffee); }
public double getCost() { return [Link]() + 20; }
public String getDescription() { return [Link]() + ", Sugar"; }
}
Step 5 — Client Code
java
Coffee coffee = new SimpleCoffee(); // cost: 100
coffee = new MilkDecorator(coffee); // cost: 130
coffee = new SugarDecorator(coffee); // cost: 150
[Link]([Link]()); // Simple Coffee, Milk, Sugar
[Link]([Link]()); // 150.0
🔍 How the Wrapping Works
Each decorator calls the inner object's method first, then adds its own on top:
[Link]()
→ calls [Link]()
→ calls [Link]() → 100
✅
→ adds 30 (milk) → 130
→ adds 20 (sugar) → 150
Think of it like peeling an onion — each layer adds its piece, delegating the rest
inward.