| Feature | Abstract Class | Interface |
|---|---|---|
| Keyword | abstract class | interface |
| Methods | Abstract + concrete | All abstract by default (default/static ok in Java 8+) |
| Variables | Instance variables allowed | Only public static final constants |
| Constructor | Allowed (not instantiated) | Not allowed |
| Inheritance | Extend ONE only (extends) | Implement MULTIPLE (implements) |
| Abstraction | 0–100% | 100% (full abstraction) |
| Use in patterns | Template Method, Builder base, Prototype base | OCP, DIP, ISP, Factory, Polymorphism (GRASP) |
// INHERITANCE — extends, IS-A, super keyword class Animal { String name; void sound() { System.out.println("..."); } } class Dog extends Animal { // IS-A Animal @Override void sound() { // OVERRIDING = runtime polymorphism System.out.println("Woof"); } } // super() — calls parent constructor, must be FIRST line class GoldenRetriever extends Dog { GoldenRetriever(String n) { super(); this.name = n; } } // OVERLOADING — same name, diff params = compile-time polymorphism class Calculator { int add(int a, int b) { return a+b; } double add(double a, double b) { return a+b; } int add(int a, int b, int c) { return a+b+c; } } // OVERRIDING rules: // Same name + same params + IS-A relationship // Subclass provides specific implementation // Used for: Runtime polymorphism (GRASP: Polymorphism, OCP)
class Person { private String name; // hidden private int age; // Getters and Setters = controlled access public String getName() { return name; } public void setName(String n) { this.name = n; // 'this' = current object } } // Visibility: // + public — anyone // # protected — subclasses + same package // - private — this class only // (none) default — same package only
class Student { int id; String name; // 1. Default — no params Student() { id = 0; name = ""; } // 2. Parameterized Student(int id, String name) { this.id = id; this.name = name; } // 3. Copy constructor Student(Student s) { this.id = s.id; this.name = new String(s.name); // new String() prevents reference sharing! } } // Private constructor → ONLY Singleton uses this // Prevents: new ClassName() from outside
class Counter { // static: ONE copy for ALL objects private static int count = 0; // instance: ONE copy PER object private String name; Counter(String n) { this.name = n; count++; } // static method: called on CLASS, not object public static int getCount() { return count; } } // Usage: // Counter.getCount(); // static — class name // obj.name; // instance — object // SINGLETON uses: private static instance // public static getInstance()
// Composition: Car HAS-A Engine (engine dies with car) class Engine { void start() { System.out.println("Engine started"); } } class Car { private Engine engine; // field = HAS-A Car() { this.engine = new Engine(); } // Creator! void drive() { engine.start(); } } // DIP-friendly version (inject dependency): class Car { private Engine engine; Car(Engine e) { this.engine = e; } // injected! } // "Favor composition over inheritance" (GoF) // → More flexible, avoids tight coupling // → GRASP: Low Coupling principle
// ArrayList — dynamic array, ordered, allows duplicates List<OrderItem> items = new ArrayList<>(); items.add(item); // add items.get(0); // get by index items.size(); // count items.remove(item); // remove // HashMap — key-value pairs, used in Factory/Prototype Map<String, PaymentMethod> methods = new HashMap<>(); methods.put("CARD", new CreditCardPayment()); PaymentMethod p = methods.get("CARD"); // forEach (Java 8+) — used in Info Expert examples double total = 0; for (OrderItem item : items) { total += item.getPrice() * item.getQty(); } // Declare as interface type (DIP / Low Coupling): List<T> list = new ArrayList<>(); // not ArrayList directly
public class OrderLogger { // 1. private static field of same type private static volatile OrderLogger instance = null; // 2. private constructor — nobody outside can call new private OrderLogger() { } // 3. public static getInstance() — only way to get object public static OrderLogger getInstance() { if (instance == null) { // lazy init synchronized (OrderLogger.class) { // thread-safe if (instance == null) // double-check instance = new OrderLogger(); } } return instance; } // 4. actual functionality public void log(String msg) { System.out.println("[LOG] " + msg); } } // Usage: OrderLogger.getInstance().log("Order placed"); OrderLogger.getInstance().log("Payment done"); // SAME object
new OrderLogger() from outside// Step 1: Product Interface public interface PaymentMethod { void pay(double amount); // abstract by default } // Step 2: Concrete Products public class CreditCardPayment implements PaymentMethod { @Override public void pay(double amount) { System.out.println("Credit Card: " + amount); } } public class UPIPayment implements PaymentMethod { @Override public void pay(double amount) { System.out.println("UPI: " + amount); } } // Step 3: Factory (Creator) public class PaymentFactory { public static PaymentMethod createPayment(String type) { switch (type) { case "CARD": return new CreditCardPayment(); case "UPI": return new UPIPayment(); case "WALLET": return new WalletPayment(); default: throw new IllegalArgumentException("Unknown"); } } } // Step 4: Client — no if-else, no concrete class names public class OrderService { public void processPayment(String type, double amount) { PaymentMethod pm = PaymentFactory.createPayment(type); pm.pay(amount); // polymorphic call } }
OrderService) never calls new CreditCardPayment() directlypm.pay() calls the right implementation at runtimePaymentMethod interface onlycreatePayment() is the interface type PaymentMethod, never the concrete class. This is what makes it loosely coupled.public class Order { // fields — all set by builder private int id; private String customer; private List<OrderItem> items; private String address; private boolean isGift; private double tip; // private constructor — only Builder creates Order private Order() { } // Inner static Builder class public static class Builder { private Order order = new Order(); public Builder(int id, String customer) { // required order.id = id; order.customer = customer; } public Builder items(List<OrderItem> items) { order.items = items; return this; // chaining } public Builder address(String a) { order.address = a; return this; } public Builder asGift() { order.isGift = true; return this; } public Builder tip(double t) { order.tip = t; return this; } public Order build() { return order; } // final step } } // Usage — clean method chaining: Order o = new Order.Builder(101, "Rahul") .items(myItems) .address("Koramangala") .asGift() .tip(30.0) .build(); // returns the Order
Order has many optional fields. Without Builder, constructor needs 10+ parameters — "telescoping constructor problem"this (the Builder) → enables method chainingbuild() returns the finished Order objectreturn this in each setter for chaining, (3) build() as final step returning the product.// Step 1: Prototype class implements Cloneable public class MenuItem implements Cloneable { private String name; private double price; private String category; private boolean available; public MenuItem(String name, double price, String cat, boolean avail) { this.name = name; this.price = price; this.category = cat; this.available = avail; } // Step 2: Override clone() @Override public MenuItem clone() { try { return (MenuItem) super.clone(); // shallow copy } catch (CloneNotSupportedException e) { return null; } } // Setters for post-clone customization public void setName(String n) { this.name = n; } public void setPrice(double p) { this.price = p; } } // Step 3: Usage — clone and customize MenuItem burgerTemplate = new MenuItem( "Classic Burger", 200, "Fast Food", true); MenuItem premium = burgerTemplate.clone(); premium.setName("Premium Burger"); premium.setPrice(350); // other fields copied! MenuItem veg = burgerTemplate.clone(); veg.setName("Veg Burger"); // category/avail inherited // Optional: Prototype Registry Map<String, MenuItem> registry = new HashMap<>(); registry.put("burger", burgerTemplate); MenuItem copy = registry.get("burger").clone();
MenuItem has many fields. Creating each from scratch is repetitive and error-prone. Clone template and tweak only the differences.implements Cloneable → Java marker interface enabling super.clone()super.clone() → shallow copy (primitive fields copied, object references shared)String fields: use new String(other.name)// BAD: Order does 4 things class Order { void calculateTotal() { } void processPayment() { } // ← violation void sendEmail() { } // ← violation void saveToDatabase() { } // ← violation } // GOOD: Each class = one responsibility class Order { private List<OrderItem> items; public double calculateTotal() { double total = 0; for (OrderItem i : items) total += i.getPrice() * i.getQty(); return total * 1.18; // tax } } class PaymentProcessor { public void process(Order o, String card) { } } class NotificationService { public void send(String email, Order o) { } } class OrderRepository { public void save(Order o) { } }
public interface PaymentMethod { void pay(double amount); } // Add new types → new class, no existing change class CreditCardPayment implements PaymentMethod { public void pay(double a) { System.out.println("CC: " + a); } } class UPIPayment implements PaymentMethod { public void pay(double a) { System.out.println("UPI: " + a); } } // PaymentProcessor NEVER changes even when new method added class PaymentProcessor { public void process(PaymentMethod pm, double a) { pm.pay(a); // works for ALL implementations } } // Adding PayPal tomorrow: class PayPalPayment implements PaymentMethod { public void pay(double a) { ... } } // ← that's IT. Nothing else changes.
// BAD: AudiobookDelivery breaks parent contract class BookDelivery { void getDeliveryLocations() { } } class AudiobookDelivery extends BookDelivery { @Override void getDeliveryLocations() { throw new UnsupportedOperationException(); // ← VIOLATION } } // GOOD: Split hierarchy so each class fully implements abstract class BookDelivery { String title; } class OfflineDelivery extends BookDelivery { void getDeliveryLocations() { /* physical stores */ } } class OnlineDelivery extends BookDelivery { void getSoftwareOptions() { /* streaming apps */ } } class HardcoverDelivery extends OfflineDelivery { } class AudiobookDelivery extends OnlineDelivery { } // Now AudiobookDelivery NEVER has to deal with getDeliveryLocations()
// BAD: SmallTakeaway forced to implement methods it can't use interface RestaurantOps { void addMenuItem(); void acceptOrder(); void processPayment(); // not all need this! void deliverOrder(); // not all need this! } // GOOD: Split into focused interfaces interface MenuManageable { void addMenuItem(); void removeMenuItem(); } interface OrderAcceptable { void acceptOrder(); } interface Deliverable { void deliverOrder(); } // Each class implements ONLY what it needs class SmallTakeaway implements MenuManageable, OrderAcceptable { public void addMenuItem() { } public void removeMenuItem() { } public void acceptOrder() { } } class FullService implements MenuManageable, OrderAcceptable, Deliverable { }
// BAD: high-level depends on concretions (tight coupling) class OrderService { private CreditCardPayment payment = new CreditCardPayment(); // ← BAD private EmailNotification notifier = new EmailNotification(); // ← BAD } // GOOD: depend on interfaces, inject via constructor interface PaymentMethod { void pay(double a); } interface NotificationChannel { void send(String m); } class OrderService { private PaymentMethod payment; // interface type! private NotificationChannel notifier; // interface type! // Constructor Injection = Dependency Injection (DI) public OrderService(PaymentMethod p, NotificationChannel n) { this.payment = p; this.notifier = n; } public void placeOrder(Order o) { payment.pay(o.calculateTotal()); notifier.send("Order confirmed!"); } }
// Concrete implementations class CreditCardPayment implements PaymentMethod { public void pay(double a) { System.out.println("CC payment: " + a); } } class UPIPayment implements PaymentMethod { public void pay(double a) { System.out.println("UPI payment: " + a); } } class EmailNotification implements NotificationChannel { public void send(String m) { System.out.println("Email: " + m); } } // Client wires it all together — easy to swap: OrderService s1 = new OrderService( new CreditCardPayment(), new EmailNotification()); OrderService s2 = new OrderService( new UPIPayment(), new EmailNotification()); // OrderService code never changes!
// Order CONTAINS OrderItems // → Order CREATES OrderItems class Order { private List<OrderItem> items = new ArrayList<>(); public OrderItem addItem( String name, double price, int qty) { OrderItem item = new OrderItem(name, price, qty); items.add(item); return item; } }
// Order HAS items → Order calculates total // NOT a utility class outside class Order { private List<OrderItem> items; public double calculateTotal() { double total = 0; for (OrderItem i : items) total += i.getPrice() * i.getQty(); return total; } } // Similarly: // Cart.getCartTotal() — Cart has items // SalesLineItem.getSubtotal() — has price+qty
// Controller delegates; doesn't do work itself class OrderController { private PaymentMethod payment; private NotificationChannel notifier; private OrderRepository repo; OrderController(PaymentMethod p, NotificationChannel n, OrderRepository r) { this.payment=p; this.notifier=n; this.repo=r; } public void placeOrder(Order o) { payment.pay(o.calculateTotal()); // delegate notifier.send("Confirmed"); // delegate repo.save(o); // delegate } // No business logic here — just coordination }
// Replaces if-else with overriding // Same method name, different behavior per type interface NotificationChannel { void send(String message); } class EmailNotification implements NotificationChannel { public void send(String msg) { System.out.println("Email: "+msg); } } class SMSNotification implements NotificationChannel { public void send(String msg) { System.out.println("SMS: "+msg); } } // Caller never needs if-else: NotificationChannel c = new SMSNotification(); c.send("Ready"); // runtime decides
// Wrap the CHANGING part behind a stable interface // Tax logic changes → protect Order from it interface TaxCalculator { double calculate(double amount); } class IndiaTax implements TaxCalculator { public double calculate(double a) { return a * 0.18; // GST } } class Order { private TaxCalculator tax; Order(TaxCalculator t) { this.tax = t; } public double getFinalTotal(double sub) { return sub + tax.calculate(sub); } } // Order never changes when tax rules change!
// Pure Fabrication: class NOT in real domain // Logger, Repository, Factory are all fabrications class OrderRepository { // not a real food concept private List<Order> orders = new ArrayList<>(); public void save(Order o) { orders.add(o); } public Order findById(int id) { for (Order o : orders) if (o.getId()==id) return o; return null; } } // Indirection: PaymentService mediates between // OrderController and payment gateways class PaymentService { // middleman / indirection private Map<String,PaymentMethod> map = new HashMap<>(); PaymentService() { map.put("CARD", new CreditCardPayment()); map.put("UPI", new UPIPayment()); } public void pay(String type, double amt) { map.get(type).pay(amt); } }
public interface InterfaceName { // methods: public abstract by default — don't write abstract void methodName(ParamType param); ReturnType anotherMethod(); // Java 8+: default method (optional, has body) default void helper() { System.out.println("default"); } } // Implement: use implements keyword class ConcreteClass implements InterfaceName { @Override public void methodName(ParamType p) { /* body */ } @Override public ReturnType anotherMethod() { return ...; } } // Multiple interfaces: class X implements A, B, C { ... }
public abstract class AbstractName { // can have instance fields (unlike interface) protected String name; protected double value; // constructor — called by subclass via super() public AbstractName(String n, double v) { this.name = n; this.value = v; } // abstract method — subclass MUST implement public abstract double compute(); // concrete method — subclass inherits or overrides public String getName() { return name; } } // Extend: class Concrete extends AbstractName { public Concrete(String n, double v) { super(n, v); } @Override public double compute() { return value * 2; } }
| Principle / Pattern | Java Construct Used | Key Code Signal |
|---|---|---|
| SRP | Split into multiple classes | Separate Order, PaymentProcessor, NotificationService classes |
| OCP + Polymorphism (GRASP) | interface + implements + @Override | No if-else; method in interface called polymorphically |
| LSP | Correct extends hierarchy; @Override without breaking contract | No throw new UnsupportedOperationException() in override |
| ISP | Multiple small interfaces; implements A, B | No empty method bodies forced on a class |
| DIP + Low Coupling | Fields declared as interface type; constructor injection | private PaymentMethod pm; + injection in constructor |
| Creator (GRASP) | Class with a List<T> field creates T inside its own method | items.add(new OrderItem(...)) inside Order class |
| Info Expert | Method placed in the class that owns the data | calculateTotal() inside Order, not in a utility class |
| Controller | Class with multiple dependency fields; delegates calls | All methods just call payment.pay(), repo.save() |
| Protected Variation | interface wrapping the changing part | TaxCalculator interface injected into Order |
| Pure Fabrication | Utility/service/repository class not in domain | OrderRepository, OrderLogger, PaymentFactory |
| Singleton | private static field + private constructor + static getInstance() | if(instance==null) inside getInstance() |
| Factory Method | interface product + factory method returning interface type | public static PaymentMethod createPayment(String type) |
| Builder | Inner static class Builder + return this + build() | Method chaining: .items().address().build() |
| Prototype | implements Cloneable + @Override clone() | return (T) super.clone() + setters for customization |