Advanced Java September 20 ,2025

Other Principles Behind Patterns

Before diving into specific design patterns, it is essential to understand the fundamental principles that shape them. These principles act as the philosophical backbone of software design, ensuring that the solutions we build are not only functional but also maintainable, scalable, and adaptable to change.

Let us explore five cornerstone principles: DRY, KISS, YAGNI, Composition over Inheritance, and Program to Interfaces, not Implementations.

1. DRY – Don’t Repeat Yourself

Definition

The DRY principle emphasizes that “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.” In simpler words, if you find yourself writing the same code in multiple places, you are violating DRY.

Why It Matters

  • Duplication introduces maintenance hazards. If logic changes in one place but not in another, bugs are inevitable.
  • DRY improves clarity: a single, well-named method communicates intent better than duplicated blocks.
  • Encourages reuse: instead of scattered code snippets, functionality is consolidated into modules or services.

Example – Violating DRY

public class InvoiceService {
    public double calculateGST(double amount) {
        return amount * 0.18; // GST calculation
    }
}

public class ReceiptService {
    public double calculateGST(double amount) {
        return amount * 0.18; // Same logic repeated
    }
}

If the tax rate changes, developers must update it in both places, risking inconsistency.

Example – Following DRY

public class TaxCalculator {
    private static final double GST_RATE = 0.18;

    public double calculateGST(double amount) {
        return amount * GST_RATE;
    }
}

public class InvoiceService {
    private final TaxCalculator taxCalculator = new TaxCalculator();
}

public class ReceiptService {
    private final TaxCalculator taxCalculator = new TaxCalculator();
}

Here, tax logic resides in a single authoritative class.

Real-World Analogy

Imagine you are preparing a recipe book. If the sugar-to-flour ratio is repeated in 20 different recipes, updating it becomes a nightmare. Instead, you keep a single master “Ingredients Reference” page. That is DRY.

2. KISS – Keep It Simple, Stupid

Definition

The KISS principle reminds us that systems work best when kept simple. Complexity should not be introduced unless absolutely necessary.

Why It Matters

  • Complex code is harder to test, debug, and maintain.
  • Simplicity increases developer productivity and system reliability.
  • Reduces the learning curve for new team members.

Example – Over-Engineered (Violating KISS)

public int sumArray(int[] numbers) {
    int result = 0;
    for (int i = 0; i < numbers.length; i++) {
        if (i == 0) {
            result = numbers[i];
        } else {
            result = result + numbers[i];
        }
    }
    return result;
}

The logic is unnecessarily verbose.

Example – Keeping It Simple

public int sumArray(int[] numbers) {
    return Arrays.stream(numbers).sum();
}

This is clear, concise, and less error-prone.

Real-World Analogy

A Swiss Army knife with 50 tools might look impressive but is impractical for daily use. Most of the time, a simple kitchen knife does the job better.

3. YAGNI – You Aren’t Gonna Need It

Definition

The YAGNI principle states: Do not implement functionality until it is required. Developers often anticipate future requirements and add “just in case” features that may never be used.

Why It Matters

  • Prevents wasted effort on unused features.
  • Reduces complexity and keeps codebase lean.
  • Allows teams to focus on delivering value that matters now.

Example – Violating YAGNI

public class User {
    private String name;
    private String email;
    private String phone;
    private String address; // Not needed yet
    private String socialSecurityNumber; // "Future-proofing"
}

Example – Following YAGNI

public class User {
    private String name;
    private String email;
}

By focusing on current needs, we avoid bloating the class.

Real-World Analogy

Think of buying gym equipment for a home gym. You may think: “Someday I might take up boxing, so let me buy a punching bag.” But unless you’re already committed to boxing, that equipment just gathers dust. YAGNI tells us to buy it only when you actually need it.

4. Composition over Inheritance

Definition

This principle suggests that object composition (building systems using “has-a” relationships) is often more flexible than inheritance (“is-a” relationships).

Why It Matters

  • Inheritance creates rigid hierarchies where changes in a parent ripple through subclasses.
  • Composition promotes loose coupling and flexibility.
  • Enables objects to acquire behaviors dynamically by combining components.

Example – Inheritance (Less Flexible)

class Engine {
    public void start() {
        System.out.println("Engine starts...");
    }
}
class Car extends Engine {} // Car "is-a" Engine (incorrect modeling)

Example – Composition (Preferred)

class Engine {
    public void start() {
        System.out.println("Engine starts...");
    }
}
class Car {
    private final Engine engine;
    public Car(Engine engine) {
        this.engine = engine;
    }
    public void drive() {
        engine.start();
        System.out.println("Car is driving...");
    }
}

Here, Car has an Engine, which models the real world more accurately and flexibly.

Real-World Analogy

Consider a smartphone. Instead of building one giant class called “Smartphone” with all features, we compose it with separate modules: a camera, a battery, a processor. This allows upgrading or replacing one component without rebuilding the entire phone.

5. Program to Interfaces, not Implementations

Definition

This principle states that code should depend on abstractions (interfaces) rather than concrete implementations.

Why It Matters

  • Encourages loose coupling.
  • Makes swapping implementations straightforward.
  • Enhances testability by allowing use of mocks/stubs.

Example – Tightly Coupled (Violating Principle)

class MySQLDatabase {
    public void connect() {
        System.out.println("Connected to MySQL");
    }
}

class Application {
    private MySQLDatabase db = new MySQLDatabase();
    public void run() {
        db.connect();
    }
}

The application is tied to MySQL and cannot easily switch to another database.

Example – Programming to Interface

interface Database {
    void connect();
}

class MySQLDatabase implements Database {
    public void connect() {
        System.out.println("Connected to MySQL");
    }
}

class PostgreSQLDatabase implements Database {
    public void connect() {
        System.out.println("Connected to PostgreSQL");
    }
}

class Application {
    private final Database database;
    public Application(Database database) {
        this.database = database;
    }
    public void run() {
        database.connect();
    }
}

Now, the Application can work with any database implementation.

Real-World Analogy

Think of a power socket. Appliances don’t care which specific electricity supplier generates the power; they just need a standardized interface (the plug point). By programming to an interface, the system remains adaptable and interchangeable.

Conclusion

These principles—DRY, KISS, YAGNI, Composition over Inheritance, and Programming to Interfaces—are not optional guidelines. They form the philosophical foundation of modern software design. Design patterns build upon these ideas to provide reusable, scalable solutions.

  • DRY avoids duplication.
  • KISS keeps solutions simple.
  • YAGNI prevents unnecessary work.
  • Composition over Inheritance favors flexibility.
  • Programming to Interfaces encourages abstraction and loose coupling.

Together, they ensure that our systems are easier to maintain, easier to extend, and resilient in the face of change.

 

Sanjiv
0

You must logged in to post comments.

Get In Touch

G06, Kristal Olivine Bellandur near Bangalore Central Mall, Bangalore Karnataka, 560103

+91-8076082435

techiefreak87@gmail.com