Java Implementation — Exam Notes

UE23CS352B · Unit 2 + GRASP + SOLID + Creational Patterns · 2-Mark Java Implementation Criterion
PART 1 OOP Building Blocks — The Java Syntax Used in Every Pattern

Abstract Class vs Interface — The Most Important Distinction

FeatureAbstract ClassInterface
Keywordabstract classinterface
MethodsAbstract + concreteAll abstract by default (default/static ok in Java 8+)
VariablesInstance variables allowedOnly public static final constants
ConstructorAllowed (not instantiated)Not allowed
InheritanceExtend ONE only (extends)Implement MULTIPLE (implements)
Abstraction0–100%100% (full abstraction)
Use in patternsTemplate Method, Builder base, Prototype baseOCP, DIP, ISP, Factory, Polymorphism (GRASP)
Rule of thumb for exam: When the problem says "many types" of something (payment, notification, discount) → interface. When types share common fields/logic → abstract class.

Inheritance, Overriding, Overloading

// 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)

Encapsulation + Access Modifiers

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
GRASP link: Encapsulation supports Information Expert — class hides its data and exposes only needed behavior.

Constructors (3 Types)

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

Static vs Instance

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 (HAS-A) — Prefer over Inheritance

// 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

Collections Used in Patterns

// 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
PART 2 Complete Java Templates — Write These in Exam
SINGLETON — Private constructor + static instance + static getInstance()
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

What to say in exam

  • Why Singleton? Only ONE logger/config/connection pool needed across the entire system
  • private constructor → prevents new OrderLogger() from outside
  • static field → same instance shared across all callers
  • volatile → ensures visibility across threads
  • Double-checked locking → best method; only synchronizes when null

Principles demonstrated

  • Low Coupling (GRASP) — one global access point
  • Pure Fabrication (GRASP) — Logger is not a domain concept
  • Violates SRP — manages own lifecycle AND does work
  • ⚠ Not ideal for unit testing (hard to mock)
4 steps to write: (1) private static field, (2) private constructor, (3) static getInstance() with null check, (4) real methods
FACTORY METHOD — Interface + concrete classes + factory that returns interface type
// 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
  }
}

What to say in exam

  • Why Factory? Type of payment not known at compile time; determined at runtime by user choice
  • Client (OrderService) never calls new CreditCardPayment() directly
  • To add new payment: create class, add one case in factory — nothing else changes

Principles demonstrated

  • OCP (SOLID) — Open for extension (new payment type = new class), closed for modification
  • Polymorphism (GRASP)pm.pay() calls the right implementation at runtime
  • Low Coupling (GRASP) — client depends on PaymentMethod interface only
  • Creator (GRASP) — factory owns object creation logic
  • DIP (SOLID) — high-level depends on abstraction, not concretion
Key: The return type of createPayment() is the interface type PaymentMethod, never the concrete class. This is what makes it loosely coupled.
BUILDER — Inner static Builder class + method chaining + build()
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

What to say in exam

  • Why Builder? Order has many optional fields. Without Builder, constructor needs 10+ parameters — "telescoping constructor problem"
  • Each setter returns this (the Builder) → enables method chaining
  • build() returns the finished Order object
  • GoF-style alternative: separate Director class controls sequence

Principles demonstrated

  • SRP (SOLID) — construction logic isolated from Order business logic
  • Creator (GRASP) — Builder owns the creation + assembly of Order
  • High Cohesion (GRASP) — each builder method focused on one field
3 things to always show: (1) private Order constructor, (2) return this in each setter for chaining, (3) build() as final step returning the product.
PROTOTYPE — Cloneable + clone() + registry
// 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();

What to say in exam

  • Why Prototype? 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)
  • For deep copy of String fields: use new String(other.name)

Principles demonstrated

  • OCP (SOLID) — new item types by subclassing, not modifying
  • Low Coupling (GRASP) — client doesn't need to know concrete type
  • Protected Variation (GRASP) — clone() hides construction complexity
PART 3 SOLID Principles — Minimal Java Code for Each

SRP — Split classes that do too much

// 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) { }
}

OCP — Extend via interface, never modify existing

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.

LSP — Subclass must fully honour parent's contract

// 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()

ISP — Many small interfaces > one fat interface

// 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 { }

DIP — Depend on abstractions + Dependency Injection

// 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!
PART 4 GRASP — Java Code Patterns to Recognise

Creator

// 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;
  }
}

Information Expert

// 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

// 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
}

Polymorphism (GRASP)

// 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

Protected Variation

// 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 + Indirection

// 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);
  }
}
PART 5 Exam Quick Reference — Java Syntax You Must Know Cold

Interface Declaration Checklist

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 { ... }

Abstract Class Checklist

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 → Java Construct Mapping (Write This as Notes)

Principle / PatternJava Construct UsedKey Code Signal
SRPSplit into multiple classesSeparate Order, PaymentProcessor, NotificationService classes
OCP + Polymorphism (GRASP)interface + implements + @OverrideNo if-else; method in interface called polymorphically
LSPCorrect extends hierarchy; @Override without breaking contractNo throw new UnsupportedOperationException() in override
ISPMultiple small interfaces; implements A, BNo empty method bodies forced on a class
DIP + Low CouplingFields declared as interface type; constructor injectionprivate PaymentMethod pm; + injection in constructor
Creator (GRASP)Class with a List<T> field creates T inside its own methoditems.add(new OrderItem(...)) inside Order class
Info ExpertMethod placed in the class that owns the datacalculateTotal() inside Order, not in a utility class
ControllerClass with multiple dependency fields; delegates callsAll methods just call payment.pay(), repo.save()
Protected Variationinterface wrapping the changing partTaxCalculator interface injected into Order
Pure FabricationUtility/service/repository class not in domainOrderRepository, OrderLogger, PaymentFactory
Singletonprivate static field + private constructor + static getInstance()if(instance==null) inside getInstance()
Factory Methodinterface product + factory method returning interface typepublic static PaymentMethod createPayment(String type)
BuilderInner static class Builder + return this + build()Method chaining: .items().address().build()
Prototypeimplements Cloneable + @Override clone()return (T) super.clone() + setters for customization