Design Patterns in Java — The Practical GoF
Gang of Four design pattern — বাস্তব Java-র দৃষ্টিতে
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.
2. Three Families of Patterns
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.
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.
new-কে একটি method-এর পিছনে লুকিয়ে রাখে, যাতে caller কোন concrete type তৈরি হবে সেটি বেছে নিতে পারে। Builder উল্টোটা করে — একটি object-এ অনেক optional parameter থাকলে telescoping constructor ছাড়াই object তৈরি করার উপায় দেয়।
// ---------- 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".
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
| Pattern | Example in JDK / ecosystem | বাংলায় |
|---|---|---|
| Singleton | Runtime.getRuntime() | প্রতি JVM-এ একটিই Runtime instance। |
| Factory | Calendar.getInstance(), List.of(...) | Static method object ফেরত দেয়। |
| Builder | StringBuilder, HttpRequest.newBuilder() | Step-by-step object তৈরি। |
| Observer | Swing listeners, Flow.Subscriber | Event-এ subscribe। |
| Strategy | Comparator, Collectors | Algorithm parameter হিসেবে। |
| Decorator | BufferedReader wrapping FileReader | একই interface-এ ক্ষমতা যোগ। |
| Iterator | Iterator<T>, every for-each | Collection-এ একে একে পরিক্রমা। |
| Template Method | AbstractList, Servlet service() | Base class কাঠামো দেয়, subclass অংশ বসায়। |
7. Vocabulary
| Term | Meaning | বাংলায় |
|---|---|---|
| Coupling | How tightly classes depend on each other. | Class-গুলো কতটা শক্তভাবে জড়িত। |
| Cohesion | How focused a class is on a single job. | একটি class একটিই কাজে কতটা নিবদ্ধ। |
| Telescoping ctor | Constructor overloads with ever-more parameters. | ধাপে ধাপে বাড়া parameter-এর constructor। |
| Dependency inversion | Depend on abstractions, not concretions. | Concrete নয়, abstraction-এর উপর নির্ভর। |
| Polymorphism | One interface, many behaviours. | একই interface, ভিন্ন আচরণ। |
| SOLID | Five classic OO design principles. | পাঁচটি classic OO principle। |
8. Practice Problems
Try each before revealing.
-
Implement a Singleton
Loggerusing the enum idiom and log three lines.Enum idiom দিয়ে একটি SingletonLoggerবানান এবং তিনটি log line লিখুন।✨ Show Answer
Main.javaenum 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"); } } -
Write a
ShapeFactorythat returns aCircle,Square, orTrianglebased on a string key, each printing its area formula.একটিShapeFactoryলিখুন যা string অনুযায়ী Circle/Square/Triangle ফেরত দেবে এবং প্রত্যেকে তার area formula প্রিন্ট করবে।✨ Show Answer
Main.javainterface 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(); } } -
Write a Builder for a
Userwith requiredname, optionalemailandphone.একটিUser-এর Builder বানান — requiredname, optionalemailওphone।✨ Show Answer
Main.javaclass 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); } } -
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.INSTANCEinside itself looks independent but is actually tightly coupled. Modern dependency-injection (Spring) gives you one-instance semantics without the static baggage. -
Rewrite the Strategy example so that instead of
BiFunction, two algorithms are enum members with an abstractapply(int, int).Strategy উদাহরণটি এমনভাবে লিখুন যাতে দুটি algorithm enum member হয় —apply(int, int)abstract method সহ।✨ Show Answer
Main.javaenum 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.