Alapelvek
Egységbezárás, öröklődés, polimorfizmus és absztrakció
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. Mélymerülés.
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. Mélymerülés
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. Interjúkérdések
Hogyan különbözteted meg röviden a kapszulázást és az absztrakciót?
A kapszulázás a belső állapot védelméről szól, az absztrakció pedig arról, hogy a hívó csak a szükséges fogalmakkal és műveletekkel találkozzon.
Miért biztonságosabb sokszor a kompozíció, mint az öröklődés?
Mert lazább csatolást ad. Az öröklődésnél a leszármazott erősen függ a szülő viselkedésétől és fejlődésétől, míg a kompozícióban a viselkedés könnyebben cserélhető vagy kombinálható.
Mikor jobb választás az abstract class, mint az interface?
Ha több szorosan kapcsolódó osztály közös állapotot vagy közös implementációt használ. Ha csak szerződés kell különböző típusok között, általában az interface a jobb választás.
Mi a jó interjúválasz az OOP négy pillérére?
Rövid definíció, Java-nyelvi példa és legalább egy trade-off. Az ítélőképesség fontosabb, mint a bemagolt definíció.
9. 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 |
10. 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