Sınıf hiyerarşisindeki nesne davranışının veya özelliklerinin * kaldırılmasını * sağlayan bir dil veya tasarım deseni var mı?


28

Geleneksel sınıf hiyerarşilerinin iyi bilinen bir eksikliği, gerçek dünyayı modelleme söz konusu olduğunda onların kötü olmalarıdır. Örnek olarak, hayvan türlerini sınıflarla temsil etmeye çalışmak. Bunu yaparken aslında birkaç problem var, ama bir çözüm görmedim, bir alt sınıfın uçamayan bir penguen gibi bir süper sınıfta tanımlanmış bir davranışı veya mülkü "kaybetmesi" dir (orada muhtemelen daha iyi örneklerdir, ama aklıma ilk gelen budur).

Bir yandan, her özellik ve davranış için, mevcut olup olmadığını belirten bir bayrak tanımlamak ve bu davranışa veya özelliğe erişmeden önce her zaman kontrol etmek istemezsiniz. Kuşların Kuşlar sınıfında basit ve açık bir şekilde uçabileceklerini söylemek istersiniz. Fakat daha sonra "istisnalar" her yerde bazı korkunç hackler kullanmak zorunda kalmadan tanımlanabilseydi daha iyi olurdu. Bu genellikle bir süredir bir sistem üretken olur. Birdenbire, orijinal tasarıma hiç uymayan bir "istisna" buluyorsunuz ve kodunuzun büyük bir bölümünü buna uyacak şekilde değiştirmek istemiyorsunuz.

Öyleyse, "süper sınıf" ve onu kullanan tüm kodlarda büyük değişiklikler gerektirmeden, bu sorunu temiz bir şekilde ele alabilen bazı dil veya tasarım kalıpları var mı? Bir çözüm yalnızca belirli bir durumu ele alsa bile, birkaç çözüm birlikte tam bir strateji oluşturabilir.

Daha fazla düşündükten sonra, Liskov Değişim İlkesini unuttuğumu fark ettim. Bu yüzden yapamazsın. Tüm ana "özellik grupları" için "özellikleri / arayüzleri" tanımladığınızı varsayarsak, Uçan özelliği, Kuşlar ve bazı özel sincaplar ve balıkların uygulayabileceği gibi, hiyerarşinin farklı dallarında özellikleri serbestçe uygulayabilirsiniz.

Bu yüzden sorum, "Bir özelliği nasıl uygulayabilirim?" Anlamına gelebilir. Süper sınıfınız bir Java Seri hale getirilebilirse, örneğin bir "Soket" içeriyorsanız, durumunuzu seri hale getirme imkanı olmasa bile, siz de bir tane olmalısınız.

Bunu yapmanın bir yolu, her zaman tüm özelliklerinizi baştan başlayarak çiftler halinde tanımlamaktır: Flying and NotFlying (eğer kontrol edilmezse, desteklenmeyenOperationException'ı fırlatır). Not-trait özelliği, herhangi bir yeni arayüz tanımlamaz ve sadece kontrol edilebilir. Özellikle en baştan kullanıldığında "ucuz" bir çözüm gibi geliyor.


3
'her yerde bazı korkunç hackler kullanmak zorunda kalmadan': bir davranışı devre dışı bırakmak korkunç bir keskidir: bunun function save_yourself_from_crashing_airplane(Bird b) { f.fly() }daha da karmaşıklaşacağı anlamına gelir . (Peter
Török'in

Strateji kalıbı ve kalıtımın bir kombinasyonu, belirli süper türler için kalıtsal davranışı "üzerine oluşturmanıza" izin verebilir mi? Ne zaman söylersiniz: " it would be nice if one could define "exceptions" afterward, without having to use some horrible hacks everywhere"hack davranışını kontrol eden bir fabrika metodu düşünüyor musunuz?
StuperUser

1
Bir elbette sadece atabilseydin NotSupportedExceptiondan Penguin.fly().
Felix Dombek

Bildiğim kadarıyla diller gitmek gibi, kesinlikle olabilir bir çocuk sınıfta bir yöntem un-uygular. Örneğin, Ruby: class Penguin < Bird; undef fly; end;. Yapmanız gereken başka bir soru.
Nathan Long,

Bu, liskov ilkesini ve tartışmasız OOP'nin bütün noktasını kıracaktı.
deadalnix

Yanıtlar:


17

Diğerlerinin de söylediği gibi, LSP'ye karşı çıkmak zorunda kalacaksınız.

Bununla birlikte, bir alt sınıfın yalnızca bir süper sınıfın keyfi bir uzantısı olduğu söylenebilir. Bu kendi başına yeni bir nesne ve süper sınıfla olan tek ilişki bir temel kullanması.

Bu ziyade penguen diyerek mantıklı mantıklı olabilir olan bir kuş. Söylediğin Penguen, Bird'den bir takım davranışlar devraldı.

Genellikle dinamik diller bunu kolayca ifade etmenizi sağlar; JavaScript kullanarak bir örnek aşağıdadır:

var Penguin = Object.create(Bird);
Penguin.fly = undefined;
Penguin.swim = function () { ... };

Bu özel durumda, nesneye değeri olan bir özellik yazarak devraldığı yöntemi Penguinetkin olarak gölgelendirmek .Bird.flyflyundefined

Şimdi artık Penguinnormal olarak değerlendirilemeyeceğini söyleyebilirsiniz Bird. Ancak belirtildiği gibi, gerçek dünyada basitçe yapamaz. Çünkü biz Birduçan bir varlık olarak modellik yapıyoruz .

Alternatif, Kuşların uçabileceği gibi geniş bir varsayımda bulunmamaktır. BirdBütün kuşların başarısızlıktan miras almasına izin veren bir soyutlamaya sahip olmak mantıklı olacaktır . Bu, yalnızca tüm alt sınıfların yapabileceği varsayımlarda bulunmak anlamına gelir.

Genel olarak, Mixin'in fikri burada güzel bir şekilde uygulanır. Çok ince bir taban sınıfına sahip olun ve diğer tüm davranışları onunla karıştırın.

Örnek:

// for some value of Object.make
var Penguin = Object.make(
  /* base class: */ Bird,
  /* mixins: */ Swimmer, ...
);
var Hawk = Object.make(
  /* base class: */ Bird,
  /* mixins: */ Flyer, Carnivore, ...
);

Eğer merak ediyorsanız, ben bir uygulama varObject.make

İlave:

Bu yüzden sorum, "Bir özelliği nasıl uygulayabilirim?" Anlamına gelebilir. Süper sınıfınız bir Java Seri hale getirilebilirse, örneğin bir "Soket" içeriyorsanız, durumunuzu seri hale getirme imkanı olmasa bile, siz de bir tane olmalısınız.

Bir özelliği "uygulamıyor" değilsiniz. Sadece kalıtım hiyerarşinizi düzeltin. Süper sınıf sözleşmenizi yerine getirebilirsiniz ya da bu türden bir numara gibi davranmamalısınız.

Nesne kompozisyonunun parladığı yer burasıdır.

Bir yana, Serileştirilebilir, her şeyin serileştirilmesi gerektiği anlamına gelmez, yalnızca "önem verdiğiniz durum" serileştirilmesi anlamına gelir.

Bir "NotX" özelliği kullanmamalısınız. Bu sadece korkunç kod şişmesi. Eğer bir fonksiyon uçan bir cisim beklerse, bir mamut verdiğinizde çarpması ve yakılması gerekir.


10
“Gerçek dünyada basitçe yapamaz.” Evet yapabilir. Bir penguen bir kuştur. Uçabilme kabiliyeti bir kuşun özelliği değildir, çoğu kuş türünün tesadüfi bir özelliğidir. Kuşları tanımlayan özellikler "tüylü, kanatlı, iki ayaklı, endotermik, yumurtlayan, omurgalı hayvanlardır" (Vikipedi) - orada uçmakla ilgili bir şey yok.
pdr

2
pdr yine bu kuş tanımınıza bağlıdır. "Kuş" terimini kullandığımdan, uçma yöntemi dahil kuşları temsil etmek için kullandığımız sınıf soyutlamasını kastediyordum. Ayrıca sınıf soyutlamanızı daha az spesifik hale getirebileceğinizi de söyledim. Ayrıca bir penguen tüylü değildir.
Raynos

2
@Raynos: Penguenler gerçekten tüylüdür. Kuş tüyleri elbette oldukça kısa ve yoğundur.
Jon Purdy

@JonPurdy yeterince adil, her zaman onların kürk olduğunu hayal ediyorum.
Raynos,

Genel olarak +1, özellikle de "mamut" için. LOL!
Sebastien Diot,

28

AFAIK, tüm mirasa dayalı dilleri Liskov Değiştirme Prensibi üzerine inşa edilmiştir . Bir alt sınıftaki bir temel sınıf özelliğini kaldırmak / devre dışı bırakmak açıkça LSP'yi ihlal edecektir, bu nedenle böyle bir olasılığın herhangi bir yerde uygulandığını sanmıyorum. Gerçek dünya gerçekten dağınıktır ve tam olarak matematiksel soyutlamalar ile modellenemez.

Bazı diller , tam olarak bu tür problemlerle daha esnek bir şekilde baş etmek için özellikler veya karışımlar sağlar.


1
LSP, sınıflar için değil tipler içindir .
Jörg W Mittag 15:11

2
@ PéterTörök: Bu soru aksi takdirde olmazdı :-) Ruby'den iki örnek düşünebilirim. IS-NOT-A'ya rağmen Classbir alt sınıftır . Fakat yine de, bir çok kodu tekrar kullandığından, bir alt sınıf olmak mantıklı geliyor. OTOH, IS-A , ancak ikisinin herhangi bir miras ilişkisi yoktur ( elbette her ikisinden de miras aldığının dışında ), çünkü herhangi bir kod paylaşmazlar. Sınıflar kod paylaşımı içindir, türler protokolleri tanımlamak içindir. ve aynı protokole, dolayısıyla aynı tipte olmasına rağmen, sınıfları alakasızdır. ModuleClassModuleStringIOIOObjectIOStringIO
Jörg W Mittag

1
@ JörgWMittag, Tamam, şimdi ne demek istediğinizi daha iyi anlıyorum. Ancak, bana göre ilk örneğiniz, önerdiğiniz gibi bazı temel problemlerin ifadesinden ziyade, bir kalıtım yanlışlığı gibi geliyor. Genel miras IMO, uygulamayı yeniden kullanmak için, sadece alt tip ilişkilerini ifade etmek için kullanılmamalıdır (is-a). Ve kötüye kullanılabileceği gerçeği diskalifiye edilmez - kötüye kullanılamayan herhangi bir alandan kullanılabilir bir araç düşünemiyorum.
Péter Török

2
Bu cevabı ihlal eden kişilere: Bunun, özellikle yapılan açıklamadan sonra soruyu gerçekten cevaplamadığını not edin. Bu cevabın önemsiz bir oyu hakettiğini düşünmüyorum, çünkü söylediklerini bilmek çok doğru ve önemli, ancak soruyu gerçekten cevaplamadı.
jhocking

1
Sadece arayüzlerin tip olduğu, sınıfların olmadığı ve alt sınıfların üst sınıflarının arayüzlerini "uygulayabildiği" bir Java düşünün ve bence siz de kaba bir fikrim var.
Jörg W Mittag

15

Fly()ilk örnekte: Strateji Deseni için İlk Tasarım Desenlerini Yönetin ve bu neden “Mirasa ilişkin kompozisyonu tercih etmeniz konusunda iyi bir durumdur . .

Kompozisyonu ve mirası FlyingBird, FlightlessBirdbir Fabrika tarafından enjekte edilen doğru davranışa sahip olan, uygun alt tiplerin, örneğin Penguin : FlightlessBirdotomatik olarak elde ettiği ve elbette gerçekten spesifik olan herhangi bir şeyin Fabrika tarafından ele alındığı , üstlerine sahip olmakla karıştırabilirsiniz .


1
Cevabımda Dekoratör kalıbından bahsettim ama Strateji kalıbı da oldukça iyi çalışıyor.
jhocking

1
Kalıtım için iyilik kompozisyonu "+1. Bununla birlikte, kompozisyonu statik olarak yazılmış dillerde uygulamak için özel tasarım desenlerinin gerekliliği Ruby gibi dinamik dillere olan önyargımı güçlendiriyor.
Roy Tinker

11

Asıl sorunun Birdbir Flyyöntemi olmadığını mı düşünüyorsunuz? Neden olmasın:

class Bird
{
    // features that all birds have
}

class BirdThatCanSwim : Bird
{
    public void Swim() {...};
}

class BirdThatCanFly : Bird
{
    public void Fly() {...};
}


class Penguin : BirdThatCanSwim { }
class Sparrow : BirdThatCanFly { }

Şimdi bariz problem çoklu kalıtımdır ( Duck), yani gerçekten ihtiyacınız olan şey arayüzlerdir:

interface IBird { }
interface IBirdThatCanSwim : IBird { public void Swim(); }
interface IBirdThatCanFly : IBird { public void Fly(); }
interface IBirdThatCanQuack : IBird { public void Quack(); }

class Duck : BirdThatCanFly, IBirdThatCanSwim, IBirdThatCanQuack
{
    public void Swim() {...};
    public void Quack() {...};
}

3
Sorun evrimin Liskov Değişim Prensibi'ni takip etmemesi ve özellik kaldırma ile miras almasıdır.
Donal Fellows

7

Öncelikle, EVET, kolay nesne dinamik modifikasyonunu sağlayan herhangi bir dil bunu yapmanıza izin verecektir. Örneğin, Ruby'de bir yöntemi kolayca kaldırabilirsiniz.

Ancak Péter Török'in dediği gibi, LSP'yi ihlal edecek .


Bu bölümde, LSP'yi unutacağım ve şunu varsayalım:

  • Kuş, fly () yöntemiyle bir sınıftır.
  • Penguen Kuş'tan miras almalı
  • Penguen uçamaz ()
  • İyi bir tasarım ya da gerçek dünyaya uyması umrumda değil, çünkü bu soruda verilen örnek.

Dedin :

Bir yandan, her özellik ve davranış için tanımlanmış olup olmadığını belirten bir bayrak tanımlamak ve bu davranışa veya özelliğe erişmeden önce her zaman kontrol etmek istemezsiniz.

İstediğin gibi görünüyor Python'un " izin yerine affedilmeyi istemek "

Sadece Pengueninize bir istisna attırın veya bir istisna atan bir NonFlyingBird sınıfından miras alın (sözde kod):

class Penguin extends Bird {
     function fly():void {
          throw new Exception("Hey, I'm a penguin, I can't fly !");
     }
}

Bu arada, ne seçerseniz seçin: bir istisna oluşturmak veya bir yöntemi kaldırmak, sonunda, aşağıdaki kod (dilin, yöntemin kaldırılmasını desteklediğini varsayalım):

var bird:Bird = new Penguin();
bird.fly();

çalışma zamanı istisnası atar.


"Sadece Pengueninize bir istisna attırın veya bir istisna atan bir NonFlyingBird sınıfından miras alın" Bu hala bir LSP ihlalidir. Yine de bir Penguen'in uçabileceğini, sinek uygulamasının başarısız olmasına rağmen hala öneriyor. Penguen üzerinde asla bir sinek yöntemi olmamalıdır.
pdr

@pdr: Bir penguen düşündüren değildir olabilir sinek, ama o kadar olmalıdır (bu, bir sözleşme) uçar. İstisna size yapamayacağını söyleyecektir . Bu arada, bunun iyi bir OOP uygulaması olduğunu iddia etmiyorum, sadece sorunun bir kısmına cevap veriyorum
David

Mesele şu ki, bir Penguen'in bir Kuş olduğu için uçması beklenmemelidir. "Eğer x uçabiliyorsa, bunu yap; yoksa bunu yap." Yazan bir kod yazmak istersem. Sürümünüzde, bir nesneyi uçup uçamayacağını sormam gereken bir deneme / yakalama kullanmalıyım (döküm veya kontrol yöntemi varsa). Sadece ifadelerde olabilir, ancak cevabınız bir istisna atmanın LSP ile uyumlu olduğu anlamına gelir.
pdr

pdr "Sürümünüzde bir deneme / yakalama kullanmalıyım" -> izin yerine affedilmeyi istemek budur (çünkü bir Ördek bile kanatlarını kırabilir ve uçamayabilirdi). İfadeleri düzelteceğim.
David,

“Bu izin almaktan çok affedilmeyi isteme meselesi.” Evet, çerçevenin herhangi bir eksik yöntem için aynı istisna türünü atmasına izin vermesi dışında, Python'un "denediği gibi: AttributeError:" tam olarak eşittir C # 'e eşittir (eğer X ise Y) {} else {} "ve anında tanınabilir gibi. Ancak, Bird'deki varsayılan fly () işlevini geçersiz kılmak için kasıtlı olarak bir CannotFlyException attıysanız, daha az tanınabilir hale gelir.
pdr

7

Yorumlarda birinin işaret ettiği gibi, penguenler kuşlardır, penguenler uçmaz, ergo tüm kuşlar uçamaz.

Öyleyse Bird.fly () çalışmamalı veya çalışmamasına izin verilmemelidir. Eskiyi tercih ederim.

FlyingBird'e sahip olmak Kuşa'nın bir .fly () yöntemine sahip olması elbette doğru olacaktır.


Ben Fly kuş olduğunu bir arayüz olmalıdır hemfikir olabilir uygulamak. Geçersiz kılınabilecek varsayılan davranışla birlikte bir yöntem olarak da uygulanabilir, ancak daha temiz bir yaklaşım bir arayüz kullanıyordur.
Jon Raynor

6

Fly () örneğindeki asıl sorun, işlemin giriş ve çıkışının uygun şekilde tanımlanmamış olmasıdır. Bir kuşun uçması için ne gereklidir? Ve uçuş başarılı olduktan sonra ne olacak? Fly () işlevi için parametre türleri ve dönüş türleri bu bilgilere sahip olmalıdır. Aksi halde, tasarımınız rastgele yan etkilere bağlıdır ve her şey olabilir. Her şey , tüm soruna neden olan şeydir, arayüz doğru bir şekilde tanımlanmamıştır ve her türlü uygulamaya izin verilir.

Yani, bunun yerine:

class Bird {
public:
   virtual void fly()=0;
};

Böyle bir şey olmalı:

   class Bird {
   public:
      virtual float fly(float x) const=0;
   };

Şimdi açık bir şekilde, fonksiyonelliğin sınırlarını - uçan davranışınızın sadece tek bir yüzmeye sahip olduğunu - pozisyon verildiğinde zeminden uzaklığı belirliyor. Şimdi tüm sorun otomatik olarak kendini çözüyor. Uçamayan bir Kuş, bu işlevden sadece 0.0 döndürür, asla yerden ayrılmaz. Bunun için doğru davranış ve bir yüzmeye karar verildiğinde arayüzü tamamen uyguladığınızı biliyorsunuz.

Gerçek davranış türlerini kodlamak zor olabilir, ancak arayüzlerinizi doğru şekilde belirlemenin tek yolu budur.

Düzenleme: Bir yönünü netleştirmek istiyorum. Bu float-> float sürümü fly () işlevinin ayrıca bir yol tanımlamasından dolayı önemlidir. Bu versiyon, bir kuşun uçarken kendisini sihirli bir şekilde çoğaltamayacağı anlamına gelir. Bu nedenle parametre tek yüzdürmedir - kuşun aldığı yoldaki konumdur. Daha karmaşık yollar istiyorsanız, o zaman Point2d posinpath (float x); hangi fly () işlevi ile aynı x kullanır.


1
Cevabını çok beğendim. Bence daha fazla oyu hakediyor.
Sebastien Diot,

2
Mükemmel cevap Sorun şu ki, soru sadece ellerini sinek () 'in gerçekte yaptığı gibi sallıyor. Herhangi bir gerçek sineğin uygulaması, en azından, penguen durumunda, {return currentPosition)}
Chris Cudmore

4

Teknik olarak bunu hemen hemen her türlü dinamik / ördek tipi dilde (JavaScript, Ruby, Lua, vb.) Yapabilirsiniz, ancak neredeyse her zaman gerçekten kötü bir fikirdir. Yöntemleri bir sınıftan kaldırmak, genel değişkenleri kullanmaya benzer şekilde bir bakım kabusudur (yani, bir modülde global durumun başka bir yerde değiştirilmediğini söyleyemezsiniz).

Tanımladığınız sorunun iyi desenleri, bir bileşen mimarisi tasarlayan Dekoratör veya Stratejidir. Temel olarak, gereksiz davranışları alt sınıflardan kaldırmak yerine, gerekli davranışları ekleyerek nesneler yaratırsınız. Bu yüzden çoğu kuşu inşa etmek için uçan bileşeni eklersiniz, ancak bu bileşeni penguenlerinize eklemeyin.


3

Peter, Liskov Değişim Prensibi'nden bahsetti, ancak bunun açıklanması gerektiğini hissediyorum.

Q (x) 'in T tipindeki x nesnesiyle ilgili kanıtlanabilir bir özellik olmasına izin verin, o zaman, S, T'nin bir alt türü olan S tipindeki y nesneleri için q (y) kanıtlanmalıdır.

Bu nedenle, eğer bir Kuş (T tipi nesne x) uçabilirse (q (x)) o zaman bir Penguen (S tipi nesne y) uçabilir (q (y)), tanım gereği. Ama açıkça durum böyle değil. Uçabilen fakat Kuş türü olmayan başka yaratıklar da vardır.

Bununla nasıl başa çıkacağınız dile bağlıdır. Eğer bir dil çoklu mirası destekliyorsa, uçabilen yaratıklar için soyut bir sınıf kullanmalısınız; eğer bir dil arayüzleri tercih ederse, o zaman çözüm budur (ve sinek uygulaması mirastan ziyade kapsüllenmelidir); veya, eğer bir dil Duck Typing'i destekliyorsa (punto amaçlanmayan), o zaman sadece varsa ve onu çağıran sınıflara bir fly metodu uygulayabilirsiniz.

Ancak bir üst sınıfın her özelliği, tüm alt sınıflarına uygulanmalıdır.

[Düzenlemeye cevaben]

CanFly'nin bir "özelliğini" Bird'e uygulamak daha iyi değil. Hala tüm kuşların uçabileceği kodları çağırıyor.

Tanımladığınız terimlerdeki özellik, Liskov'un "mülk" derken tam olarak ne anlama geldiğidir.


2

Neden bunu yapmamanız gerektiğini açıklayan Liskov Yerine Getirme İlkesini söyleyerek başlayayım. Ancak yapmanız gereken şey tasarımdan biridir. Bazı durumlarda Penguen’in uçamadığı önemli olmayabilir. Belki Penguen'e, Kuş :: fly () 'un belgelerinde açıkça görüldüğü gibi uçamayan kuşlar için onu fırlatabileceğini açıkça belirtti. Gerçekten uçup uçamayacağını görmek için bir test yaptırdım, ancak arayüzü etkiliyor.

Alternatif, sınıflarınızı yeniden yapılandırmaktır. "FlyingCreature" sınıfını oluşturalım (ya da eğer izin veren dilin üzerinde çalışıyorsanız, daha iyi bir arayüz). "Kuş" FlyingCreature'den miras kalmaz, ancak bunu yapan "FlyingBird" oluşturabilirsiniz. Lark, Akbaba ve Kartal, FlyingBird'den miras kaldı. Penguen değil. Sadece Kuş'tan miras kalıyor.

Naif yapıdan biraz daha karmaşık, ancak doğru olma avantajı var. Beklenen tüm sınıfların orada olduğunu (Kuş) ve yaratığınızın uçup uçamayacağının önemli olmaması durumunda, kullanıcının genellikle 'icat edilmiş olanları' (FlyingCreature) görmezden gelebileceğini not edersiniz.


0

Böyle bir durumla başa çıkmak için tipik bir yol UnsupportedOperationException(Java) cevabı gibi bir şey atmaktır . NotImplementedException(C #).


Bu olasılığı Kuş'ta belgelediğiniz sürece.
DJClayworth

0

Birçok yorum ile çok iyi cevaplar, ama hepsi aynı fikirde değiller ve sadece bir tane seçebilirim, bu yüzden burada hemfikir olduğum bütün görüşleri özetleyeceğim.

0) "Statik yazım" varsaymayın (sorduğumda yaptım, çünkü neredeyse yalnızca Java yapıyorum). Temel olarak, problem kişinin kullandığı dilin türüne bağlıdır.

1) Tip hiyerarşisini, çoğunlukla üst üste gelse bile, tasarımdaki ve birinin kafasındaki kod yeniden kullanım hiyerarşisinden ayırmalısınız. Genellikle, yeniden kullanım için sınıflar ve türler için arabirimler kullanın.

2) Normalde Kuş IS-A Sinekinin nedeni çoğu kuşun uçabilmesidir, bu nedenle kod yeniden kullanım bakış açısından pratiktir, ancak Kuş IS-A Sinekinin aslında en az bir istisna olduğu için yanlış olduğunu söylemek pratiktir. (Penguen).

3) Hem statik hem de dinamik dillerde, sadece bir istisna atabilirsiniz. Ancak bu, yalnızca işlevsellik ilan eden sınıf / arabirimin "sözleşmesinde" açıkça belirtilmesi durumunda kullanılmalıdır, aksi takdirde "sözleşmenin ihlali" olur. Bu aynı zamanda, her yerde istisnayı yakalamaya hazır olmanız gerektiği anlamına gelir, bu nedenle çağrı sitesine daha fazla kod yazarsınız ve çirkin koddur.

4) Bazı dinamik dillerde, bir süper sınıfın işlevselliğini "kaldırmak / gizlemek" mümkündür. Eğer fonksiyonelliğin varlığını kontrol etmek, bu dilde "IS-A" yı nasıl kontrol ettiğinize göre, bu yeterli ve mantıklı bir çözümdür. Öte yandan, "IS-A" işlemi hala nesnenizin "şu anda eksik olan işlevselliği" "uygulaması gerektiğini" söyleyen bir şeyse, arama kodunuz, işlevin var olduğunu varsayacaktır ve arayacak ve çökecek, bir istisna atmak için gereken miktar.

5) Daha iyi bir alternatif, Sinek özelliğini aslında Kuş özelliklerinden ayırmaktır. Bu yüzden uçan bir kuş, hem Kuş hem Uçan / Uçan'ı açıkça genişletmeli / uygulamalıdır. Bu muhtemelen en temiz tasarımdır, çünkü hiçbir şeyi "kaldırmak" zorunda kalmazsınız. Tek dezavantajı, şimdi hemen hemen her kuşun hem Kuş hem de Uçuşu uygulamak zorunda olmasıdır, bu yüzden daha fazla kod yazıyorsunuz. Bunun etrafındaki yol, hem Kuş hem de Uçağı uygulayan ve ortak durumu temsil eden bir aracı sınıf FlyingBird'e sahip olmaktır, ancak bu çalışma, çoklu kalıtım olmadan sınırlı kullanımda olabilir.

6) Çoklu kalıtım gerektirmeyen bir başka alternatif ise kalıtım yerine kompozisyon kullanmaktır. Bir hayvanın her yönü bağımsız bir sınıf tarafından modellenir ve somut bir Kuş bir Kuş bileşimidir ve Muhtemelen Uç veya Yüz, ... Tam kod yeniden kullanırsınız, ancak bir veya daha fazla adım atmanız gerekir Somut bir Kuş referansına sahip olduğunuzda Uçan işlevselliği. Ayrıca, doğal "nesne IS-A Fly" ve "AS-A (cast) Fly" nesnesi artık çalışmaz, bu nedenle kendi sözdizimini icat etmeniz gerekir (bazı dinamik dillerin bu konuda bir yolu olabilir). Bu, kodunuzu daha hantal hale getirebilir.

7) Uçma özelliğini, uçamayan bir şey için net bir çıkış yolu sağlayacak şekilde tanımlayın. Fly.getNumberOfWings () 0 döndürür. Fly.fly (direction, currentPotinion) uçuştan sonra yeni pozisyonunu döndürürse, Penguin.fly () geçerli olan Pose'u değiştirmeden döndürebilir. Teknik olarak çalışan bir kodla bitebilirsiniz, ancak bazı uyarılar var. Birincisi, bazı kodların bariz bir "hiçbir şey yapma" davranışına sahip olmayabilir. Ayrıca, birisi x.fly () öğesini çağırırsa , yorumun fly () 'in hiçbir şey yapmasına izin verilmediğini söylese bile bir şey yapmasını beklerler . Son olarak, penguen IS-A Flying, programcı için kafa karıştırıcı olabilen, yine de gerçek haline dönecekti.

8) 5) yapın, ancak çoklu kalıtım gerektiren durumları aşmak için kompozisyon kullanın. Statik bir dil için tercih edeceğim seçenek bu, çünkü 6) daha hantal görünüyor (ve daha fazla nesneye sahip olduğumuz için daha fazla bellek gerektiriyor). Dinamik bir dil 6) daha az hantal hale getirebilir, ama 5'ten daha az hantal olacağından şüpheliyim).


0

Temel sınıfta varsayılan bir davranış tanımlayın (sanal olarak işaretleyin) ve zorunlu olarak geçersiz kılın. Bu şekilde her kuş "uçabilir".

Penguenler bile uçar, buzun üzerinde sıfır yükseklikte süzülür!

Uçma davranışı gerektiği gibi geçersiz kılınabilir.

Diğer bir olasılık ise bir Fly Arayüze sahip olmaktır. Bütün kuşlar bu arayüzü kullanmayacak.

class eagle : bird, IFly
class penguin : bird

Özellikler kaldırılamaz, bu nedenle tüm kuşlar için hangi özelliklerin ortak olduğunu bilmek önemlidir. Ortak özelliklerin temel düzeyde uygulandığından emin olmanın daha fazla bir tasarım sorunu olduğunu düşünüyorum.


-1

Sanırım aradığınız kalıp eski güzel polimorfizmdir. Bazı dillerde bir sınıfı bir arayüzden kaldırabilseniz de, Péter Török tarafından verilen nedenlerden dolayı muhtemelen iyi bir fikir değildir. Herhangi bir OO dilinde, davranışını değiştirmek için bir yöntemi geçersiz kılabilirsiniz ve buna hiçbir şey yapmamak da dahildir. Örneğinizi ödünç almak için aşağıdakilerden herhangi birini yapan bir Penguin :: fly () yöntemi sağlayabilirsiniz:

  • hiçbir şey değil
  • istisna atar
  • bunun yerine Penguin :: swim () yöntemini çağırır.
  • penguenin su altında olduğunu iddia eder (su içinde "uçar" yaparlar)

Önceden planlıyorsanız, özellikleri eklemek ve kaldırmak biraz daha kolay olabilir. Örnek değişkenleri kullanmak yerine, özellikleri bir harita / sözlük / ilişkisel dizide saklayabilirsiniz. Fabrika modelini bu tür yapıların standart örneklerini üretmek için kullanabilirsiniz, bu yüzden BirdFactory'den gelen bir Kuş her zaman aynı özelliklerle başlar. Objective-C'nin Anahtar Değer Kodlaması bu tür şeylerin iyi bir örneğidir.

Not: Aşağıdaki yorumlardaki ciddi ders, bir davranışı kaldırmanın geçersiz kılınmasının işe yarayabilir olmasına rağmen, her zaman en iyi çözüm değildir. Bunu önemli bir şekilde yapmanız gerektiğine karar verirseniz, kalıtım grafiğinizin hatalı olduğunu belirten güçlü bir sinyal almalısınız. Miras aldığınız sınıfları yeniden incelemek her zaman mümkün olmuyor, ancak bu genellikle daha iyi bir çözüm olduğunda.

Penguin örneğinizi kullanarak, yeniden uçurmanın bir yolu, uçan yeteneğini Kuş sınıfından ayırmak olacaktır. Tüm kuşlar uçamayacağından, Bird'deki bir fly () yöntemi de dahil olmak üzere uygunsuzdu ve doğrudan sorduğunuz türden bir soruna yol açtı. Bu nedenle, fly () yöntemini (ve belki de kalkış () ve land ()) bir Aviator sınıfına veya arayüzüne (dile bağlı olarak) taşıyın. Bu, hem Kuş hem de Aviator'dan miras alan (ya da Bird'den miras alan ve Aviator'ı uygulayan) bir FlyingBird sınıfı oluşturmanıza olanak sağlar. Penguen doğrudan Kuş'tan miras almaya devam eder ancak Aviator'u değil, böylece sorunu önler. Böyle bir düzenleme, diğer uçan şeyler için de sınıf oluşturmayı kolaylaştırabilir: FlyingFish, FlyingMammal, FlyingMachine, AnnoyingInsect, vb.


2
-1 Penguin :: swim () öğesini çağırmayı önerdiğiniz için. Bu , en az şaşkınlık ilkesini ihlal eder ve her yerde bakım programcılarının adınızı lanetlemelerine neden olur.
DJClayworth

1
@DJClayworth Örnek ilk etapta gülünç tarafta olduğu için, sinek () ve yüzmek () inferred davranışının ihlali için aşağı yönlü görünüyor. Fakat buna gerçekten ciddi bir şekilde bakmak istiyorsanız, diğer yöne gitmeniz ve yüzmeyi () sinek () açısından uygulamanızın daha muhtemel olduğuna katılıyorum. Ördekler ayaklarını çırparak yüzerler; penguenler kanat çırparak yüzüyorlar.
Caleb

1
Sorunun aptalca olduğuna katılıyorum, ancak sorun şu ki, insanların bunu gerçek hayatta yaptıklarını gördüm - nadir işlevsellik uygulamak için "hiçbir şey yapmayan" mevcut çağrıları kullanın. Bu gerçekten kodu mahveder ve genellikle "if (! (Penguen penguen)) fly ();" yazmak zorunda kalır. birçok yerde, hiç kimsenin bir Devekuşu sınıfı yaratmadığını umarken.
DJClayworth

Bu iddia daha da kötü. Hepsinde fly () yöntemine sahip olan bir Kuşlar dizisi varsa, üzerlerinde fly () derken bir iddia hatası istemiyorum.
DJClayworth

1
Penguen belgelerini okumamıştım , çünkü bir dizi Kuşa verildi ve bir Penguenin dizide olacağını bilmiyordum . Fly () dediğimde kuşun uçup gittiğini söyleyen Kuş belgelerini okudum . Bu belgeler kuşun uçamadığı durumlarda bir istisna atılabileceğini açıkça belirtmiş olsaydı, buna izin verirdim. Fly () 'ı çağırmanın bazen yüzmesini sağlayacağını söyleseydim, farklı bir sınıf kütüphanesini kullanmaya değiştirirdim. Ya da çok büyük bir içki için gitti.
DJClayworth
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.