Kezdő Olvasási idő: ~17 perc

Alapelvek

Egységbezárás, öröklődés, polimorfizmus és absztrakció

OOP Alapelvek

Az objektumorientált programozás négy alappillére: encapsulation, inheritance, polymorphism és abstraction — valós üzleti domain példákon keresztül.

1. Definíció

Mi az OOP?

Az Object-Oriented Programming (OOP) egy programozási paradigma, amely az adatokat és a rajtuk végzett műveleteket objektumokba szervezi. A Java nyelv tervezésének alapja — a java.lang.Object minden osztály őse.

Miért létezik?

A procedurális megközelítés nagy kódbázisoknál gyorsan kezelhetetlenné válik: az adatstruktúrák és a hozzájuk tartozó logika szétszóródik. Az OOP ezt a problémát oldja meg:

  • Modularitás — egymástól független egységek, amelyek önállóan fejleszthetők
  • Újrafelhasználhatóság — közös viselkedés kiemelése, nem copy-paste
  • Karbantarthatóság — a változtatás hatóköre jól behatárolható
  • Modellezés — az üzleti domain természetes leképezése kódra

Hol helyezkedik el?

Az OOP a fő programozási paradigmák egyike:

  • Procedurális — például C
  • Objektumorientált — Java, C#, Kotlin ← ez a téma
  • Funkcionális — Haskell, Scala
  • Hibrid — Java 8+ és Kotlin, ahol OOP és FP együtt jelenik meg

A modern Java (8+) ötvözi az OOP-t és a funkcionális elemeket: lambda expressions, Stream API, Optional — de az alapja továbbra is objektumorientált.


2. Alapfogalmak

2.1 Encapsulation (Egységbezárás)

Az encapsulation az adatok és a hozzájuk tartozó műveletek egy egységbe zárása, valamint az adatok közvetlen hozzáférésének korlátozása.

Access modifiers

Modifier Osztályon belül Package Leszármazott Mindenhol
private
(default)
protected
public

Miért fontos?

  • Invariánsok védelme — az objektum belső állapota mindig konzisztens marad
  • Változáskezelés — a belső implementáció cserélhető anélkül, hogy a klienskód változna
  • API felület minimalizálás — csak azt tesszük publikussá, amit tényleg használni kell
// Egy fizetési tranzakció encapsulált modellje
public class PaymentTransaction {

    // Belső állapot — kívülről nem módosítható közvetlenül
    private final String transactionId;
    private BigDecimal amount;
    private Currency currency;
    private TransactionStatus status;

    public PaymentTransaction(String transactionId, BigDecimal amount, Currency currency) {
        // Invariáns: összeg nem lehet negatív
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Az összeg pozitív kell legyen: " + amount);
        }
        this.transactionId = transactionId;
        this.amount = amount;
        this.currency = currency;
        this.status = TransactionStatus.PENDING;
    }

    // Állapotváltás üzleti szabályokkal védve
    public void approve() {
        if (this.status != TransactionStatus.PENDING) {
            throw new IllegalStateException(
                "Csak PENDING tranzakció hagyható jóvá, jelenlegi: " + this.status
            );
        }
        this.status = TransactionStatus.APPROVED;
    }

    // Getter — az összeg olvasható, de nem írható felül közvetlenül
    public BigDecimal getAmount() {
        return amount;
    }

    // Nincs setAmount()! Az összeg módosítás üzleti műveleten keresztül történik.
    public void applyDiscount(BigDecimal percentage) {
        if (this.status != TransactionStatus.PENDING) {
            throw new IllegalStateException("Kedvezmény csak PENDING státuszban adható");
        }
        this.amount = this.amount.multiply(BigDecimal.ONE.subtract(percentage));
    }
}

Lényeg: Ne csak getter/setter-eket írj — gondolkodj azon, hogy milyen üzleti műveleteken keresztül érhető el az állapotváltozás.


2.2 Inheritance (Öröklődés)

Az inheritance lehetővé teszi, hogy egy osztály átvegye egy másik osztály mezőit és metódusait. Java-ban extends kulcsszóval jelöljük — egyszeres öröklődés van (egy osztálynak max egy szülőosztálya lehet).

IS-A reláció

Az öröklődés IS-A (egy fajta) kapcsolatot fejez ki:

Példák IS-A kapcsolatra:

  • EmailNotificationSender egy NotificationSender
  • SmsNotificationSender egy NotificationSender
  • PushNotificationSender egy NotificationSender

Method overriding

A leszármazott felülírhatja a szülő metódusait, ha:

  • Ugyanaz a metódus-szignatúra (név + paraméterek)
  • A visszatérési típus azonos vagy szűkebb (covariant return)
  • Az access modifier azonos vagy tágabb (nem szűkíthető)
  • A @Override annotáció nem kötelező, de erősen ajánlott — fordítási hibát kapsz, ha elírod a nevet
// Közös logika a szülőosztályban
public abstract class NotificationSender {

    private final AuditLogger auditLogger;

    protected NotificationSender(AuditLogger auditLogger) {
        this.auditLogger = auditLogger;
    }

    // Template Method pattern — a közös flow itt van, a specifikus részt a leszármazott adja
    public final void send(Notification notification) {
        validate(notification);
        doSend(notification);                     // absztrakt — leszármazott implementálja
        auditLogger.log(notification, channel()); // közös audit log
    }

    protected void validate(Notification notification) {
        if (notification.getRecipient() == null || notification.getRecipient().isBlank()) {
            throw new IllegalArgumentException("A címzett nem lehet üres");
        }
    }

    // A leszármazottak ezt implementálják
    protected abstract void doSend(Notification notification);
    protected abstract String channel();
}

public class EmailNotificationSender extends NotificationSender {

    private final EmailGateway emailGateway;

    public EmailNotificationSender(AuditLogger auditLogger, EmailGateway emailGateway) {
        super(auditLogger);
        this.emailGateway = emailGateway;
    }

    @Override
    protected void doSend(Notification notification) {
        emailGateway.sendEmail(
            notification.getRecipient(),
            notification.getSubject(),
            notification.getBody()
        );
    }

    @Override
    protected String channel() {
        return "EMAIL";
    }
}

Figyelem: Az öröklődés szoros csatolást (tight coupling) hoz létre a szülő és a leszármazott között. Használd megfontoltan — → lásd: 7. Senior-szintű meglátások.


2.3 Polymorphism (Polimorfizmus)

A polimorfizmus azt jelenti, hogy ugyanaz az interfész különböző implementációkat takarhat. Java-ban két formája van:

Compile-time (statikus) polimorfizmus — Method Overloading

A fordító a paraméterek típusa és száma alapján dönti el, melyik metódus hívódik meg:

public class RiskScoreCalculator {

    // Alapértelmezett kockázat számítás
    public RiskScore calculate(Transaction transaction) {
        return evaluate(transaction, RiskProfile.DEFAULT);
    }

    // Profil-alapú kockázat számítás — overloaded verzió
    public RiskScore calculate(Transaction transaction, RiskProfile profile) {
        return evaluate(transaction, profile);
    }

    // Batch kockázat számítás — újabb overload
    public List<RiskScore> calculate(List<Transaction> transactions) {
        return transactions.stream()
            .map(this::calculate)
            .toList();
    }

    private RiskScore evaluate(Transaction tx, RiskProfile profile) {
        // ... kockázat-értékelési logika
        return new RiskScore(tx.getId(), computeScore(tx, profile));
    }
}

Runtime (dinamikus) polimorfizmus — Method Overriding

A JVM futásidőben dönti el, hogy melyik implementáció hívódik meg (virtual method dispatch):

// Interfész — a szerződés
public interface PaymentProcessor {
    PaymentResult process(PaymentRequest request);
    boolean supports(PaymentMethod method);
}

// Implementáció: kártyás fizetés
public class CardPaymentProcessor implements PaymentProcessor {

    @Override
    public PaymentResult process(PaymentRequest request) {
        // Kártyaadatok validálása, tokenizálás, acquirer hívás
        CardDetails card = request.getCardDetails();
        AuthResponse auth = acquirerGateway.authorize(card, request.getAmount());
        return new PaymentResult(auth.isSuccessful(), auth.getAuthCode());
    }

    @Override
    public boolean supports(PaymentMethod method) {
        return method == PaymentMethod.CREDIT_CARD || method == PaymentMethod.DEBIT_CARD;
    }
}

// Implementáció: banki átutalás
public class BankTransferProcessor implements PaymentProcessor {

    @Override
    public PaymentResult process(PaymentRequest request) {
        // IBAN validálás, SEPA/ACH hívás
        BankAccount account = request.getBankAccount();
        TransferResult result = bankingGateway.initiateTransfer(account, request.getAmount());
        return new PaymentResult(result.isAccepted(), result.getReferenceId());
    }

    @Override
    public boolean supports(PaymentMethod method) {
        return method == PaymentMethod.BANK_TRANSFER;
    }
}

// Használat — a hívó kód nem tudja (és nem is érdekli), melyik implementáció fut
public class PaymentService {

    private final List<PaymentProcessor> processors;

    public PaymentService(List<PaymentProcessor> processors) {
        this.processors = processors;
    }

    public PaymentResult processPayment(PaymentRequest request) {
        // Futásidőben dől el, melyik processor fut
        return processors.stream()
            .filter(p -> p.supports(request.getMethod()))
            .findFirst()
            .orElseThrow(() -> new UnsupportedPaymentMethodException(request.getMethod()))
            .process(request);
    }
}

2.4 Abstraction (Absztrakció)

Az abstraction a lényeges részletek kiemelése és a nem lényeges részletek elrejtése. Java-ban két eszközzel valósítjuk meg:

Abstract class vs Interface

Szempont abstract class interface
Példányosítás ❌ Nem ❌ Nem
Konstruktor ✅ Van ❌ Nincs
Állapot (mezők) ✅ Instance fields ⚠️ Csak static final
Többszörös öröklés ❌ Egy szülő ✅ Több interface
default metódus ✅ Java 8+
Mikor használd Közös állapot + részleges implementáció Szerződés, capability

Mikor melyiket?

  • Abstract class → ha van közös állapot vagy közös logika, amit a leszármazottak megosztanak (pl. NotificationSender fent — van közös auditLogger)
  • Interface → ha viselkedési szerződést adsz meg, amit bármilyen osztály megvalósíthat (pl. PaymentProcessor — kártya és banki átutalás teljesen különböző osztályok)
// Interface — tisztán viselkedési szerződés
public interface RiskEvaluator {
    RiskLevel evaluate(Transaction transaction);
    String evaluatorName();
}

// Abstract class — közös logika és állapot
public abstract class AbstractRiskEvaluator implements RiskEvaluator {

    private final RiskThresholdConfig config;
    private final MetricsCollector metrics;

    protected AbstractRiskEvaluator(RiskThresholdConfig config, MetricsCollector metrics) {
        this.config = config;
        this.metrics = metrics;
    }

    @Override
    public final RiskLevel evaluate(Transaction transaction) {
        long start = System.nanoTime();
        try {
            double score = computeScore(transaction);   // leszármazott logikája
            return mapToLevel(score);                    // közös mapping
        } finally {
            metrics.record(evaluatorName(), System.nanoTime() - start);
        }
    }

    // Leszármazottak saját scoring logikát adnak
    protected abstract double computeScore(Transaction transaction);

    // Közös mapping — a score-ból RiskLevel-t csinálunk a config alapján
    private RiskLevel mapToLevel(double score) {
        if (score >= config.getHighThreshold()) return RiskLevel.HIGH;
        if (score >= config.getMediumThreshold()) return RiskLevel.MEDIUM;
        return RiskLevel.LOW;
    }

    protected RiskThresholdConfig getConfig() {
        return config;
    }
}

// Konkrét implementáció — sebességalapú kockázat
public class VelocityRiskEvaluator extends AbstractRiskEvaluator {

    private final TransactionHistoryRepository historyRepo;

    public VelocityRiskEvaluator(RiskThresholdConfig config, MetricsCollector metrics,
                                  TransactionHistoryRepository historyRepo) {
        super(config, metrics);
        this.historyRepo = historyRepo;
    }

    @Override
    protected double computeScore(Transaction transaction) {
        // Az elmúlt 24 óra tranzakcióinak száma alapján
        int recentCount = historyRepo.countRecent(transaction.getUserId(), Duration.ofHours(24));
        int limit = getConfig().getVelocityLimit();
        return Math.min((double) recentCount / limit, 1.0);
    }

    @Override
    public String evaluatorName() {
        return "VELOCITY";
    }
}

3. Gyakorlati használat

Mikor használd

Pillér Használati eset
Encapsulation Mindig, amikor üzleti invariánsokat kell védeni (pl. tranzakció összege ne lehessen negatív)
Inheritance Template Method pattern, közös állapot + részleges implementáció, framework hook-ok
Polymorphism Strategy pattern, plugin-architektúra, tesztelhetőség (mock-ok)
Abstraction API tervezés, modul-határok definiálása, dependency inversion

Mikor NE használd

Anti-pattern Probléma
Getter/Setter mindenhol Az encapsulation illúzióját kelti — az objektum valójában egy „nyílt adat-struktúra"
Mély öröklődési hierarchia (3+ szint) Fragile Base Class problem — a szülő változtatása megtöri a leszármazottakat
Marker interface állapottal Ha állapotot akarsz megosztani, az interface nem a megfelelő eszköz
Polimorfizmus egyetlen implementációval Over-engineering — ha tudom, hogy csak egyféle payment processor lesz, ne csináljak belőle interface-t
Abstract class egy metódussal, állapot nélkül Használj interface-t helyette — egyszerűbb és rugalmasabb

4. Kód példák

4.1 Encapsulation — Immutable value object

/**
 * Immutable pénzösszeg — teljesen encapsulált.
 * Egyszer létrehozva nem módosítható, szálbiztos.
 */
public final class Money {

    private final BigDecimal amount;
    private final Currency currency;

    public Money(BigDecimal amount, Currency currency) {
        Objects.requireNonNull(amount, "Az összeg nem lehet null");
        Objects.requireNonNull(currency, "A pénznem nem lehet null");
        // Két tizedesjegy, bankári kerekítéssel
        this.amount = amount.setScale(2, RoundingMode.HALF_EVEN);
        this.currency = currency;
    }

    // Üzleti művelet — új objektumot ad vissza (immutable)
    public Money add(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new CurrencyMismatchException(this.currency, other.currency);
        }
        return new Money(this.amount.add(other.amount), this.currency);
    }

    public Money multiply(int quantity) {
        return new Money(this.amount.multiply(BigDecimal.valueOf(quantity)), this.currency);
    }

    public boolean isGreaterThan(Money other) {
        ensureSameCurrency(other);
        return this.amount.compareTo(other.amount) > 0;
    }

    private void ensureSameCurrency(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new CurrencyMismatchException(this.currency, other.currency);
        }
    }

    // Csak olvasás — nincs setter
    public BigDecimal getAmount() { return amount; }
    public Currency getCurrency() { return currency; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Money money)) return false;
        return amount.compareTo(money.amount) == 0 && currency.equals(money.currency);
    }

    @Override
    public int hashCode() {
        return Objects.hash(amount.stripTrailingZeros(), currency);
    }
}

4.2 Inheritance — Template Method a notification küldéshez

→ Lásd a 2.2 Inheritance szekció NotificationSender példáját.

4.3 Polymorphism — Strategy pattern kockázat-értékeléshez

// Több evaluator — mindegyik más szempontot vizsgál
public class CompositeRiskEvaluator implements RiskEvaluator {

    private final List<RiskEvaluator> evaluators;

    public CompositeRiskEvaluator(List<RiskEvaluator> evaluators) {
        this.evaluators = List.copyOf(evaluators); // defensive copy
    }

    @Override
    public RiskLevel evaluate(Transaction transaction) {
        // A legmagasabb kockázati szintet adjuk vissza
        return evaluators.stream()
            .map(e -> e.evaluate(transaction))
            .max(Comparator.comparingInt(RiskLevel::severity))
            .orElse(RiskLevel.LOW);
    }

    @Override
    public String evaluatorName() {
        return "COMPOSITE";
    }
}

4.4 Gyakori hiba — ❌ / ✅

// ❌ ROSSZ: Az encapsulation megsértése — publikus mutable mező
public class PaymentOrder {
    public List<LineItem> items = new ArrayList<>();  // bárki módosíthatja!
    public BigDecimal totalAmount;                     // felülírható közvetlenül!
}

// Használat — az invariáns könnyen megsérül:
PaymentOrder order = new PaymentOrder();
order.items.add(new LineItem("Widget", new BigDecimal("10.00")));
order.totalAmount = new BigDecimal("-999.99");  // ⚠️ Negatív összeg!
// ✅ HELYES: Üzleti műveleteken keresztül módosítható, invariánsok védettek
public class PaymentOrder {

    private final List<LineItem> items = new ArrayList<>();
    private BigDecimal totalAmount = BigDecimal.ZERO;

    public void addItem(LineItem item) {
        Objects.requireNonNull(item, "A tétel nem lehet null");
        if (item.getPrice().compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("A tétel ára pozitív kell legyen");
        }
        this.items.add(item);
        this.totalAmount = this.totalAmount.add(item.getPrice());
    }

    // Defensive copy — a hívó nem módosíthatja a belső listát
    public List<LineItem> getItems() {
        return Collections.unmodifiableList(items);
    }

    public BigDecimal getTotalAmount() {
        return totalAmount;
    }
}

5. Trade-offok

Szempont Részletek
Performance A virtual method dispatch (polimorfizmus) minimális overhead-del jár — a JIT compiler inline-olja a hot path-okat. Az abstraction layerek viszont cache miss-t okozhatnak, ha túl sok indirection van.
💾 Memory Minden objektumnak van 12-16 byte header-je (mark word + class pointer). A mély öröklődési hierarchia növeli az objektum méretét, mert minden szülő mezői is benne vannak.
🔧 Maintainability Az OOP jól strukturált kódot eredményez, ha megfelelően alkalmazzák. Túlzott öröklődés és túl sok abstract layer viszont rontja az olvashatóságot — „trace hell", ahol 8 osztályon keresztül kell követni egy hívást.
🔄 Flexibility Az interface-alapú tervezés (programming to interfaces) kiváló rugalmasságot ad — új implementáció hozzáadása nem igényel meglévő kód módosítást (Open/Closed Principle). Az öröklődés ezzel szemben szoros csatolást hoz létre.

6. Gyakori hibák

6.1 — Anemic Domain Model

// ❌ Az objektum csak adat-tartó, a logika egy "service"-ben van
public class Transaction {
    private String id;
    private BigDecimal amount;
    private TransactionStatus status;

    // Getter + Setter mindenhol...
    public void setStatus(TransactionStatus status) { this.status = status; }
    public void setAmount(BigDecimal amount) { this.amount = amount; }
}

public class TransactionService {
    public void approve(Transaction tx) {
        if (tx.getStatus() == TransactionStatus.PENDING) {
            tx.setStatus(TransactionStatus.APPROVED);  // kívülről piszkáljuk az állapotot
        }
    }
}
// ✅ Rich Domain Model — az objektum felelős a saját állapotáért
public class Transaction {
    private final String id;
    private BigDecimal amount;
    private TransactionStatus status;

    public void approve() {
        if (this.status != TransactionStatus.PENDING) {
            throw new IllegalStateException("Csak PENDING tranzakció hagyható jóvá");
        }
        this.status = TransactionStatus.APPROVED;
    }

    // Nincs setStatus()! Az állapotváltás üzleti műveleten keresztül történik.
}

6.2 — Inheritance a kód-újrafelhasználáshoz (HAS-A helyett IS-A)

// ❌ ROSSZ: A PaymentLogger nem "egy fajta" ArrayList!
public class PaymentLogger extends ArrayList<String> {
    public void logPayment(String message) {
        this.add("[PAYMENT] " + message);
    }
}
// Probléma: a PaymentLogger örökli az ArrayList ÖSSZES publikus metódusát
// — clear(), remove(), set() — amik nem relevánsak egy logger-nek!
// ✅ HELYES: Composition — a logger TARTALMAZ egy listát, nem KITERJESZTI
public class PaymentLogger {
    private final List<String> logs = new ArrayList<>();

    public void logPayment(String message) {
        logs.add("[PAYMENT] " + message);
    }

    // Csak azt tesszük elérhetővé, amire tényleg szükség van
    public List<String> getRecentLogs(int count) {
        int start = Math.max(0, logs.size() - count);
        return Collections.unmodifiableList(logs.subList(start, logs.size()));
    }
}

6.3 — Leaky Abstraction (belső implementáció kiszivárgása)

// ❌ A belső lista referencia kiszivárog
public class RiskAssessmentResult {
    private final List<RiskFactor> factors;

    public RiskAssessmentResult(List<RiskFactor> factors) {
        this.factors = factors;  // ⚠️ Nem defensive copy!
    }

    public List<RiskFactor> getFactors() {
        return factors;  // ⚠️ Mutable referencia — a hívó módosíthatja!
    }
}

// A hívó véletlenül (vagy szándékosan) módosítja a belső állapotot:
List<RiskFactor> factors = result.getFactors();
factors.clear();  // 💥 Az eredeti objektum belső állapota elromlott!
// ✅ Defensive copy + unmodifiable view
public class RiskAssessmentResult {
    private final List<RiskFactor> factors;

    public RiskAssessmentResult(List<RiskFactor> factors) {
        this.factors = List.copyOf(factors);  // Defensive copy a konstruktorban
    }

    public List<RiskFactor> getFactors() {
        return factors;  // List.copyOf() már unmodifiable-t ad vissza
    }
}

6.4 — @Override annotáció hiánya

// ❌ Elírt metódusnév — override helyett új metódus jön létre
public class CustomRiskEvaluator extends AbstractRiskEvaluator {

    // Nem komputScore, hanem computeScore kellene!
    // @Override nélkül a fordító nem szól
    protected double komputScore(Transaction transaction) {
        return 0.5;
    }
    // Eredmény: az AbstractRiskEvaluator absztrakt computeScore()-ja fut
    // — vagy ha az is abstract, fordítási hiba, de ha default implementáció van, csendben rossz
}
// ✅ Az @Override annotáció fordítási hibát ad, ha a metódus nem létezik a szülőben
public class CustomRiskEvaluator extends AbstractRiskEvaluator {

    @Override  // ← A fordító ellenőrzi, hogy tényleg felülírás-e
    protected double computeScore(Transaction transaction) {
        return 0.5;
    }
}

7. Senior-szintű meglátások

Composition over Inheritance

A Gang of Four (GoF) könyv óta ismert elv: részesítsd előnyben a kompozíciót az öröklődés helyett. Az öröklődés a legerősebb csatolási forma Java-ban — ha a szülő változik, minden leszármazott potenciálisan érintett.

// ❌ Öröklődés alapú megközelítés — merev, nehezen bővíthető
public class FraudCheckingPaymentService extends PaymentService {
    @Override
    public PaymentResult process(PaymentRequest request) {
        fraudChecker.check(request);
        return super.process(request);
    }
}
// Mi van, ha rate limiting-et is akarok? Újabb extends?
// FraudCheckingRateLimitedPaymentService extends FraudCheckingPaymentService? 🤮
// ✅ Composition + Decorator pattern — rugalmasan bővíthető
public class FraudCheckingPaymentProcessor implements PaymentProcessor {

    private final PaymentProcessor delegate;
    private final FraudChecker fraudChecker;

    public FraudCheckingPaymentProcessor(PaymentProcessor delegate, FraudChecker fraudChecker) {
        this.delegate = delegate;
        this.fraudChecker = fraudChecker;
    }

    @Override
    public PaymentResult process(PaymentRequest request) {
        fraudChecker.check(request);       // előfeltétel
        return delegate.process(request);  // delegálás
    }

    @Override
    public boolean supports(PaymentMethod method) {
        return delegate.supports(method);
    }
}
// Rate limiting? Csinálj egy RateLimitingPaymentProcessor-t, ami szintén delegate-el.
// Tetszőlegesen kombinálhatók, egymásba ágyazhatók.

SOLID kapcsolat

Az OOP pillérei önmagukban nem elegek — a SOLID elvek adják meg, hogyan alkalmazd őket helyesen:

SOLID Kapcsolat az OOP-hez
S — Single Responsibility Egy osztály egy okból változzon → encapsulation fókuszálása
O — Open/Closed Bővíthető, de nem módosítandó → polimorfizmus + abstraction
L — Liskov Substitution A leszármazott helyettesíthesse a szülőt → helyes inheritance
I — Interface Segregation Kis, fókuszált interface-ek → jobb abstraction
D — Dependency Inversion Absztrakciókra építs, ne konkrét implementációkra → interface-ek használata

Over-abstraction veszélyei

Kódbázis mérete:     ~5000 sor
Absztrakt rétegek:   8 (Controller → Service → Facade → Strategy → Handler → Processor → Adapter → Gateway)
Egy request trace:    15 osztály, 3 interface
Új fejlesztő onboarding ideje: 3 hét 😱

Szabály: Kérdezd meg magadtól — „Ha ezt holnap kellene módosítanom, hány fájlt kell megnyitnom, hogy megértsem a flow-t?" Ha a válasz 6+, valószínűleg túl sok az absztrakció.

Sealed classes (Java 17+)

A modern Java sealed kulcsszóval korlátozhatja az öröklődést — csak az engedélyezett leszármazottak létezhetnek:

// Pontosan három féle notification-outcome lehet, más nem
public sealed interface NotificationOutcome
    permits Delivered, Failed, Retrying {
}

public record Delivered(Instant timestamp) implements NotificationOutcome {}
public record Failed(String reason, Instant timestamp) implements NotificationOutcome {}
public record Retrying(int attempt, Instant nextRetry) implements NotificationOutcome {}

// Pattern matching — a fordító ellenőrzi, hogy minden esetet kezelünk
public String describe(NotificationOutcome outcome) {
    return switch (outcome) {
        case Delivered d   -> "Kézbesítve: " + d.timestamp();
        case Failed f      -> "Sikertelen: " + f.reason();
        case Retrying r    -> "Újrapróbálkozás #" + r.attempt();
        // Nincs szükség default ágra — a sealed biztosítja a teljességet
    };
}

Interview tipp

Kérdés: „Mikor használnál abstract class-t és mikor interface-t?"

Jó válasz: „Ha van közös állapot vagy implementáció, amit a leszármazottak megosztanak — abstract class. Ha tisztán viselkedési szerződésről van szó, amit különböző, nem rokon osztályok is megvalósíthatnak — interface. Java 8 óta az interface-eknek is lehet default metódusuk, ami összezavarja a határt — de a döntő különbség, hogy interface-nek nincs állapota (instance field)."


8. Szószedet

Fogalom Definíció
Virtual method dispatch A JVM mechanizmusa, amellyel futásidőben dönt el, melyik override-olt metódus hívódik meg — a vtable (virtual method table) alapján
Covariant return type A felülírt metódus visszatérési típusa lehet szűkebb (specifikusabb) az eredetinél (Java 5+)
Fragile Base Class Probléma, amikor a szülőosztály módosítása váratlanul megtöri a leszármazottakat
Tight coupling Szoros csatolás — két komponens annyira függ egymástól, hogy az egyik változása maga után vonja a másik módosítását
Defensive copy Másolat készítése bejövő/kimenő mutable objektumokról, hogy megvédjük a belső állapotot
Anemic Domain Model Anti-pattern, ahol az objektumok csak adatot tárolnak, az üzleti logika „service" osztályokban van
Diamond problem Többszörös öröklődésnél felmerülő ambiguitás — Java az interface default metódusokkal kezeli
Marker interface Üres interface (pl. Serializable), ami jelölésre szolgál, nem viselkedés definiálására

9. Gyorsreferencia

  1. Encapsulation = adatok + műveletek egy egységben, access modifiers-szel korlátozva
  2. Inheritance = IS-A reláció, extends kulcsszó, egyszeres öröklődés Java-ban
  3. Polymorphism = compile-time (overloading) + runtime (overriding, virtual dispatch)
  4. Abstraction = abstract class (állapot + részleges impl.) vs interface (szerződés)
  5. Ne írj getter/setter-t automatikusan — gondolkodj üzleti műveletekben
  6. Részesítsd előnyben a composition-t az inheritance helyett
  7. Használj @Override annotációt — mindig
  8. Interface-t használj a modul-határokon, abstract class-t a közös implementációhoz
  9. Ha 3+ szint mély az öröklődési hierarchia, refaktorálj composition irányba
  10. A sealed (Java 17+) az öröklődés kontrollált formája — exhaustive pattern matching-gel

🎮 Játékok

10 kérdés