Loslegen
Javaneer
Zurück zur Stufe
Stufe 5·Software-Handwerk

Design Principles

High cohesion, low coupling, composition over inheritance, program to interfaces, immutability, and DI.

18 Min. LesezeitFortgeschritten

Deutsche Übersetzung in Arbeit

Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.

Auf dieser Seite

These design principles govern how classes relate to one another. Get them right and your system becomes flexible and testable; get them wrong and everything tangles together. They underpin the design patterns you'll meet next.

High cohesion

Cohesion measures how focused a class is. A highly cohesive class has elements that all work toward one clear purpose.

Cohesion is a well-organized toolbox

A drawer labeled "screwdrivers" containing only screwdrivers is cohesive - you know exactly what's inside and why. A junk drawer with batteries, receipts, and a spoon is not. Cohesive classes are easy to name, understand, and reuse.

Low coupling

Coupling measures how much classes depend on each other. Low coupling means changing one class rarely forces changes in others.

Coupling is like tangled headphones

Loosely arranged cables are easy to rearrange; tangled ones mean tugging one yanks them all. Tightly coupled classes are tangled cables - a change in one drags the rest along. Aim for parts you can move independently.

The goal: high cohesion, low coupling. Focused parts, loosely connected.

Composition over inheritance

Inheritance ("is-a") is rigid and creates deep, brittle hierarchies. Composition ("has-a") - building objects from other objects - is more flexible.

// Inheritance: rigid - what if a duck-like robot needs to fly but not quack?
class Duck extends Bird { }

// Composition: mix in behaviors as parts
class Duck {
    private final FlyBehavior fly;      // HAS-A a fly behavior
    private final QuackBehavior quack;  // HAS-A a quack behavior
    Duck(FlyBehavior fly, QuackBehavior quack) {
        this.fly = fly;
        this.quack = quack;
    }
}

Favor composition

Composition lets you assemble and swap behaviors at runtime, avoids fragile deep hierarchies, and sidesteps the limits of single inheritance. Reach for inheritance only for genuine, stable 'is-a' relationships; otherwise, compose.

Program to interfaces, not implementations

Depend on what something does (an interface), not how (a concrete class). You saw this with collections and Dependency Inversion.

List<String> items = new ArrayList<>();   // depend on List, not ArrayList

Now you can switch ArrayList for LinkedList - or a mock in tests - without touching the rest of your code.

Favor immutability where appropriate

Immutable objects (no setters, final fields) are simpler to reason about, thread-safe, and safe to share. Prefer them for values - you met this in the OOP stage.

Dependency Injection

Instead of a class creating its own dependencies, they're given to it from outside. This is Dependency Inversion made practical.

// Without DI: hard to test, tightly coupled
class OrderService {
    private final PaymentGateway gateway = new StripeGateway();  // welded in
}

// With DI: dependencies passed in - flexible and testable
class OrderService {
    private final PaymentGateway gateway;
    OrderService(PaymentGateway gateway) {   // injected
        this.gateway = gateway;
    }
}
Dependency injection is like ordering ingredients

Rather than a chef growing their own wheat and milking cows (creating dependencies), ingredients are delivered to the kitchen (injected). The chef focuses on cooking. Swap suppliers freely - or hand over fake ingredients for a practice run (tests). Frameworks like Spring do this delivery automatically.

These principles enable the patterns

High cohesion, low coupling, composition, and programming to interfaces are the foundation the design patterns build on. Notice how each pattern in the next lessons is really these principles applied to a recurring problem.

Quick check

You want a class to be easy to test and swap its dependencies. Which approach fits best?

Key takeaways

  • Aim for high cohesion (focused classes) and low coupling (few dependencies between them).
  • Favor composition (has-a) over inheritance (is-a) for flexibility.
  • Program to interfaces, not implementations, so parts stay swappable.
  • Favor immutability for values - simpler, thread-safe, shareable.
  • Dependency Injection supplies a class's dependencies from outside, improving testability and flexibility.

Now we reach one of software's greatest topics: design patterns. First up, the creational patterns.