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:
EmailNotificationSenderegyNotificationSenderSmsNotificationSenderegyNotificationSenderPushNotificationSenderegyNotificationSender
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
@Overrideannotá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.
NotificationSenderfent — van közösauditLogger) - 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
- Encapsulation = adatok + műveletek egy egységben, access modifiers-szel korlátozva
- Inheritance = IS-A reláció,
extendskulcsszó, egyszeres öröklődés Java-ban - Polymorphism = compile-time (overloading) + runtime (overriding, virtual dispatch)
- Abstraction =
abstract class(állapot + részleges impl.) vsinterface(szerződés) - Ne írj getter/setter-t automatikusan — gondolkodj üzleti műveletekben
- Részesítsd előnyben a composition-t az inheritance helyett
- Használj
@Overrideannotációt — mindig - Interface-t használj a modul-határokon, abstract class-t a közös implementációhoz
- Ha 3+ szint mély az öröklődési hierarchia, refaktorálj composition irányba
- A
sealed(Java 17+) az öröklődés kontrollált formája — exhaustive pattern matching-gel
🎮 Játékok
10 kérdés