Kihagyás

SOLID elvek


S — Single Responsibility Principle (Egyetlen felelősség elve)

Egy osztálynak csak egy oka legyen a változásra.

❌ Rossz példa

public class OrderService {

    public void placeOrder(Order order) {
        // üzleti logika
        order.setStatus(OrderStatus.CONFIRMED);

        // e-mail küldés — ez nem az OrderService dolga
        String body = "Rendelés visszaigazolva: " + order.getId();
        EmailClient.send(order.getCustomerEmail(), body);

        // naplózás — ez sem
        System.out.println("Order placed: " + order.getId());
    }
}

✅ Jó példa

public class OrderService {
    private final EmailNotifier emailNotifier;
    private final OrderLogger orderLogger;

    public void placeOrder(Order order) {
        order.setStatus(OrderStatus.CONFIRMED);
        emailNotifier.sendConfirmation(order);
        orderLogger.log(order);
    }
}

public class EmailNotifier {
    public void sendConfirmation(Order order) {
        String body = "Rendelés visszaigazolva: " + order.getId();
        EmailClient.send(order.getCustomerEmail(), body);
    }
}

public class OrderLogger {
    public void log(Order order) {
        System.out.println("Order placed: " + order.getId());
    }
}

Miért jobb? Az OrderService csak az üzleti logikáért felelős. Ha az e-mail sablon változik, csak az EmailNotifier-t kell módosítani — az OrderService-t nem.


O — Open/Closed Principle (Nyitott/zárt elv)

Az osztályok legyenek nyitottak a bővítésre, de zártak a módosításra.

❌ Rossz példa

public class DiscountCalculator {

    public double calculate(Order order, String customerType) {
        if (customerType.equals("VIP")) {
            return order.getTotal() * 0.80;
        } else if (customerType.equals("STUDENT")) {
            return order.getTotal() * 0.90;
        }
        return order.getTotal();
    }
}

Új kedvezménytípus hozzáadásakor mindig módosítani kell ezt az osztályt.

✅ Jó példa

public interface DiscountPolicy {
    double apply(double total);
}

public class VipDiscount implements DiscountPolicy {
    public double apply(double total) { return total * 0.80; }
}

public class StudentDiscount implements DiscountPolicy {
    public double apply(double total) { return total * 0.90; }
}

public class NoDiscount implements DiscountPolicy {
    public double apply(double total) { return total; }
}

public class DiscountCalculator {
    public double calculate(Order order, DiscountPolicy policy) {
        return policy.apply(order.getTotal());
    }
}

Miért jobb? Új kedvezménytípus hozzáadásához csak egy új implementációt kell írni — a DiscountCalculator érintetlen marad.


L — Liskov Substitution Principle (Liskov-féle helyettesítési elv)

Az alosztályok a szülőosztályuk helyett is felhasználhatók kell legyenek, a program helyes működése nélkül.

❌ Rossz példa

public class Rectangle {
    protected int width, height;

    public void setWidth(int width)   { this.width = width; }
    public void setHeight(int height) { this.height = height; }
    public int area() { return width * height; }
}

public class Square extends Rectangle {
    @Override
    public void setWidth(int width) {
        this.width = width;
        this.height = width; // megszegi a Rectangle kontraktusát!
    }

    @Override
    public void setHeight(int height) {
        this.width = height;
        this.height = height;
    }
}

// Klienskód, ami Rectangle-t vár:
Rectangle r = new Square();
r.setWidth(5);
r.setHeight(3);
System.out.println(r.area()); // 9, nem 15 — meglepetés!

✅ Jó példa

public interface Shape {
    int area();
}

public class Rectangle implements Shape {
    private final int width, height;

    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public int area() { return width * height; }
}

public class Square implements Shape {
    private final int side;

    public Square(int side) { this.side = side; }

    public int area() { return side * side; }
}

Miért jobb? A Square nem örökli a Rectangle viselkedési kontraktusát — mindkét forma önállóan, helyesen viselkedik.


I — Interface Segregation Principle (Interfész szegregáció elve)

Egy osztályt ne kényszerítsünk olyan metódusok implementálására, amelyeket nem használ.

❌ Rossz példa

public interface Worker {
    void work();
    void eat();
    void sleep();
}

public class Robot implements Worker {
    public void work() { /* rendben */ }
    public void eat()  { throw new UnsupportedOperationException(); } // robot nem eszik
    public void sleep(){ throw new UnsupportedOperationException(); } // robot nem alszik
}

✅ Jó példa

public interface Workable {
    void work();
}

public interface Restable {
    void eat();
    void sleep();
}

public class HumanWorker implements Workable, Restable {
    public void work()  { System.out.println("Dolgozik."); }
    public void eat()   { System.out.println("Eszik."); }
    public void sleep() { System.out.println("Alszik."); }
}

public class Robot implements Workable {
    public void work() { System.out.println("Robot dolgozik."); }
}

Miért jobb? A Robot csak azt az interfészt implementálja, ami rá vonatkozik. Nincs felesleges kód, nincs UnsupportedOperationException.


D — Dependency Inversion Principle (Függőség megfordításának elve)

A magas szintű modulok ne függjenek alacsony szintű moduloktól. Mindkettő absztrakciótól függjön.

❌ Rossz példa

public class OrderService {
    private final MySQLOrderRepository repository = new MySQLOrderRepository(); // konkrét implementáció

    public void save(Order order) {
        repository.save(order);
    }
}

Ha PostgreSQL-re váltunk, az OrderService-t kell módosítani.

✅ Jó példa

public interface OrderRepository {
    void save(Order order);
    Optional<Order> findById(Long id);
}

public class MySQLOrderRepository implements OrderRepository {
    public void save(Order order) { /* MySQL-specifikus kód */ }
    public Optional<Order> findById(Long id) { /* ... */ return Optional.empty(); }
}

public class OrderService {
    private final OrderRepository repository; // absztrakció

    public OrderService(OrderRepository repository) { // DI konstruktoron keresztül
        this.repository = repository;
    }

    public void save(Order order) {
        repository.save(order);
    }
}

Spring kontextusban:

@Service
public class OrderService {
    private final OrderRepository repository;

    public OrderService(OrderRepository repository) {
        this.repository = repository;
    }
}

@Repository
public class MySQLOrderRepository implements OrderRepository { ... }

Miért jobb? Az OrderService nem tudja (és nem is kell tudnia), hogy MySQL vagy PostgreSQL van mögötte. Teszteléskor könnyen cserélhető mock implementációra.


Összefoglalás

Elv Lényeg Kulcsszó
SRP Egy osztály = egy felelősség Cohesion
OCP Bővíthető, de nem módosítandó Abstraction
LSP Alosztály behelyettesíthető Contracts
ISP Vékony, célzott interfészek Segregation
DIP Absztrakcióra támaszkodj, ne konkrét osztályra Inversion