Aikido

Bakımı kolay ve esnek bir kod için miras yerine bileşimi nasıl tercih etmeliyiz?

Bakım Kolaylığı

Kural
Tercih kompozisyon kullan kalıtım
Derin kalıtım hiyerarşiler oluştur sıkı bağlantı
ve sistemleri kod zorlaştırmak anlaşılması anlaşılması ve bakımı

Desteklenen diller: 45+

Giriş

Kalıtım, üst sınıf ile alt sınıf arasında sıkı bir bağ oluşturur; bu da kodu kırılgan hale getirir ve değiştirilmesini zorlaştırır. Bir sınıf davranışını miras aldığında, üst sınıfının uygulama ayrıntılarına bağımlı hale gelir. Yöntemleri geçersiz kılan ancak yine de süper özellikle sorunlu olup, kendi mantıklarını miras alınan davranışlarla öyle bir şekilde harmanlarlar ki, ebeveyn nesne değiştiğinde bu yapı bozulur. Bileşim, nesnelerin diğer nesnelere görev devretmesine olanak tanıyarak bu sorunu çözer; böylece gevşek bağlanma ve görevlerin net bir şekilde ayrılması sağlanır.

Neden önemli?

Karışık endişeler ve sıkı bağlantı: Kalıtım, birbiriyle ilgisi olmayan unsurları aynı sınıf hiyerarşisine dahil eder. Bir ödeme işlemcisinden miras alan bir tekrarlayan ödeme sınıfı, zamanlama mantığını ödeme işlemeyle karıştırır. Şunu çağırmanız gerektiğinde super.process() ve ardından kendi davranışınızı eklediğinizde, üst sınıfın uygulamasıyla sıkı bir şekilde bağlanmış olursunuz. Eğer üst sınıfın process() Yöntemlerde değişiklik yapıldığında, alt sınıf beklenmedik şekillerde hataya düşer.

İstenmeyen davranışların devralınması: Alt sınıflar, ihtiyaç duymadıkları veya farklı uygulamalar gerektiren yöntemler de dahil olmak üzere, üst sınıflarından her şeyi devralır. Tekrarlayan bir ödeme, şunları devralır: refund() Bu mantık tek seferlik ödemeler için tasarlanmıştır, ancak abonelik iadeleri farklı şekilde işler. Ya yöntemleri geçersiz kılar ve kafa karışıklığına yol açarsınız, ya da uygun olmayan miras alınan davranışla yetinirsiniz.

Hassas temel sınıf sorunu: Ana sınıflarda yapılan değişiklikler tüm alt sınıflara yansır. Şu şekilde değiştirme Kredi Kartı Ödemesi ödemelerin işlenmesi, şunları etkiler: Tekrarlayan Kredi Kartı Ödemesi Değişiklik, zamanlama ile ilgisiz olsa bile. Bu durum, yeniden yapılandırmayı riskli hale getirir; çünkü hangi alt sınıfların çalışmaz hale geleceğini öngöremezsiniz.

Test karmaşıklığı: Kalıtım hiyerarşisinin derinliklerinde yer alan sınıfları test etmek, üst sınıfın davranışını anlamayı gerektirir. Tekrarlayan ödeme planlamasını test etmek için, kredi kartı işleme mantığı, Stripe API çağrıları ve doğrulama işlemleriyle de ilgilenmeniz gerekir. Bileşim yaklaşımı, basit bir sahte ödeme nesnesi kullanarak planlamayı test etmenize olanak tanır.

Kod örnekleri

❌ Uygun değil:

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
}

Neden yanlış: Tekrarlayan Kredi Kartı Ödemesi Ödeme işleme mantığını devralır, ancak asıl odak noktası ödemeler değil, zamanlamadır. Şunu çağırmalıdır: super.process() ve bunu zamanlama davranışıyla sararak sıkı bir bağ oluşturur. Sınıf, refund() ana hesap üzerinden yapılır, ancak aboneliklerin iadesi tek seferlik ödemelerden farklı bir mantık gerektirir. Şu değişiklikler: Kredi Kartı Ödemesi etkilemek Tekrarlayan Kredi Kartı Ödemesi bu değişiklikler zamanlamayla ilgisiz olsa bile.

✅ Uygunluk:

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(),
);

Bunun önemi: Tekrarlayan Kredi Kartı Ödemesi yalnızca planlamaya odaklanır ve ödeme işlemlerini oluşturulan birime devreder Kredi Kartı Ödemesi örneği. Kalıtım olmaması, üst sınıfın uygulamasına sıkı bir bağlanma olmadığı anlamına gelir. Kredi kartı işlemlerinde yapılan değişiklikler, zamanlama mantığını etkilemez. Zamanlama kodunda herhangi bir değişiklik yapılmasına gerek kalmadan, ödeme örneği herhangi bir ödeme yöntemiyle değiştirilebilir.

Sonuç

Kalıtım yoluyla konuları birbirine karıştırmak yerine, kompozisyonu kullanarak bunları birbirinden ayırın. Bir sınıfın başka bir sınıfın işlevselliğine ihtiyacı olduğunda, onu bir bağımlılık olarak kabul edin ve o sınıftan miras almak yerine işlevleri ona devredin. Bu, gevşek bağlanma sağlar, test etmeyi kolaylaştırır ve bir sınıftaki değişikliklerin başka bir sınıfı bozmasını önler.

Sık Sorulan Sorular

Sorularınız mı var?

Ne zaman miras, ne zaman bileşim kullanmalıyım?

Kalıtım özelliğini yalnızca, alt sınıfın üst sınıfın gerçekten özelleştirilmiş bir versiyonu olduğu gerçek “bir türüdür” ilişkilerinde kullanın. Eğer alanınızda kareler dikdörtgen sayılıyorsa, “Kare, Dikdörtgen’i genişletir” ifadesi mantıklıdır. “Bir şeye sahiptir” veya “bir şeyi kullanır” ilişkilerinde bileşim özelliğini kullanın. Tekrarlayan bir ödeme, bir ödeme işlemcisini kullanır; ancak ödeme işlemcisinin bir türü değildir. Şüpheye düştüğünüzde, bileşim özelliğini tercih edin.

Ya birden fazla kaynaktan gelen kodları yeniden kullanmam gerekirse?

Kompozisyon, bunu çoklu bağımlılıklar aracılığıyla doğal bir şekilde sağlar. Bir sınıf, çoklu miras kısıtlamalarıyla uğraşmak zorunda kalmadan bir ödeme işleyicisini, bir zamanlayıcıyı ve bir bildirim aracısını bir araya getirebilir. Miras, sizi tek miraslı dillere ya da karmaşık çoklu miras hiyerarşilerine mecbur bırakır. Kompozisyon ise daha nettir: her bir bağımlılık, oluşturucuda açıkça belirtilir.

Kalıtımı bileşime nasıl yeniden düzenleyebilirim?

Alt sınıfın miras aldığı işlevlere kıyasla gerçekte ne yaptığını belirleyin. Örnekte, RecurringCreditCardPayment ödemeleri planlar ancak işleme mantığını miras alır. Üst sınıfın işlevselliğini ayrı bir sınıfa ayırın, ardından bunu bir bağımlılık olarak aktarın. extends Parent ifadesini bir yapıcı parametresiyle değiştirin. super.method() çağrılarını this.dependency.method() ile değiştirin. Her adımı test edin.

Kompozisyon, daha fazla şablon kod oluşturmuyor mu?

İlk kurulumda bağımlılıkların açıkça belirtilmesi gerekir, ancak bu netlik çok değerlidir. Üst hiyerarşileri tek tek taramaya gerek kalmadan her sınıfın tam olarak neye ihtiyaç duyduğunu görebilirsiniz. Modern bağımlılık enjeksiyon çerçeveleri, tekrarlanan kodları azaltır. Bu açık ifade, örtük olarak miras alınan davranışlardan kaynaklanan hataları önler. Kurulum kodu için yazılan birkaç satırlık ekstra kod, sağladığı esneklik ve bakım kolaylığı karşılığında kesinlikle değerlidir.

Peki ya soyut temel sınıflar ve arayüzler?

Arayüzler, uygulamalarla bağımlılık oluşturmadan sözleşmeleri tanımlamak için mükemmeldir. Bir sınıfın hangi davranışlara sahip olması gerektiğini belirtmek için arayüzleri kullanın, ardından somut uygulamaları enjekte edin. Soyut sınıflar, sadece bazı yöntemleri uygulanmamış olan miras alma mekanizmalarından ibarettir; aynı bağımlılık sorunlarına sahiptirler. Miras alma içeren soyut sınıflar yerine, bileşim içeren arayüzleri tercih edin.

Paylaşılan yardımcı yöntemleri nasıl ele almalıyım?

Bunları ayrı yardımcı sınıflara veya hizmetlere ayırın. Ortak doğrulama mantığını miras almak yerine, bir Validator hizmeti enjekte edin. Örnekte, hem tek seferlik hem de tekrarlayan ödemeler için aynı doğrulama gerekiyorsa, her ikisinin de bileşim yoluyla kullanabileceği ortak bir PaymentValidator oluşturun. Bu, ortak mantığı üst sınıflarda gizlenmiş yöntemlere kıyasla daha kolay bulunabilir ve test edilebilir hale getirir.

Şimdi güvenliğinizi sağlayın

Kodunuzu, bulutunuzu ve çalışma zamanınızı tek bir merkezi sistemde güvenceye alın.
Güvenlik açıklarını otomatik olarak hızla bulun ve düzeltin.

Kredi kartı gerekmez | Tarama sonuçları 32 saniyede.