View on GitHub

SCRATCH PAD

No polish, just pulse

1. Seven Design Patterns from the Video

1.1 Singleton Pattern

Definition: Ensures that a class has only one instance and provides a global point of access to it.

Why Use It?

Implementation in Java:

public class Singleton {
    private static Singleton instance;

    private Singleton() {} // Private constructor prevents instantiation

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

Real-World Example:


1.2 Factory Pattern

Definition: A creational design pattern that provides an interface for creating objects but allows subclasses to alter the type of objects that will be created.

Why Use It?

Implementation in Java:

// Step 1: Define an interface
interface Shape {
    void draw();
}

// Step 2: Implement concrete classes
class Circle implements Shape {
    public void draw() {
        System.out.println("Drawing a Circle");
    }
}

class Rectangle implements Shape {
    public void draw() {
        System.out.println("Drawing a Rectangle");
    }
}

// Step 3: Create a Factory class
class ShapeFactory {
    public static Shape getShape(String shapeType) {
        if (shapeType.equalsIgnoreCase("CIRCLE")) {
            return new Circle();
        } else if (shapeType.equalsIgnoreCase("RECTANGLE")) {
            return new Rectangle();
        }
        return null;
    }
}

// Step 4: Use the Factory
public class FactoryPatternExample {
    public static void main(String[] args) {
        Shape shape1 = ShapeFactory.getShape("CIRCLE");
        shape1.draw();

        Shape shape2 = ShapeFactory.getShape("RECTANGLE");
        shape2.draw();
    }
}

Real-World Example:


1.3 Observer Pattern

Definition: A behavioral design pattern in which an object (the subject) maintains a list of dependents (observers) that need to be notified of any state changes.

Why Use It?

Implementation in Java:

import java.util.ArrayList;
import java.util.List;

// Step 1: Define the Observer Interface
interface Observer {
    void update(String message);
}

// Step 2: Create Concrete Observers
class User implements Observer {
    private String name;
    
    public User(String name) {
        this.name = name;
    }

    public void update(String message) {
        System.out.println(name + " received notification: " + message);
    }
}

// Step 3: Define the Subject (Publisher)
class NotificationService {
    private List<Observer> observers = new ArrayList<>();

    public void subscribe(Observer observer) {
        observers.add(observer);
    }

    public void unsubscribe(Observer observer) {
        observers.remove(observer);
    }

    public void notifyUsers(String message) {
        for (Observer observer : observers) {
            observer.update(message);
        }
    }
}

// Step 4: Use the Pattern
public class ObserverPatternExample {
    public static void main(String[] args) {
        NotificationService service = new NotificationService();

        Observer user1 = new User("Alice");
        Observer user2 = new User("Bob");

        service.subscribe(user1);
        service.subscribe(user2);

        service.notifyUsers("New article published!");
    }
}

Real-World Example:


1.4 Strategy Pattern

Definition: A behavioral design pattern that allows selecting an algorithm’s implementation at runtime.

Why Use It?

Implementation in Java:

// Step 1: Create a strategy interface
interface PaymentStrategy {
    void pay(int amount);
}

// Step 2: Implement concrete strategies
class CreditCardPayment implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("Paid $" + amount + " using Credit Card.");
    }
}

class PayPalPayment implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("Paid $" + amount + " using PayPal.");
    }
}

// Step 3: Create a context class
class ShoppingCart {
    private PaymentStrategy paymentStrategy;

    public ShoppingCart(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    public void checkout(int amount) {
        paymentStrategy.pay(amount);
    }
}

// Step 4: Use the Strategy pattern
public class StrategyPatternExample {
    public static void main(String[] args) {
        ShoppingCart cart1 = new ShoppingCart(new CreditCardPayment());
        cart1.checkout(100);

        ShoppingCart cart2 = new ShoppingCart(new PayPalPayment());
        cart2.checkout(200);
    }
}

Real-World Example:


1.5 Decorator Pattern

Definition: A structural design pattern that allows adding behavior to an object dynamically.

Why Use It?

Implementation in Java:

// Step 1: Create a base interface
interface Coffee {
    String getDescription();
    double cost();
}

// Step 2: Implement a basic concrete class
class SimpleCoffee implements Coffee {
    public String getDescription() {
        return "Simple Coffee";
    }

    public double cost() {
        return 5.0;
    }
}

// Step 3: Create a Decorator class
abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;

    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    public String getDescription() {
        return coffee.getDescription();
    }

    public double cost() {
        return coffee.cost();
    }
}

// Step 4: Create concrete decorators
class Milk extends CoffeeDecorator {
    public Milk(Coffee coffee) {
        super(coffee);
    }

    public String getDescription() {
        return coffee.getDescription() + ", Milk";
    }

    public double cost() {
        return coffee.cost() + 1.5;
    }
}

// Step 5: Use the pattern
public class DecoratorPatternExample {
    public static void main(String[] args) {
        Coffee coffee = new SimpleCoffee();
        System.out.println(coffee.getDescription() + " $" + coffee.cost());

        Coffee coffeeWithMilk = new Milk(coffee);
        System.out.println(coffeeWithMilk.getDescription() + " $" + coffeeWithMilk.cost());
    }
}

Real-World Example:


Awesome! Let’s continue with the rest of the design patterns from the video and then add some additional ones that are equally important.


1.6 Command Pattern

Definition

The Command pattern is a behavioral design pattern that encapsulates a request as an object, allowing parameterization of clients with different requests, queuing of requests, and logging the history of executed actions.

Why Use It?


Implementation in Java:

// Step 1: Create a Command Interface
interface Command {
    void execute();
}

// Step 2: Create Concrete Commands
class Light {
    public void turnOn() {
        System.out.println("Light is ON");
    }

    public void turnOff() {
        System.out.println("Light is OFF");
    }
}

class LightOnCommand implements Command {
    private Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    public void execute() {
        light.turnOn();
    }
}

class LightOffCommand implements Command {
    private Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    public void execute() {
        light.turnOff();
    }
}

// Step 3: Create an Invoker (Remote Control)
class RemoteControl {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public void pressButton() {
        command.execute();
    }
}

// Step 4: Use the Command Pattern
public class CommandPatternExample {
    public static void main(String[] args) {
        Light light = new Light();

        Command lightOn = new LightOnCommand(light);
        Command lightOff = new LightOffCommand(light);

        RemoteControl remote = new RemoteControl();
        
        remote.setCommand(lightOn);
        remote.pressButton();

        remote.setCommand(lightOff);
        remote.pressButton();
    }
}

Real-World Example


1.7 Adapter Pattern

Definition

The Adapter pattern is a structural design pattern that allows objects with incompatible interfaces to work together by converting one interface into another.

Why Use It?


Implementation in Java:

// Step 1: Define the Target Interface
interface MediaPlayer {
    void play(String audioType, String fileName);
}

// Step 2: Create an Adaptee class (Incompatible Interface)
class AdvancedMediaPlayer {
    public void playMp4(String fileName) {
        System.out.println("Playing mp4 file: " + fileName);
    }
}

// Step 3: Create an Adapter class
class MediaAdapter implements MediaPlayer {
    private AdvancedMediaPlayer advancedMediaPlayer;

    public MediaAdapter() {
        this.advancedMediaPlayer = new AdvancedMediaPlayer();
    }

    public void play(String audioType, String fileName) {
        if (audioType.equalsIgnoreCase("MP4")) {
            advancedMediaPlayer.playMp4(fileName);
        }
    }
}

// Step 4: Create the Client class
class AudioPlayer implements MediaPlayer {
    private MediaAdapter mediaAdapter;

    public void play(String audioType, String fileName) {
        if (audioType.equalsIgnoreCase("MP4")) {
            mediaAdapter = new MediaAdapter();
            mediaAdapter.play(audioType, fileName);
        } else {
            System.out.println("Invalid media type: " + audioType);
        }
    }
}

// Step 5: Use the Adapter Pattern
public class AdapterPatternExample {
    public static void main(String[] args) {
        AudioPlayer player = new AudioPlayer();
        player.play("MP4", "song.mp4");
    }
}

Real-World Example


2. Additional Important Design Patterns

The video covered some essential design patterns, but there are a few more that you should definitely know.


2.1 Builder Pattern (Creational Pattern)

Definition

The Builder pattern allows for constructing complex objects step by step, separating the construction logic from the object’s representation.

Why Use It?


Implementation in Java:

// Step 1: Define a Product class
class Computer {
    private String CPU;
    private int RAM;
    private boolean hasGraphicsCard;

    private Computer(ComputerBuilder builder) {
        this.CPU = builder.CPU;
        this.RAM = builder.RAM;
        this.hasGraphicsCard = builder.hasGraphicsCard;
    }

    public void showSpecs() {
        System.out.println("CPU: " + CPU + ", RAM: " + RAM + "GB, Graphics Card: " + hasGraphicsCard);
    }

    // Step 2: Create a Builder class
    static class ComputerBuilder {
        private String CPU;
        private int RAM;
        private boolean hasGraphicsCard;

        public ComputerBuilder setCPU(String CPU) {
            this.CPU = CPU;
            return this;
        }

        public ComputerBuilder setRAM(int RAM) {
            this.RAM = RAM;
            return this;
        }

        public ComputerBuilder setGraphicsCard(boolean hasGraphicsCard) {
            this.hasGraphicsCard = hasGraphicsCard;
            return this;
        }

        public Computer build() {
            return new Computer(this);
        }
    }
}

// Step 3: Use the Builder Pattern
public class BuilderPatternExample {
    public static void main(String[] args) {
        Computer myPC = new Computer.ComputerBuilder()
                .setCPU("Intel i9")
                .setRAM(32)
                .setGraphicsCard(true)
                .build();

        myPC.showSpecs();
    }
}

Real-World Example


2.2 Proxy Pattern (Structural Pattern)

Definition

A Proxy acts as an intermediary between a client and an object, controlling access to it.

Why Use It?


Implementation in Java:

interface Internet {
    void connectTo(String server);
}

class RealInternet implements Internet {
    public void connectTo(String server) {
        System.out.println("Connecting to " + server);
    }
}

class ProxyInternet implements Internet {
    private RealInternet realInternet = new RealInternet();
    private static List<String> bannedSites = List.of("blocked.com", "malware.com");

    public void connectTo(String server) {
        if (bannedSites.contains(server.toLowerCase())) {
            System.out.println("Access Denied to " + server);
            return;
        }
        realInternet.connectTo(server);
    }
}

public class ProxyPatternExample {
    public static void main(String[] args) {
        Internet internet = new ProxyInternet();
        internet.connectTo("example.com");
        internet.connectTo("blocked.com");
    }
}

Real-World Example


Conclusion

We covered: