Polimorfizm bağlamında alt tipler için eklenen yöntemler nasıl ele alınır?


14

Polimorfizm kavramını kullandığınızda bir sınıf hiyerarşisi yaratırsınız ve ebeveyn referansını kullanarak, hangi türün nesneye sahip olduğunu bilmeden arayüz işlevlerini çağırırsınız. Bu harika. Misal:

Hayvan koleksiyonunuz var ve tüm hayvan fonksiyonlarını çağırıyorsunuz eatve bir köpek yemi mi yoksa kedi mi olduğunu umursamıyorsunuz. Miras ve sınıftan uygulanan diğerinden daha - Ama aynı sınıf hiyerarşisinde ek sahip hayvanlar var Animalörneğin makeEggs, getBackFromTheFreezedStatevb. Bu nedenle, bazı durumlarda işlevinizde ek davranışları çağırmak için belirli türü bilmek isteyebilirsiniz.

Örneğin, durumunda bu sabah zamanıdır ve o zaman çağrı sadece bir hayvan ise eatbir insandır aksi takdirde, daha sonra ilk çağrı washHands, getDressedve ancak o zaman diyoruz eat. Bu davalar nasıl ele alınır? Çok biçimlilik ölür. Kod kokusu gibi görünen nesnenin türünü bulmanız gerekir. Bu davaları ele almak için ortak bir yaklaşım var mı?


7
Tanımladığınız polimorfizmin tipine alt tip polimorfizm denir , ancak bu tek tür değildir (bakınız Polimorfizm ). Polimorfizm yapmak için bir sınıf hiyerarşisi oluşturmak zorunda değilsiniz (ve aslında kalıtımın alt tip polimorfizmi elde etmek için en yaygın yöntem olmadığını iddia ediyorum, bir arayüz uygulamak çok daha yaygın.
Vincent Savard

24
Yöntemle bir Eaterarabirim tanımlarsanız eat(), o zaman bir istemci olarak, bir Humanuygulamanın ilk çağırması gerekmediğini washHands()ve getDressed()bu sınıfın bir uygulama ayrıntılarını umursamadığını unutmayın . Bir müşteri olarak, bu gerçeği önemsiyorsanız, büyük olasılıkla iş için doğru aracı kullanmıyorsunuzdur.
Vincent Savard

3
Ayrıca, sabahları bir insanın getDressedonlardan önce yapması gerekebileceğini eat, öğle yemeği için durumun böyle olmadığını da düşünmelisiniz . Koşullarınıza bağlı olarak, washHands();if !dressed then getDressed();[code to actually eat]bunu bir insan için uygulamanın en iyi yolu olabilir. Başka bir olasılık, başka şeyler gerektirdiğinde washHandsve / veya getDressedçağrıldığında ne olur ? Varsayalım leaveForWork? Program akışınızı, bundan çok önce çağrılmış olacak şekilde yapılandırmanız gerekebilir.
Duncan X Simpson

1
Tam tipe karşı kontrolün OOP'de bir kod kokusu olabileceğini, ancak FP'de çok yaygın bir uygulama olduğunu unutmayın (yani, ayrımcı bir birliğin türünü belirlemek için desen eşleştirmeyi kullanın ve ardından harekete geçin).
Theodoros Chatzigiannakis

3
Hayvanlar gibi OO hiyerarşilerinin okul örneklerine dikkat edin. Gerçek programlar neredeyse hiç bu kadar temiz taksonomilere sahip değildir. Örneğin, ericlippert.com/2015/04/27/wizards-and-warriors-part-one . Ya da bütün domuzlara gitmek ve tüm paradigmayı sorgulamak istiyorsanız: Nesneye Yönelik Programlama Kötüdür .
jpmc26

Yanıtlar:


18

Bağlı olmak. Ne yazık ki jenerik bir çözüm yok. Gereksinimlerinizi düşünün ve bu şeylerin ne yapması gerektiğini anlamaya çalışın.

Örneğin, sabah farklı hayvanların farklı şeyler yaptığını söylediniz. Bir yöntem getUp()veya prepareForDay()benzeri bir şey tanıtmaya ne dersiniz? Sonra polimorfizm ile devam edebilir ve her hayvanın sabah rutinini yürütmesine izin verebilirsiniz.

Hayvanlar arasında ayrım yapmak istiyorsanız, onları rastgele bir listede saklamamalısınız.

Başka bir şey işe yaramazsa , hayvanlardan tür kesin geri çağrılar alacak bir ziyaretçi gönderebileceğiniz dinamik bir gönderi türüne izin vermek için bir tür hack olan Ziyaretçi Deseni'ni deneyebilirsiniz . Ancak her şey başarısız olursa bu son çare olması gerektiğini vurgulamak istiyorum.


33

Bu iyi bir soru ve OO'nun nasıl kullanılacağını anlamaya çalışırken bir çok insanın sıkıntısı var. Çoğu geliştiricinin bununla mücadele ettiğini düşünüyorum. Keşke çoğunun geçmesini söyleyebilseydim ama durumun bu olduğundan emin değilim. Çoğu geliştirici, benim tecrübelerime göre, sözde OO özellikli çantalar kullanıyor .

İlk olarak, net olalım. Bu senin hatan değil. OO'nun tipik olarak öğretilme şekli oldukça kusurludur. AnimalÖrneğin, IMO birinci fail olduğu. Temel olarak, diyoruz ki, nesneler hakkında konuşalım, ne yapabilirler. Bir Animalkutu eat()ve olabilir speak(). Süper. Şimdi bazı hayvanlar yaratın ve nasıl yediklerini ve konuştuklarını kodlayın. Şimdi OO'yu biliyorsun, değil mi?

Sorun, bunun OO'ya yanlış yönden gelmesidir. Bu programda neden hayvanlar var ve neden konuşmaları ve yemek yemeleri gerekiyor?

Bir Animaltür için gerçek bir kullanım düşünmekte zorlanıyorum . Eminim var ama bir akıl yürütmesi daha kolay olduğunu düşündüğüm bir şeyi tartışalım Trafiği çeşitli senaryolarda modellemek istediğimizi varsayalım. İşte bunu yapabilmek için ihtiyacımız olan bazı temel şeyler.

Vehicle
Road
Signal

Her türlü yaya ve trenle daha derine inebiliriz ama bunu basit tutacağız.

Düşünelim Vehicle. Aracın hangi yeteneklere ihtiyacı var? Bir yolda seyahat etmesi gerekiyor. Sinyallerde durabilmesi gerekir. Kavşaklarda gezinebilmesi gerekir.

interface Vehicle {
  move(Road road);
  navigate(Road... intersection);
}

Bu muhtemelen çok basit ama bir başlangıç. Şimdi. Bir aracın yapabileceği diğer tüm şeylere ne dersiniz? Bir yoldan ve bir hendeğe dönüşebilirler. Bu simülasyonun bir parçası mı? Hayır. Gerek yok. Bazı otomobiller ve otobüsler, sırasıyla sıçramalarına veya diz çökmelerine izin veren hidroliklere sahiptir. Bu simülasyonun bir parçası mı? Hayır. Gerek yok. Çoğu araba benzin yakar. Bazıları bilmiyor. Santral simülasyonun bir parçası mı? Hayır. Gerek yok. Tekerlek boyutu? Buna gerek yok. GPS navigasyon? Bilgi-eğlence sistemi? Onlara ihtiyacım yok.

Yalnızca kullanacağınız davranışları tanımlamanız gerekir. Bu amaçla, onlarla etkileşen koddan OO arabirimleri oluşturmak genellikle daha iyi olduğunu düşünüyorum. Boş bir arabirim ile başlar ve varolmayan yöntemleri çağıran kodu yazmaya başlar. Arayüzünüzde hangi yöntemlere ihtiyacınız olduğunu bu şekilde bilirsiniz. Sonra bunu yaptıktan sonra, bu davranışları uygulayan sınıfları tanımlamaya başlarsınız. Kullanılmayan davranışlar önemsizdir ve tanımlanmaları gerekmez.

OO'nun asıl amacı, daha sonra çağrı kodunu değiştirmeden bu arayüzlerin yeni uygulamalarını ekleyebilmenizdir. İşe yarayan tek yol, arama kodunun ihtiyaçlarının arayüzde ne olduğunu belirlemesi. Daha sonra düşünülebilecek tüm olası şeylerin tüm davranışlarını tanımlamanın bir yolu yoktur.


13
Bu iyi bir cevap. "Bu amaçla, onlarla etkileşen koddan OO arabirimleri oluşturmak genellikle daha iyi olur." Kesinlikle, ve bunun tek yol olduğunu iddia ediyorum. Bir arayüzün kamu sözleşmesini sadece uygulama yoluyla bilemezsiniz, her zaman müşterilerinin bakış açısıyla tanımlanır. (Ve bir yan not olarak, TDD aslında
bununla

@VincentSavard "Tek yol olduğunu iddia ediyorum." Haklısın. Sanırım mutlak olamamamın nedeni, bir kez fikre sahip olduğunuzda, arayüzün dışına çıkabilir ve daha sonra bu şekilde iyileştirebilirsiniz. Sonuçta, pirinç meselelerine indiğinizde, önemli olan tek şey budur.
JimmyJames

@ jpmc26 Belki biraz güçlü bir şekilde ifade edilmiştir. Bunu uygulamanın nadir olduğunu kabul ettiğimden emin değilim. Korkunç bir fikir olduğunu düşündüğüm marker arayüzlerinden başka bu şekilde kullanmıyorsanız arayüzlerin nasıl yararlı olabileceğinden emin değilim.
JimmyJames

9

TL; DR:

Tüm alt sınıflar için geçerli olan bir soyutlama ve yöntemleri düşünün ve ihtiyacınız olan her şeyi kapsar.

Önce eat()örneğinizle kalalım.

İnsan olmanın bir özelliği, yemenin bir ön koşulu olarak, insanların yemekten önce ellerini yıkamak ve giyinmek istiyor. Birinin seninle kahvaltı etmesini istiyorsan, ellerini yıkamasını ve giyinmesini söylemezsin , davet ettiğinde bunu kendi başlarına yaparlar ya da "Hayır, gelemem bitti, ellerimi yıkamadım ve henüz giyinmedim ".

Yazılıma geri dön:

Bir Humanörnek önkoşullar olmadan yemek yemeyeceği için, Human's eat()yöntemi yapmak washHands()ve getDressed()bu yapılmadıysa olurdu. Bu eat()tuhaflık hakkında bilmek arayan olarak sizin işiniz olmamalıdır . İnatçı-insanın alternatifi, önkoşullar karşılanmazsa, sizi hayal kırıklığına uğratırsa, ancak en azından yemenin işe yaramadığı konusunda bilgilendirilirse, bir istisna atmak olacaktır ("yemeye hazır değilim!").

Ne olmuş makeEggs()?

Düşünme şeklinizi değiştirmenizi tavsiye ederim. Muhtemelen tüm varlıkların planlanan sabah görevlerini yerine getirmek istiyorsunuz. Yine, arayan olarak görevlerinin ne olduğunu bilmek sizin işiniz olmamalıdır. Bu yüzden doMorningDuties()tüm sınıfların uyguladığı bir yöntem öneriyorum .


Bu yanıta katılıyorum. Narek kod kokusu konusunda haklı. Bu koklamak arayüz tasarımı, bu yüzden bunu ve senin iyi düzeltmek.
Jonathan van de Veen

Bu cevabın tanımladığı şey genellikle Liskov İkame İlkesi olarak adlandırılır .
Philipp

2

Cevap oldukça basit.

Beklediğinizden daha fazlasını yapabilen nesneler nasıl işlenir?

Bunu idare etmenize gerek yok çünkü hiçbir amaca hizmet etmeyecekti. Bir arayüz genellikle nasıl kullanılacağına bağlı olarak tasarlanır. Arayüzünüz ellerin yıkanmasını tanımlamazsa, arayüz arayan olarak umursamazsınız; eğer yapsaydın, onu farklı şekilde tasarlarsın.

Örneğin, sabah vakti olması ve sadece bir hayvan olması durumunda yemek diyorsunuz, aksi takdirde insansa, ilk önce washHands, getDressed ve sadece sonra eat diyoruz. Bu davalar nasıl ele alınır?

Örneğin, sözde kodda:

interface IEater { void Eat(); }
interface IMorningRoutinePerformer { void DoMorningRoutine(); }
interface IAnimal : IEater, IMorningPerformer;
interface IHuman : IEater, IMorningPerformer; 
{
  void WashHands();
  void GetDressed();
}

void MorningTime()
{
   IList<IMorningRoutinePerformer> items = Service.GetMorningPerformers();
   foreach(item in items) { item.DoMorningRoutine(); }
}

Şimdi uygulamak IMorningPerformeriçin Animalsadece yeme gerçekleştirmek ve Humanaynı zamanda ellerini yıkamak için ve giyin onu yerine getirirler. MorningTime yönteminizin arayanı, insan veya hayvan ise daha az bakım yapabilir. Tek istediği, her nesnenin OO sayesinde takdire şayan bir şekilde yaptığı sabah rutini.

Çok biçimlilik ölür.

Yoksa öyle mi?

Nesnenin türünü bulmanız gerekiyor

Neden bunu varsayıyoruz? Bence bu yanlış bir varsayım olabilir.

Bu davaları ele almak için ortak bir yaklaşım var mı?

Evet, genellikle özenle tasarlanmış sınıf veya arayüz hiyerarşisiyle çözülür. Yukarıdaki örnekte, örneğinizin verdiğiniz gibi çelişen hiçbir şey olmadığını unutmayın, ancak muhtemelen memnun olmayacaksınız, çünkü yazma anından itibaren soruda yazmadığınız bazı varsayımlar yaptınız. ve bu varsayımlar muhtemelen ihlal edilmiştir.

Varsayımlarınızı sıkılaştırarak bir tavşan deliğine gitmek mümkündür ve cevapları hala tatmin etmek için değiştiriyorum, ancak bunun yararlı olacağını düşünmüyorum.

İyi sınıf hiyerarşileri tasarlamak zordur ve iş alanınız hakkında çok fazla bilgi gerektirir. Karmaşık alanlar için, iş alanlarındaki farklı varlıkların yeterli bir modele ulaşana kadar nasıl etkileşimde bulunduklarını anlamalarını sağladıkları için iki, üç veya daha fazla yinelemeden geçer.

Basit hayvan örneklerinin eksik olduğu yer burasıdır. Basit öğretmek istiyoruz, ancak çözmeye çalıştığımız sorun daha derine inene kadar açık değil, daha karmaşık düşünceler ve alanlar var.


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.