Design Patterns in Java — The Practical GoF

Gang of Four design pattern — বাস্তব Java-র দৃষ্টিতে

Read: ~40 min Advanced 5 practice problems Live code runner

1. What Are Design Patterns?

In 1994, four authors — Gamma, Helm, Johnson, and Vlissides (the Gang of Four) — catalogued 23 recurring solutions to common design problems in object-oriented code. You don't need to memorise all 23. The goal is simpler: recognise them in the libraries you already use, and know the five or six that come up weekly in real Java projects.

১৯৯৪ সালে চারজন লেখক (Gang of Four — Gamma, Helm, Johnson, Vlissides) ২৩টি পুনরাবৃত্ত design সমস্যার সমাধান catalog করেন। সব ২৩টি মুখস্থ করার দরকার নেই। আসল কাজ — আপনার প্রতিদিনের library-তে এগুলো চিনতে পারা এবং ৫–৬টি common pattern সাবলীলভাবে লেখা।

2. Three Families of Patterns

GoF Patterns — Three Families Creational HOW objects are made Singleton Factory / Abstract Factory Builder · Prototype Structural HOW objects compose Adapter · Decorator Facade · Proxy Composite · Bridge Behavioural HOW objects interact Observer · Strategy Command · Iterator Template · State Figure 45.1 — ২৩টি pattern তিন পরিবারে ভাগ করা — Creational, Structural, Behavioural।
২৩টি pattern তিনটি পরিবারে ভাগ করা — কীভাবে object তৈরি হয় (Creational), কীভাবে object একসাথে জোড়া লাগে (Structural), এবং কীভাবে object-রা একে অপরের সাথে কথা বলে (Behavioural)।

3. Singleton — One, and Only One

The Singleton pattern ensures exactly one instance of a class exists per JVM. Common for config, caches, and loggers. The enum form is the simplest, safest, and thread-safe by construction — recommended by Joshua Bloch in Effective Java.

Singleton pattern নিশ্চিত করে — পুরো JVM-এ একটিই instance। Config, cache বা logger-এ বেশি লাগে। Java-তে enum দিয়ে Singleton সবচেয়ে সহজ, thread-safe এবং Joshua Bloch-এর Effective Java-তে সুপারিশকৃত।
Main.java
enum Config {
    INSTANCE;
    private String env = "production";
    public String getEnv() { return env; }
    public void setEnv(String e) { env = e; }
}

class Main {
    public static void main(String[] args) {
        Config.INSTANCE.setEnv("staging");
        System.out.println("env = " + Config.INSTANCE.getEnv());
        // Any other caller in the JVM gets the SAME object.
        System.out.println("same? " + (Config.INSTANCE == Config.INSTANCE));
    }
}

4. Factory & Builder — Taming Construction

A Factory hides the new operator behind a method so callers can choose which concrete type to make. A Builder solves the opposite problem: creating a single object that has many optional parameters, without a telescoping constructor.

Factory new-কে একটি method-এর পিছনে লুকিয়ে রাখে, যাতে caller কোন concrete type তৈরি হবে সেটি বেছে নিতে পারে। Builder উল্টোটা করে — একটি object-এ অনেক optional parameter থাকলে telescoping constructor ছাড়াই object তৈরি করার উপায় দেয়।
Main.java
// ---------- FACTORY ----------
interface Payment { void pay(int taka); }
class BkashPayment    implements Payment { public void pay(int t){System.out.println("bKash "+t);} }
class CardPayment     implements Payment { public void pay(int t){System.out.println("Card "+t);} }

class PaymentFactory {
    static Payment of(String kind) {
        return switch(kind) {
            case "bkash" -> new BkashPayment();
            case "card"  -> new CardPayment();
            default       -> throw new IllegalArgumentException(kind);
        };
    }
}

// ---------- BUILDER ----------
class Invoice {
    private final String customer; private final int amount; private final String currency; private final boolean paid;
    private Invoice(Builder b){this.customer=b.c;this.amount=b.a;this.currency=b.cur;this.paid=b.p;}
    public String toString(){return customer+" "+amount+" "+currency+(paid?" [PAID]":"");}
    static class Builder {
        String c; int a; String cur = "BDT"; boolean p;
        Builder customer(String s){c=s;return this;}
        Builder amount(int x){a=x;return this;}
        Builder currency(String s){cur=s;return this;}
        Builder paid(boolean x){p=x;return this;}
        Invoice build(){return new Invoice(this);}
    }
}

class Main {
    public static void main(String[] args) {
        PaymentFactory.of("bkash").pay(500);
        PaymentFactory.of("card").pay(2500);

        Invoice inv = new Invoice.Builder()
            .customer("Arif").amount(1250).paid(true).build();
        System.out.println(inv);
    }
}

5. Observer & Strategy — Behaviour That Changes

Observer lets objects subscribe to events from a subject (think: UI listeners, event buses). Strategy lets you swap an algorithm at runtime — in modern Java this is often just "pass a lambda".

Observer — object-রা একটি subject-এর event-এ subscribe করে (UI listener, event bus)। Strategy — algorithm runtime-এ বদলানো যায়; আধুনিক Java-তে এটি প্রায়শই একটি lambda।
Main.java
import java.util.*;
import java.util.function.*;

// OBSERVER: publisher keeps a list of Consumer listeners
class PriceFeed {
    private final List<Consumer<Integer>> obs = new ArrayList<>();
    public void subscribe(Consumer<Integer> c) { obs.add(c); }
    public void publish(int price) { obs.forEach(c -> c.accept(price)); }
}

// STRATEGY: BiFunction picked at runtime
class Main {
    public static int discount(int price, BiFunction<Integer,Integer,Integer> strat) {
        return strat.apply(price, 10);
    }
    public static void main(String[] args) {
        PriceFeed f = new PriceFeed();
        f.subscribe(p -> System.out.println("UI update: " + p));
        f.subscribe(p -> System.out.println("Alert@" + p));
        f.publish(120);

        BiFunction<Integer,Integer,Integer> percent = (pr, d) -> pr - (pr * d / 100);
        BiFunction<Integer,Integer,Integer> flat    = (pr, d) -> pr - d;
        System.out.println("percent: " + discount(1000, percent));
        System.out.println("flat   : " + discount(1000, flat));
    }
}

6. You're Already Using Them

PatternExample in JDK / ecosystemবাংলায়
SingletonRuntime.getRuntime()প্রতি JVM-এ একটিই Runtime instance।
FactoryCalendar.getInstance(), List.of(...)Static method object ফেরত দেয়।
BuilderStringBuilder, HttpRequest.newBuilder()Step-by-step object তৈরি।
ObserverSwing listeners, Flow.SubscriberEvent-এ subscribe।
StrategyComparator, CollectorsAlgorithm parameter হিসেবে।
DecoratorBufferedReader wrapping FileReaderএকই interface-এ ক্ষমতা যোগ।
IteratorIterator<T>, every for-eachCollection-এ একে একে পরিক্রমা।
Template MethodAbstractList, Servlet service()Base class কাঠামো দেয়, subclass অংশ বসায়।

7. Vocabulary

TermMeaningবাংলায়
CouplingHow tightly classes depend on each other.Class-গুলো কতটা শক্তভাবে জড়িত।
CohesionHow focused a class is on a single job.একটি class একটিই কাজে কতটা নিবদ্ধ।
Telescoping ctorConstructor overloads with ever-more parameters.ধাপে ধাপে বাড়া parameter-এর constructor।
Dependency inversionDepend on abstractions, not concretions.Concrete নয়, abstraction-এর উপর নির্ভর।
PolymorphismOne interface, many behaviours.একই interface, ভিন্ন আচরণ।
SOLIDFive classic OO design principles.পাঁচটি classic OO principle।

8. Practice Problems

Try each before revealing.

আগে নিজে চেষ্টা করুন, তারপর উত্তর দেখুন।
  1. Implement a Singleton Logger using the enum idiom and log three lines.
    Enum idiom দিয়ে একটি Singleton Logger বানান এবং তিনটি log line লিখুন।
    ✨ Show Answer
    Main.java
    enum Logger {
        INSTANCE;
        void log(String msg) { System.out.println("[LOG] " + msg); }
    }
    
    class Main {
        public static void main(String[] args) {
            Logger.INSTANCE.log("app started");
            Logger.INSTANCE.log("user login");
            Logger.INSTANCE.log("payment received");
        }
    }
  2. Write a ShapeFactory that returns a Circle, Square, or Triangle based on a string key, each printing its area formula.
    একটি ShapeFactory লিখুন যা string অনুযায়ী Circle/Square/Triangle ফেরত দেবে এবং প্রত্যেকে তার area formula প্রিন্ট করবে।
    ✨ Show Answer
    Main.java
    interface Shape { void area(); }
    class Circle   implements Shape { public void area(){System.out.println("π r²");} }
    class Square   implements Shape { public void area(){System.out.println("s²");} }
    class Triangle implements Shape { public void area(){System.out.println("½ b h");} }
    
    class ShapeFactory {
        static Shape of(String k) {
            return switch(k) {
                case "circle"   -> new Circle();
                case "square"   -> new Square();
                case "triangle" -> new Triangle();
                default           -> throw new IllegalArgumentException(k);
            };
        }
    }
    
    class Main {
        public static void main(String[] args) {
            ShapeFactory.of("circle").area();
            ShapeFactory.of("square").area();
            ShapeFactory.of("triangle").area();
        }
    }
  3. Write a Builder for a User with required name, optional email and phone.
    একটি User-এর Builder বানান — required name, optional email ও phone।
    ✨ Show Answer
    Main.java
    class User {
        private final String name, email, phone;
        private User(Builder b){name=b.n;email=b.e;phone=b.p;}
        public String toString(){return name+"/"+email+"/"+phone;}
        static class Builder {
            String n, e="—", p="—";
            Builder(String name){n=name;}
            Builder email(String x){e=x;return this;}
            Builder phone(String x){p=x;return this;}
            User build(){return new User(this);}
        }
    }
    
    class Main {
        public static void main(String[] args) {
            User u = new User.Builder("Fatima").email("f@abcltech.com").build();
            System.out.println(u);
        }
    }
  4. Is "every Singleton" always a good idea? List two problems.
    সব সময় Singleton কি ভালো? দুটি সমস্যা লিখুন।
    ✨ Show Answer

    Answer: (1) Singletons are effectively global state — they make testing hard because tests cannot swap them for a fake without static hacks. (2) They hide dependencies: a class that reaches for Config.INSTANCE inside itself looks independent but is actually tightly coupled. Modern dependency-injection (Spring) gives you one-instance semantics without the static baggage.

  5. Rewrite the Strategy example so that instead of BiFunction, two algorithms are enum members with an abstract apply(int, int).
    Strategy উদাহরণটি এমনভাবে লিখুন যাতে দুটি algorithm enum member হয় — apply(int, int) abstract method সহ।
    ✨ Show Answer
    Main.java
    enum Discount {
        PERCENT { public int apply(int pr, int d) { return pr - (pr * d / 100); } },
        FLAT    { public int apply(int pr, int d) { return pr - d; } };
        public abstract int apply(int pr, int d);
    }
    
    class Main {
        public static void main(String[] args) {
            System.out.println(Discount.PERCENT.apply(1000, 10));
            System.out.println(Discount.FLAT.apply(1000, 150));
        }
    }

Summary — Module 45

Design patterns are named solutions to recurring OO problems. Know a handful deeply — Singleton, Factory, Builder, Observer, Strategy — and you will read the JDK, Spring, and every mature Java codebase with ease. Modern Java (lambdas, records, sealed types, switch expressions) makes many patterns shorter than the 1994 book suggests — use them, but keep it simple.

Design pattern হলো পুনরাবৃত্ত OO সমস্যার নামকৃত সমাধান। ৫–৬টি ভালোভাবে জানলেই JDK, Spring, ও যেকোনো পরিণত Java codebase পড়া সহজ হবে। আধুনিক Java-তে (lambda, record, sealed, switch expression) অনেক pattern আরো সংক্ষিপ্ত — ব্যবহার করুন, জটিল করবেন না।

Next Module → Java Modules (JPMS) & Modern Packaging — Java 9+ module system।