Regel
Komposition gegenüber Vererbung bevorzugen
Tiefe Vererbungshierarchien erzeugen eine starke Kopplung
und erschweren das Verständnis und die Wartung des Codes.
Unterstützte Sprachen: 45+Einleitung
Vererbung erzeugt eine starke Kopplung zwischen Eltern- und Kindklassen, was den Code fragil und schwer änderbar macht. Wenn eine Klasse Verhalten erbt, wird sie von den Implementierungsdetails ihrer Elternklasse abhängig. Unterklassen, die Methoden überschreiben, aber immer noch aufrufen super sind besonders problematisch, da sie ihre eigene Logik mit geerbtem Verhalten auf eine Weise vermischen, die bei Änderungen des übergeordneten Elements zu Fehlern führt. Komposition löst dies, indem Objekte an andere Objekte delegieren, wodurch eine lose Kopplung und eine klare Trennung der Belange entsteht.
Warum es wichtig ist
Vermischte Anliegen und enge Kopplung: Vererbung zwingt unabhängige Belange in dieselbe Klassenhierarchie. Eine Klasse für wiederkehrende Zahlungen, die von einem Zahlungsabwickler erbt, vermischt Planungslogik mit Zahlungsabwicklung. Wenn Sie aufrufen müssen super.process() und dann Ihr eigenes Verhalten hinzufügen, sind Sie eng an die Implementierung des übergeordneten Elements gekoppelt. Wenn das übergeordnete Element process() Methodenänderungen führen dazu, dass die Kindklasse auf unerwartete Weise bricht.
Unerwünschtes Verhalten erben: Unterklassen erben alles von ihren Eltern, einschließlich Methoden, die sie nicht benötigen oder die andere Implementierungen erfordern. Eine wiederkehrende Zahlung erbt refund() Logik, die für einmalige Zahlungen konzipiert wurde, aber Abonnement-Rückerstattungen funktionieren anders. Sie überschreiben entweder Methoden und stiften Verwirrung, oder Sie leben mit unangemessenem geerbten Verhalten.
Fragiles Basisklassenproblem: Änderungen an Elternklassen wirken sich auf alle Unterklassen aus. Die Änderung der Art und Weise, wie Kreditkartenzahlung betrifft Zahlungsabwicklungen Wiederkehrende Kreditkartenzahlung obwohl die Änderung für die Zeitplanung irrelevant ist. Dies macht Refactoring gefährlich, da Sie nicht vorhersagen können, welche Unterklassen brechen werden.
Testkomplexität: Das Testen von Klassen tief in einer Vererbungshierarchie erfordert das Verständnis des Verhaltens der übergeordneten Klasse. Um die Planung wiederkehrender Zahlungen zu testen, müssen Sie sich auch mit der Logik der Kreditkartenverarbeitung, Stripe-API-Aufrufen und der Validierung befassen. Komposition ermöglicht es Ihnen, die Planung mit einem einfachen Mock-Zahlungsobjekt zu testen.
Code-Beispiele
❌ Nicht konform:
class Payment {
constructor(amount, currency) {
this.amount = amount;
this.currency = currency;
}
async process() {
throw new Error('Must implement in subclass');
}
async refund() {
throw new Error('Must implement in subclass');
}
async sendReceipt(email) {
// All paymet types need receipts
await emailService.send(email, this.buildReceipt());
}
}
class CreditCardPayment extends Payment {
constructor(amount, currency, cardToken, billingAddress) {
super(amount, currency);
this.cardToken = cardToken;
this.billingAddress = billingAddress;
}
async process() {
await this.validateCard();
return await stripe.charges.create({
amount: this.amount * 100,
source: this.cardToken,
currency: this.currency
});
}
async refund() {
await this.validateRefund();
return await stripe.refunds.create({ charge: this.chargeId });
}
async validateCard() {
// Card validation logic
}
}
// Problem: RecurringCreditCardPayment's main concern is dealing with scheduling
// and not the actual payment
class RecurringCreditCardPayment extends CreditCardPayment {
constructor(amount, currency, cardToken, billingAddress, schedule) {
super(amount, currency, cardToken, billingAddress);
this.schedule = schedule;
}
async process() {
// Problem: Need to override parent's process() but also use it
await super.process();
await this.scheduleNextPayment();
}
async scheduleNextPayment() {
// Subscription scheduling
}
// Problem: Inherits refund() from parent but refunding
// subscriptions needs different logic
}Warum es falsch ist: Wiederkehrende Kreditkartenzahlung erbt die Zahlungsabwicklungslogik, aber ihr eigentliches Anliegen ist die Terminplanung, nicht die Zahlungen. Sie muss aufrufen super.process() und es mit Planungsverhalten umwickeln, wodurch eine enge Kopplung entsteht. Die Klasse erbt refund() vom übergeordneten Element, aber die Rückerstattung von Abonnements erfordert eine andere Logik als einmalige Zahlungen. Änderungen an Kreditkartenzahlung betreffen Wiederkehrende Kreditkartenzahlung selbst wenn diese Änderungen für die Zeitplanung irrelevant sind.
✅ Konform:
class CreditCardPayment extends Payment {
constructor(amount, currency, cardToken, billingAddress) {
super(amount, currency);
this.cardToken = cardToken;
this.billingAddress = billingAddress;
}
async process() {
await this.validateCard();
return await stripe.charges.create({
amount: this.amount * 100,
source: this.cardToken,
currency: this.currency
});
}
async refund() {
await this.validateRefund();
return await stripe.refunds.create({ charge: this.chargeId });
}
async validateCard() {
// Card validation logic
}
}
class RecurringCreditCardPayment {
constructor(creditCardPayment, schedule) {
this.creditCardPayment = creditCardPayment;
this.schedule = schedule;
}
async scheduleNextPayment() {
this.schedule.onNextCyle(() => {
await this.creditCardPayment.process();
})
}
}
const recurringCreditCardPayment = new RecurringCreditCardPayment(
new CreditCardPayment(),
new Schedule(),
);Warum dies wichtig ist: Wiederkehrende Kreditkartenzahlung konzentriert sich ausschließlich auf die Terminplanung und delegiert die Zahlungsabwicklung an die komponierte Einheit Kreditkartenzahlung Instanz. Keine Vererbung bedeutet keine enge Kopplung an die Implementierung der Elternklasse. Änderungen an der Kreditkartenverarbeitung beeinflussen die Planungslogik nicht. Die Zahlungsinstanz kann durch jede Zahlungsmethode ersetzt werden, ohne den Planungscode zu ändern.
Fazit
Nutzen Sie Komposition, um Belange zu trennen, anstatt sie durch Vererbung zu vermischen. Wenn eine Klasse die Funktionalität einer anderen Klasse benötigt, akzeptieren Sie diese als Abhängigkeit und delegieren Sie an sie, anstatt von ihr zu erben. Dies führt zu einer losen Kopplung, erleichtert das Testen und verhindert, dass Änderungen in einer Klasse eine andere beeinträchtigen.
.avif)
