PHP'de Çoklu Kalıtım


97

PHP5'in hala çoklu kalıtımı desteklemediği gerçeğini aşmanın iyi ve temiz bir yolunu arıyorum. Sınıf hiyerarşisi şöyledir:

Mesaj
- TextMessage
-------- InvitationTextMessage
- EmailMessage
-------- InvitationEmailMessage

İki tür Davet * sınıfının birçok ortak noktası vardır; Her ikisinin de miras alacağı ortak bir ebeveyn sınıfına sahip olmayı çok isterim, Davet. Ne yazık ki, şu anki atalarıyla da birçok ortak yönleri var ... TextMessage ve EmailMessage. Burada çoklu miras için klasik arzu.

Sorunu çözmek için en hafif yaklaşım nedir?

Teşekkürler!


4
Kalıtımın (hatta çoklu mirasın) haklı olduğu pek çok durum yoktur. SOLID ilkelerine bakın. Kalıtım yerine kompozisyonu tercih edin.
Ondřej Mirtes

2
@ OndřejMirtes ne demek istiyorsun - "mirasın haklı olduğu pek çok durumda değil."
styler1972

12
Demek istediğim - kalıtım faydadan çok sorun getirir (Liskov ikame ilkesine bakın). Hemen hemen her şeyi kompozisyonla çözebilir ve çok fazla baş ağrısından kurtulabilirsiniz. Kalıtım da statiktir - bu, kodda zaten yazılanları değiştiremeyeceğiniz anlamına gelir. Ancak birleştirme çalışma zamanında kullanılabilir ve uygulamaları dinamik olarak seçebilirsiniz - örneğin, aynı sınıfı farklı önbelleğe alma mekanizmalarıyla yeniden kullanın.
Ondřej Mirtes

5
: PHP 5.4 "özellikleri" vardır stackoverflow.com/a/13966131/492130
f.ardelian

1
Yeni başlayanlara mirası asla kullanmamalarını öneririm . Genel olarak, mirasa izin verilen iki durum şunlardır: 1) bir kütüphane oluştururken kullanıcılar daha az kod yazarlar ve 2) proje lideri onu kullanmanızı talep ettiğinde
gurghet

Yanıtlar:


141

Alex, çoğu zaman çoklu kalıtıma ihtiyaç duyarsan, nesne yapının bir şekilde yanlış olduğunun bir işaretidir. Anlattığınız durumda, sınıf sorumluluğunuzun çok geniş olduğunu görüyorum. Mesaj, uygulama iş modelinin bir parçasıysa, çıktı oluşturmaya özen göstermemelidir. Bunun yerine sorumluluğu bölebilir ve metin veya html arka uç kullanarak iletilen İletiyi gönderen MessageDispatcher'ı kullanabilirsiniz. Kodunuzu bilmiyorum, ancak şu şekilde simüle etmeme izin verin:

$m = new Message();
$m->type = 'text/html';
$m->from = 'John Doe <jdoe@yahoo.com>';
$m->to = 'Random Hacker <rh@gmail.com>';
$m->subject = 'Invitation email';
$m->importBody('invitation.html');

$d = new MessageDispatcher();
$d->dispatch($m);

Bu şekilde, Mesaj sınıfına bazı uzmanlıklar ekleyebilirsiniz:

$htmlIM = new InvitationHTMLMessage(); // html type, subject and body configuration in constructor
$textIM = new InvitationTextMessage(); // text type, subject and body configuration in constructor

$d = new MessageDispatcher();
$d->dispatch($htmlIM);
$d->dispatch($textIM);

MessageDispatcher'ın, typeiletilen Message nesnesindeki özelliğe bağlı olarak HTML veya düz metin olarak gönderilip gönderilmeyeceğine karar vereceğini unutmayın .

// in MessageDispatcher class
public function dispatch(Message $m) {
    if ($m->type == 'text/plain') {
        $this->sendAsText($m);
    } elseif ($m->type == 'text/html') {
        $this->sendAsHTML($m);
    } else {
        throw new Exception("MIME type {$m->type} not supported");
    }
}

Özetlemek gerekirse, sorumluluk iki sınıf arasında bölünmüştür. Mesaj yapılandırması InvitationHTMLMessage / InvitationTextMessage sınıfında yapılır ve gönderme algoritması dağıtıcıya delege edilir. Buna Strateji Modeli denir, buradan daha fazlasını okuyabilirsiniz .


13
Şaşırtıcı derecede kapsamlı cevap, teşekkürler! Bugün bir şey öğrendim!
Alex Weinstein

26
... bunun biraz eski olduğunu biliyorum (PHP'de MI olup olmadığını araştırıyordum ... sadece merak için) Bunun Strateji Modeli için iyi bir örnek olduğunu düşünmüyorum. Strateji Modeli, istediğiniz zaman yeni bir "strateji" uygulayabilmeniz için tasarlanmıştır. Sağladığınız uygulama böyle bir yetenek içermiyor. Bunun yerine, Message, MessageDispatcher-> dispatch () (Dispatcher bir param veya üye değişkeni) çağıran "send" işlevine sahip olmalıdır ve yeni sınıflar HTMLDispatcher & TextDispatcher kendi yöntemleriyle "dispatch" uygulayacaktır (bu, diğer Göndericilerin yapmasına olanak sağlar) diğer iş)
Terence Honles

12
Maalesef PHP, Strateji modelini uygulamak için harika değil. Yöntem aşırı yüklemesini destekleyen diller burada daha iyi çalışır - aynı ada sahip iki yönteme sahip olduğunuzu hayal edin: dispatch (HTMLMessage $ m) ve dispatch (TextMessage $) - şimdi güçlü bir şekilde yazılmış dilde derleyici / yorumlayıcı otomatik olarak doğru "stratejiyi" kullanır. parametre türü. Bunun dışında, yeni strateji uygulamasına açık olmanın Strateji Modeli'nin özü olduğunu düşünmüyorum. Elbette sahip olmak güzel bir şey, ancak çoğu zaman bir gereklilik değil.
Michał Rudnicki

2
TracingBir dosyada hata ayıklama, kritik sorun için SMS gönderme vb. Gibi genel şeylerin olmasını istediğiniz bir sınıfınız olduğunu varsayalım (bu yalnızca bir örnektir). Tüm sınıflarınız bu sınıfın çocuklarıdır. Şimdi Exception, bu işlevlere sahip olması gereken bir sınıf oluşturmak istediğinizi varsayalım (= alt öğesi Tracing). Bu sınıfın çocuğu olmalı Exception. Bu tür şeyleri çoklu miras olmadan nasıl tasarlarsınız ? Evet, her zaman bir çözümünüz olabilir, ancak her zaman korsanlığa yaklaşacaksınız. Ve bilgisayar korsanlığı = uzun vadede pahalı bir çözüm. Hikayenin sonu.
Olivier Pons

1
Olivier Pons, Alt sınıflandırma İzlemenin kullanım durumunuz için doğru çözüm olacağını düşünmüyorum. Statik yöntemlerle Debug, SendSMS vb. İle soyut bir İzleme sınıfına sahip olmak kadar basit bir şey, bunlar daha sonra herhangi bir sınıfın içinden Tracing :: SendSMS () vb. İle çağrılabilir. Diğer sınıflarınız İzleme 'türleri' değildir, İzlemeyi 'kullanırlar'. Not: Bazı kişiler statik yöntemlere göre tekli tercih edebilir; Mümkünse tekil yerine statik yöntemler kullanmayı tercih ederim.

15

Belki bir 'is-a' ilişkisini 'has-a' ilişkisi ile değiştirebilirsiniz? Bir Davetiyenin bir Mesajı olabilir, ancak mutlaka 'is-a' mesajı olması gerekmez. Mesaj modeli ile birlikte iyi gitmeyen bir Davet fe onaylanabilir.

Bununla ilgili daha fazla bilgiye ihtiyacınız varsa, 'kompozisyon ve kalıtım' araması yapın.


9

Phil'den bu başlıktan alıntı yapabilirsem ...

PHP, Java gibi, çoklu kalıtımı desteklemez.

PHP 5.4'te bu soruna çözüm sağlamaya çalışan özellikler gelecek .

Bu arada, sınıf tasarımınızı yeniden düşünmeniz en iyisidir. Sınıflarınız için genişletilmiş bir API'nin peşindeyseniz birden çok arabirim uygulayabilirsiniz.

Ve Chris ...

PHP çoklu kalıtımı gerçekten desteklemez, ancak onu uygulamanın bazı (biraz dağınık) yolları vardır. Bazı örnekler için bu URL'ye bakın:

http://www.jasny.net/articles/how-i-php-multiple-inheritance/

İkisinin de yararlı bağlantıları olduğunu düşünmüştüm. Özellikleri veya belki bazı karışımları denemek için sabırsızlanıyorum ...


1
Özellikler gitmenin yolu
Jonathan

6

Symfony framework bunun için bir mixin eklentisine sahiptir , kontrol etmek isteyebilirsiniz - sadece fikir için, kullanmasanız bile.

"Tasarım modeli" yanıtı, paylaşılan işlevselliği ayrı bir bileşene soyutlamak ve çalışma zamanında oluşturmaktır. Kalıtım dışında bir şekilde İleti sınıflarınızla ilişkilendirilen bir sınıf olarak Davet işlevini soyutlamanın bir yolunu düşünün.


4

Bunu çözmek için PHP 5.4'teki özellikleri kullanıyorum. http://php.net/manual/en/language.oop5.traits.php

Bu, genişletmelerle klasik kalıtıma izin verir, ancak aynı zamanda ortak işlevsellik ve özellikleri bir 'özelliğe' yerleştirme olasılığını da verir. Kılavuzun dediği gibi:

Özellikler, PHP gibi tek kalıtım dillerinde kodun yeniden kullanımı için bir mekanizmadır. Bir Özellik, bir geliştiricinin farklı sınıf hiyerarşilerinde yaşayan birkaç bağımsız sınıfta yöntem kümelerini özgürce yeniden kullanmasını sağlayarak tekli kalıtımın bazı sınırlamalarını azaltmayı amaçlamaktadır.



3

Bu hem soru hem de çözüm ...

Peki ya büyülü _ call (),_get (), __set () yöntemleri? Henüz bu çözümü test etmedim ama bir multiInherit sınıfı yaparsanız ne olur? Bir çocuk sınıftaki korumalı bir değişken, devralınacak bir sınıflar dizisi içerebilir. Çoklu arabirim sınıfındaki kurucu, miras alınan sınıfların her birinin örneklerini oluşturabilir ve bunları, örneğin _ext gibi özel bir özelliğe bağlayabilir. __Call () yöntemi, çağrılacak doğru yöntemi bulmak için _ext dizisindeki sınıfların her birinde method_exists () işlevini kullanabilir. __get () ve __set, dahili özellikleri bulmak için kullanılabilir veya referansları olan bir uzmanınız, alt sınıfın özelliklerini ve miras alınan sınıfları aynı verilere referanslar haline getirebilirsiniz. Nesnenizin çoklu kalıtımı, bu nesneleri kullanan koda şeffaf olacaktır. Ayrıca, _ext dizisi sınıf adına göre indekslendiği sürece dahili nesneler, gerekirse miras alınan nesnelere doğrudan erişebilir. Bu süper sınıfı yaratmayı hayal ettim ve henüz işe yararsa bazı kötü programlama alışkanlıklarının geliştirilmesine yol açacağını düşündüğüm için henüz uygulamadım.


Bunun mümkün olduğunu düşünüyorum. Birden çok sınıfın işlevselliğini birleştirecek, ancak aslında onları (anlamında instanceof) miras
almayacak

Ve bu kesinlikle iç sınıfta kendine bir çağrı yapar yapmaz geçersiz kılmalara izin vermeyecektir :: <ne olursa olsun>
Phil Lello

1

Ne yaptığınızı netleştirmek için sormam gereken birkaç sorum var:

1) Mesaj nesneniz yalnızca bir mesaj mı içeriyor, örneğin gövde, alıcı, zamanlama saati? 2) Davet nesnenizle ne yapmayı planlıyorsunuz? Bir EmailMessage ile karşılaştırıldığında özel olarak ele alınması gerekiyor mu? 3) Eğer öyleyse, bu konuda bu kadar özel olan nedir? 4) Durum böyleyse, neden mesaj türlerinin bir davet için farklı şekilde ele alınması gerekiyor? 5) Bir karşılama mesajı veya bir OK mesajı göndermek isterseniz ne olur? Onlar da yeni nesneler mi?

Çok fazla işlevselliği, yalnızca bir mesaj içeriğini tutmakla ilgilenmesi gereken bir dizi nesnede birleştirmeye çalışıyormuşsunuz gibi görünüyor - nasıl ele alınması gerektiği değil. Bana göre, bir davet ile standart bir mesaj arasında hiçbir fark yoktur. Davet özel işlem gerektiriyorsa, bu bir mesaj türü değil uygulama mantığı anlamına gelir.

Örneğin: oluşturduğum bir sistem, SMS, E-posta ve diğer mesaj türlerine genişletilen paylaşılan bir temel mesaj nesnesine sahipti. Ancak: bunlar daha fazla uzatılmadı - bir davet mesajı, E-posta tipi bir mesajla gönderilecek önceden tanımlanmış bir metindi. Belirli bir Davet uygulaması, bir davet için doğrulama ve diğer gereksinimlerle ilgilenecektir. Sonuçta, tek yapmak istediğiniz, kendi başına ayrı bir sistem olması gereken X mesajını Y alıcısına göndermek.


0

Java ile aynı sorun. Bu sorunu çözmek için soyut işlevlere sahip arayüzler kullanmayı deneyin


0

PHP arayüzleri destekler. Kullanım alanlarınıza bağlı olarak bu iyi bir bahis olabilir.


5
Arayüzler somut işlev uygulamalarına izin vermez, bu yüzden burada yardımcı olmazlar.
Alex Weinstein

1
Arayüzler, sınıfların aksine Çoklu Devralmayı destekler.
Craig Lewis

-1

Mesaj sınıfının hemen altında bir Davet dersine ne dersiniz?

böylece hiyerarşi şöyle olur:

Mesaj
- Davet
------ TextMessage
------ EmailMessage

Ve Invitation sınıfında, InvitationTextMessage ve InvitationEmailMessage içindeki işlevselliği ekleyin.

Davetiyenin gerçekten bir Mesaj türü olmadığını biliyorum, daha çok Mesajın bir işlevi. Bu yüzden bunun iyi bir OO tasarımı olup olmadığından emin değilim.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.