Vərəsəlik üzərində kompozisiya seçimini üstün edirsiniz?

Nə mirasdan ibarətdir? Hər bir yanaşma üçün ayrım nədir? Kompozisiya üzərində miras seçmək üçün nə vaxt lazımdır?

1365
08 сент. Oxuyuruq 08 sentyabr. 2008-09-08 04:58 '08 at 04:58 2008-09-08 04:58
@ 34 cavab
  • 1
  • 2

Vərəsəlik üzərində kompozisiya üstünlük təşkil edir, çünki daha sonra daha asan və asanlıqla dəyişdirilir, amma kompozisiya üsulunu hər zaman istifadə etmir. Kompozisiya ilə Enjeksiyon / Setters Bağımlılığı istifadə edərək, uçuşda davranışı dəyişdirmək asandır. Məvacib daha sərtdir, çünki əksər dillər birdən çox növü əldə etməyə imkan vermir. Beləliklə, siz TipA'dan əldə etdiyiniz kimi qaz daha az pişirilir.

Yuxarıda göstərmiş olduğum turşu testi:

  • TypeB tip A tipli (bütün ictimai metodları ən azı) A tipində ifşa etmək istəyirmi? Mö'cüzəni göstərir.

məsələn. Biplane Cessna, daha çox olmasa, təyyarənin tam interfeysini açacaq. Bu, təyyarə ala bilmək üçün əlverişlidir.

  • TipB, TypeA tərəfindən göstərilən davranışın yalnız bir hissəsini istəyirmi? Kompozisiya ehtiyacını göstərir .

məsələn. Quş yalnız uçağın uçan davranışına ehtiyac ola bilər. Bu halda, interfeys / sinif / ikisi kimi çıxarmaq və hər iki sinifin üzvü olmaq mantiqidir.

Yeniləmə: Mənim cavabımı geri qaytardı və indi Barbara Liskovun xüsusi bir qeyd etmədən tamamlanmadığı görünür. Liskov Əvəz Prinsipi "Mən bu tipə sahib olmalıyam?"

1033
10 сент. Cavab Gişu tərəfindən verilir. 2008-09-10 06:04 '08 at 06:04 2008-09-10 06:04

Dəyişikliklərin necə əlaqəli olduğunu düşünün. "Avtomobil" "mühərrik, adam" "ad" və s.

Vərəsəlik kimi düşünün. "Avtomobil" - "vasitə, insan" - "məməlilər və s.".

border=0

Mən bu yanaşmaya aid deyiləm. Mən onu Stiv McConnell tərəfindən hazırlanmış kodun ikinci nəşri ilə düzbucaq etdi, Bölmə 6.3.

353
08 сент. Nick Zalutskiy tərəfindən verilmiş cavab Sep 08 2008-09-08 05:09 '08 at 5:09 pm 2008-09-08 05:09

Fərqi başa düşsəniz, izah etmək daha asandır.

Prosedur qaydaları

Bunun bir nümunəsi PHP-nin dərslərdən istifadə etməməsidir (xüsusən PHP5-dən əvvəl). Bütün məntiq bir sıra funksiyalar kimi kodlanır. Yardımçı funksiyaları olan və s. Olan digər faylları əlavə edə bilərsiniz və funksiyaya görə məlumatların ötürülməsi yolu ilə biznes mantığınıza rəhbərlik edə bilərsiniz. Bu proqramlar böyüdükcə idarə etmək çox çətin ola bilər. PHP5 daha çox obyekt yönümlü dizayn təklif edərək bunu düzəltməyə çalışır.

miras

Bu, dərslərin istifadəsinə həvəsləndirir. Miras OO dizaynının üç prinsiplərindən biridir (miras, polimorfizm, encapsulation).

 class Person { String Title; String Name; Int Age } class Employee : Person { Int Salary; String Title; } 

Bu, işdə miras. "İşçi" - "Üzdən və ya şəxsdən miras qaldı". Bütün miras əlaqələri "a-a" əlaqələridir. Işçi həmçinin Başlıq əmlakını şəxsdən, yəni, Employee.Title şəxsdən deyil, əməkdaşı üçün İşin adı qaytarır.

Tərkibi

Kompozisiya mirasdan üstündür. Sadəcə olaraq, siz:

 class Person { String Title; String Name; Int Age; public Person(String title, String name, String age) { this.Title = title; this.Name = name; this.Age = age; } } class Employee { Int Salary; private Person person; public Employee(Person p, Int salary) { this.person = p; this.Salary = salary; } } Person johnny = new Person ("Mr.", "John", 25); Employee john = new Employee (johnny, 50000); 

Kompozisiya adətən istifadə edir və ya istifadə edir. Burada İşçi sinfi şəxsdir. Bir şəxsdən miras alınmır, amma bunun əvəzinə ona keçilən bir şəxs obyekti alır, buna görə də "Şəxsiyyət" var.

Vərəsəlik üzərində tərkibi

İndi menecer tipini yaratmaq istədiyinizi söyləyin, əldə etdiyiniz nəticə:

 class Manager : Person, Employee { ... } 

Ancaq bu nümunə yaxşı işləyəcək, amma şəxs və işçi Title ? Manager.Title "Əməliyyat meneceri" və ya "cənab" i qaytarmalıdır? Kompozisiya baxımından bu qeyri-müəyyənlik yaxşı işlənmişdir:

 Class Manager { public string Title; public Manager(Person p, Employee e) { this.Title = e.Title; } } 

Müdirin obyekti işçi və şəxsdən ibarətdir. Başlıq davranışı işçi tərəfindən alınır. Bu açıq tərkib digər şeylər arasında qeyri-müəyyənliyi aradan qaldırır və az səhvlərlə qarşılaşacaqsınız.

180
21 мая '09 в 10:54 2009-05-21 10:54 Cavab aleemb 21 may tarixində 10 : 34-da verilmişdir 2009-05-21 10:54

Vərəsəliklə təmin edilən bütün etibarsız üstünlükləri ilə bəzi mənfi cəhətləri var.

Vərəsənin mənfi cəhətləri:

  • Üstünlüklərdən miras qalan tətbiqin iş vaxtında dəyişdirə bilməzsiniz (açıq-aydın, çünki miras tərtib vaxtında müəyyən edilir).
  • Vərəsəlik, ana sinif tətbiqinin detalları üçün bir alt sinif təmin edir, buna görə də tez-tez mirasçılığın encapsulation (həqiqətən, həyata keçirilməsinə deyil, interfeyslərə diqqət etmək lazımdır ki, subclass yenidən istifadə etmək deyil, həmişə üstünlük) deməkdir.
  • Vərəsəliklə təmin edilən sıx birləşmə, üstün tətbiqin hər hansı bir dəyişməsi alt sinifə dəyişiklik gətirə biləcəyini sübutun tətbiqinə çox aid olan alt sinifin həyata keçirilməsini təmin edir.
  • Subclassing tərəfindən həddindən artıq yenidən istifadə miras çox dərin və çox karmaşık müəyyən edə bilər.

Digər tərəfdən, obyektin tərkibi digər obyektlərə istinadlar verən obyektlərdən istifadə zamanı müəyyən edilir. Bu vəziyyətdə bu obyektlər bir-birinin qorunan məlumatlarına (encapsulation müdaxilə etmədən) çatmaz və bir-birinə hörmət etməlidirlər. Bu vəziyyətdə də tətbiqin tətbiq olunduğu bağımlılıklar miras halında olduğundan daha az olacaqdır.

96
21 мая '09 в 11:34 2009-05-21 11:34 Cavab Galilyou 21 May '09 saat 11:34 'da verilir 2009-05-21 11:34

Vərəsəlikdən kompozisiya seçmək üçün başqa, çox praktik bir səbəb domen modelinizlə və əlaqəli məlumat bazası ilə müqayisə edilir. SQL modelinin mirasını müqayisə etmək çox çətindir (hər zaman istifadə edilməyən, görünüşlərdən istifadə edən sütunlar yaratmaq və s. Kimi) hər hansı bir hacker işəgötürənini alırsınız. Bəzi ORML'lar bununla mübarizə aparmağa çalışırlar, lakin həmişə tez bir zamanda çətinləşir. Kompozisiya asanlıqla iki masa arasındakı xarici əsas əlaqələrdən istifadə etməklə modelləşdirilə bilər, lakin miras daha çox mürəkkəbdir.

77
08 сент. Tim Howland'a cavab 08 Sep. 2008-09-08 05:48 '08 at 05:48 2008-09-08 05:48

Bir sözlə, "mən mirasın üstündəki kompozisiyanı üstün tuturam" deyirəm, tez-tez mənə "Coca-Cola üzərində kartof seçirəm" kimi səslənir. Vərəsəlik və kompozisiya yerləri var. Fərqi anlamaq lazımdır, sonra bu sual ortadan qalxacaq. Mənim üçün bu, "əgər mirasdan istifadə edəcəyəmsə, yenidən düşünün, ehtimal ki, kompozisiya lazımdır".

Siz Coca-Cola kartofuna üstünlük vermək və içmək istədiyin zaman Coca-Cola kartofuna üstünlük verməlisiniz.

Bir alt sinif yaratmaq, superklass üsullarını çağırmaq üçün yalnız rahat bir yoldan daha çox deməkdir. Üstün sinif - üstün sinif olaraq istifadə ediləcəyi zaman bir sinif həm struktur, həm də funksional olsa və siz onu istifadə edəcəyi halda mirasdan istifadə etməlisiniz. Əgər deyilsə, miras deyil, başqa bir şeydir. Tərkibi, obyektlərinizin başqa bir şeydən meydana gəldiyi və ya onlarla əlaqəsi olan bir şeydir.

Beləliklə, mənim üçün, əgər kimsə mirasa və ya tərkibə ehtiyac duymadığını bilmirsə, əsl problem onun içmək və ya yemək istəmədiyini bilmir. Problem alanınızı daha çox düşünün, daha yaxşı başa düş.

64
09 мая '09 в 1:29 2009-05-09 01:29 Pavel Feldmana 09 may 2009-cu il tarixdə '09 'da cavab verən 2009-05-09 01:29

Mərhum xüsusilə prosessual yerdən olduqca cazibədardır və tez-tez aldadıcı zərif görünür. Demək istəmirəm ki, etmək lazım olan bütün funksiyaların bir qismini digər bir sinifə əlavə edir, sağ mı? Problemlərdən biri mirasın olmasıdır

ola biləcək ən pis ünsürdür

Sizin əsas sinifiniz encapsulation pozur, tətbiqin detallarını qorunan üzvlər kimi alt siniflərə keçirir. Bu sisteminizi çətin və kövrək edir. Ancaq daha çox faciəli bir qüsur, bütün baqajı və miras zəncirinin rəyini aparan yeni alt sinifdir.

Məqalə : Evilin mirasçılığı: epik səhv DataAnnotationsModelBinder , bunun nümunəsini C # kodundan keçir. Bu kompozisiya istifadə edildikdə və necə yenidən təşkil edilə biləcəyi təqdirdə mirasın istifadəsini göstərir.

54
23 янв. Jan 23-də Mike Valenty tərəfindən verilmiş cavab 2010-01-23 22:55 '10 10:55 pm 2010-01-23 22:55

Java və ya C # -də obyekt yaratıldıqdan sonra növünü dəyişə bilmir.

Beləliklə, əgər obyektiniz başqa bir obyekt kimi görünsə və ya obyektin və ya vəziyyətin vəziyyətindən asılı olaraq fərqli davranarsa, Tərkibi : DövlətStrateji Dizayn Nümunələrinə baxın.

Nesne eyni cür olmalıdırsa, Kalıtsı istifadə edin və ya interfeyslər tətbiq edin.

37
08 сент. Cavab Sung Kim tərəfindən Sep 08 verilir 2008-09-08 05:25 '08 at 5:25 2008-09-08 05:25

Şəxsən, həmişə mirasdan ibarət kompozisiya seçimini öyrəndim. Kompozisiya ilə həll edə bilməyəcəyiniz miras ilə həll edə biləcək bir proqram problemi ilə əlaqədar heç bir problem yoxdur; bəzi hallarda interfeyslər (Java) və ya protokolları (Obj-C) istifadə etmək olar. C ++ belə bir şey bilmirsə, soyut bazlı dərslərdən istifadə etməlisiniz, yəni siz C ++-da mirasdan tamamilə xilas ola bilməzsiniz.

Tərkibi tez-tez daha mantıksaldır, daha yaxşı soyutma, daha yaxşı encapsulasiya, daha yaxşı kodu yenidən istifadə etmək (xüsusən də çox böyük layihələrdə) və istənilən yerdə kodunuzda təcrid edilmiş bir dəyişiklik etdiyiniz üçün bir məsafədə bir şey qırmaq ehtimalı azdır. O, "bir sinif dəyişikliyinə bir çox səbəb olmamalıdır" kimi ümumiləşdirilmiş olan "tək məsuliyyət prinsipi" üçün də yardımı asanlaşdırır. Bu, hər bir sinfi müəyyən bir məqsəd üçün mövcuddur və yalnız onun məqsədi ilə birbaşa əlaqəli olan üsullara malik olmalıdır. Bundan əlavə, çox kiçik miras ağacına malik olmağınız, layihəniz həqiqətən də böyük olmağa başlamış olsa belə, daha asanlaşdırır. Bir çox insanlar mirasın gerçək dünyasını olduqca yaxşı təmsil etdiyinə inanır, amma bu doğru deyil. Gerçək dünya mirasdan daha çox kompozisiya istifadə edir. Əlinizdə saxlaya biləcəyiniz demək olar ki, hər bir real dünya obyekti daha kiçik real dünyada başqa obyektlərdən ibarət idi.

Bununla birlikdə tərkibə çatışmazlıqlar var. Heç bir mirasdan qaçırsanız və yalnız kompozisiyaya diqqət yetirsəniz, mirasdan istifadə etmədiyiniz təqdirdə bir neçə əlavə kod xəttini yazmanız lazım olduğunu görəcəksiniz. Bəzən özünüzü təkrar etmək lazımdır və bu, DRY (DRY = Özünü Təkrar etməyin) prinsipini pozur. Bundan əlavə, kompozisiya tez-tez nümayəndə heyətini tələb edir və metod başqa bir obyektin başqa bir üsulunu bu çağırışı əhatə edən başqa bir kod olmadan çağırır. Belə "ikiqat çağırış" (asanlıqla üçqat və ya dördqat çağırışlara və hətta daha da uzadıla biləcək) zənglər, valideyninizin üsulunu sadəcə miras aldığı mirasdan daha pisdir. Devralmış bir üsula çağırış qeyri-mirasız bir üsula çağırış kimi sürətli ola bilər və ya bir az yavaş ola bilər, lakin adətən iki ardıcıl üsul çağırışından daha tez olur.

Çox OO dilləri birdən çox mirasın alınmasına icazə vermədiyini fərq etmiş ola bilərsiniz. Bir çox mirasın həqiqətən sizə bir şey satın ala biləcəyi bir neçə hal olsa da, bunlar qayda deyil, istisnalardır. "Birdən çox miras bu problemi həll etmək üçün həqiqətən maraqlı bir funksiya olacaq" deyə düşündüyünüz bir vəziyyətlə qarşılaşdığınız zaman, odur ki, mirasın tamamilə yenidən nəzərdən keçirilməsi lazım olan bir nöqtədə olur, çünki bu da bir neçə əlavə kod xətti Kompozisiyaya əsaslanan bir həll adətən daha zərif, çevik və gələcək olacaqdır.

Mükəmməllik həqiqətən gözəl bir xüsusiyyətdir, amma son bir neçə il ərzində çoxdan istifadə olunduğundan qorxuram. İnsanlar, həqiqətən, bir dırnaq, bir vida və ya tamamilə fərqli bir şey olub-olmamasından asılı olmayaraq, hamısına nail ola biləcək bir çəkic kimi baxdı.

28
31 янв. Cavab 31 yanvarda Mecki tərəfindən verilir . 2013-01-31 22:34 '13 at 10:34 pm 2013-01-31 22:34

Mən burada qənaətbəxş bir cavab tapmadım, buna görə yenə də yazdım.

Nəyə görə "miras üzərində kompozisiya seçimini" başa düşmək üçün ilk növbədə bu qısaldılmış deyimdə nəzərdə tutulan fərziyyəyə qayıtmalıyıq.

Mirasın iki üstünlüyü var: subtip və subclasses

  • Subtipping , növü (interfeys) imza, yani API dəstini uyğunlaşdırmaq deməkdir və alt hissələrin siyasətçiliyinə nail olmaq üçün imza bir hissəsini yenidən təyin edə bilərsiniz.

  • Alt sinif üsulların həyata keçirilməsidir.

İki üstünlüklə miras üçün iki fərqli məqsəd istifadə olunur: subtyping və kodu yenidən istifadə üçün yönəldilmişdir.

Kodun təkrar istifadə edilməsi tək hədəfdirsə, subclassing, ehtiyac duyduğundan daha bir şey verə bilər, yəni ana sinifin bəzi ictimai metodları uşaq sinifinə çox əhəmiyyət vermir. Bu halda, miras üzərində üstünlük tərkibinin əvəzinə bir kompozisiya tələb olunur. Bu da "is-a" və "is-a" konsepsiyasına əsaslanır.

Beləliklə, yalnız subtipinq nəzərdə tutulduğunda, yəni yeni bir sinfi daha sonra polimorfik bir şəkildə istifadə etmək üçün, mirasın və ya tərkibin seçilməsi problemi ilə qarşılaşırıq. Bu fərziyyə müzakirə olunan qısaldılmış deyimdə qalır.

Alt növü növü imza ilə uyğun olmalıdır, yəni tərkibi hər zaman ən azı az sayda növü API göstərməlidir. İndi güzəşt tətili:

Yuxarıda verilmiş uzlaşmaları nəzərə alaraq, biz mirasla bağlı kompozisiyanı üstün edirik. Bununla yanaşı, yaxından əlaqəli dərslər üçün, yəni örtülü kodun təkrar istifadə edilməsi həqiqətən fayda verərsə və ya açıq recursionun sehrli gücü arzuolunmazsa, seçmə miras olmalıdır.

27
14 сент. Cavab lcn 14 sep verilir . 2015-09-14 08:22 '15 'də 8:22' de 2015-09-14 08:22

Mənim ümumi qayda: mirasdan istifadə etməzdən əvvəl kompozisiya hissi olub-olmadığını düşünün.

Səbəb Alt siniflər, adətən, daha mürəkkəblik və uyğunluq deməkdir, yəni. səhv etmədən dəyişdirmək, saxlamaq və ölçmək daha çətin.

Günəşdən Tim Boudreau'dan daha dəqiq və dəqiq bir cavab :

Vərəsəlikdən istifadə etdiyin ümumi problemlər, gördüyüm kimi, bunlardır:

  • Günahsız hərəkətlər gözlənilməz nəticələrə səbəb ola bilər. Bunun bir klassik nümunəsi subclass nümunələrinin sahələri başlamazdan əvvəl, bir superclass konstruktordan imtina üsulları çağırır. İdeal bir dünyada heç kim bunu edə bilməz. Bu mükəmməl bir dünya deyil.
  • O, alt siniflər üçün üsul çağırışları və s. Haqqında fərziyyələr vermək üçün sapqın temptasiyaları təklif edir. - superklass zamanla inkişaf edə bilərsə, bu cür ehtimallar sabit qalmayacaqdır. Mənim toaster və qəhvə hazırlayıcı analoguna baxın.
  • Dərslər daha da çətinləşir - hər zaman sizin superklassın konstruktorunuzda nə olduğunu və ya nə qədər istifadə edəcəyini bilmirsiniz. Beləliklə, günahsız bir potensial işıq obyektinin qurulması düşündüyünüzdən daha bahalı ola bilər və bu zaman superclass inkişaf edərsə, zaman keçdikcə dəyişə bilər
  • Alt siniflərin partlamasını təşviq edir. Classloading zaman alır, daha çox sinif xərcləri yaddaş edir. NetBeans miqyaslı bir tətbiqi ilə məşğul olmadığı müddətcə bu problem olmaya bilər, ancaq burada menyudan ilk görünüş dərslərin kütləvi yüklənməsinə başlayır, çünki, məsələn, yavaş menyularla problemlərimiz var idi. Bunu daha çox deklarativ sintaksis və digər üsullara yönəltməklə müəyyən etdik, ancaq düzəliş etmək üçün vaxt lazımdır.
  • Daha sonra bir şey dəyişdirməyi çətinləşdirir - əgər bir sinifdə ictimaiyyətə çatdırsanız, superclass əvəz alt sinifləri əvəz edəcək - bu, ümumi açıqlamanı etdikdən sonra evli olduğunuz bir seçimdir. Ona görə də, əgər superklassın əsl funksiyasını dəyişdirməsə, Daha sonra daha çox şeyi dəyişmək azadlığı, ehtiyac duyduğunuz şeyi genişləməyəsiniz. Məsələn, JPanel alt sinifini götür - bu adətən yanlışdır; alt sinif ictimaiyyətin bir yerində olsaydı, bu qərara qayıtma şansınız olmayacaqdır. JComponent getThePanel () kimi mövcuddursa, hələ də bunu edə bilərsiniz (ipucu: API daxilindəki komponentlər üçün modelləri ifşa et).
  • Иерархии объектов не масштабируются (или их масштабирование намного сложнее, чем планирование вперед) - это классическое "слишком много слоев", проблема. Я расскажу об этом ниже, и как шаблон AskTheOracle может (хотя это может оскорбить пуристов ООП).

...

Мое занятие, что делать, если вы разрешаете наследование, которое вы можете взять с солью:

  • Не показывать поля, кроме констант
  • Методы должны быть абстрактными или окончательными.
  • Вызов no методов из конструктора суперкласса

...

все это меньше относится к небольшим проектам, чем к крупным, и меньше для частных классов, чем для публичных

20
ответ дан Peter Tseng 21 мая '12 в 4:59 2012-05-21 04:59

Наследование очень мощное, но вы не можете его заставить (см. проблема с круглым эллипсом ). Если вы действительно не можете быть полностью уверены в истинном соотношении подтипов "is-a", тогда лучше всего пойти с композицией.

17
ответ дан yukondude 08 сент. '08 в 5:29 2008-09-08 05:29

Наследование создает прочную взаимосвязь между подклассом и суперклассом; подкласс должен быть осведомлен о деталях реализации суперкласса. Создание суперкласса намного сложнее, когда вам нужно подумать о том, как его можно расширить. Вы должны тщательно документировать инварианты классов и указывать, какие другие методы переопределяют методы, используемые внутри.

Наследование иногда полезно, если иерархия действительно представляет собой aa-relationship. Он относится к принципу открытого закрытия, который гласит, что классы должны быть закрыты для модификации, но открыты для расширения. Таким образом, вы можете иметь полиморфизм; иметь общий метод, который имеет дело с супер-типом и его методами, но с помощью динамической отправки вызывается метод подкласса. Это гибко и помогает создавать косвенные действия, которые необходимы в программном обеспечении (меньше знать о деталях реализации).

Наследование легко переоценивается и создает дополнительную сложность с жесткими зависимостями между классами. Также понимание того, что происходит во время выполнения программы, довольно сложно из-за слоев и динамического выбора вызовов методов.

Я бы предложил использовать компоновку по умолчанию. Он более модульный и дает преимущество позднего связывания (вы можете динамически изменять компонент). Также проще тестировать вещи по отдельности. И если вам нужно использовать метод из класса, вы не должны быть в определенной форме (принцип замены Лискова).

14
ответ дан egaga 21 мая '09 в 11:11 2009-05-21 11:11

Вам нужно взглянуть на Принцип замены Лискова у дяди Боба SOLID принципы дизайна класса.:)

13
ответ дан nabeelfarid 05 окт. '10 в 14:57 2010-10-05 14:57

Предположим, что у самолета есть только две части: двигатель и крылья.
Тогда есть два способа разработать класс воздушных судов.

 Class Aircraft extends Engine{ var wings; } 

Теперь ваш самолет может начать с закрепленных крыльев и менять их на поворотные крылья на лету. По существу, двигатель с крыльями. Но что, если я захочу изменить двигатель "на лету"?

Либо базовый класс Engine предоставляет мутатору, чтобы изменить его свойства, или я редизайна Aircraft как:

 Class Aircraft { var wings; var engine; } 

Теперь я могу заменить свой движок на лету.

11
ответ дан dharm0us 29 нояб. '10 в 13:54 2010-11-29 13:54

Почему предпочитают композицию над наследованием?

См. Другие ответы.

Когда следует выбирать наследование над составом?

Всякий раз, когда предложение "Бар - это Foo, а Bar может делать все, что может сделать Foo", имеет смысл.

Обычная мудрость гласит, что если предложение "Бар является Foo" имеет смысл, то это хороший намек на то, что выбор наследования является подходящим. Например, собака - это животное, поэтому наличие наследования у собак из Animal - это, вероятно, хороший дизайн.

К сожалению, этот простой тест "is-a" не является надежным. Проблема Circle-Ellipse - отличный контрастный пример: даже если круг является эллипсом, это плохая идея, чтобы класс Circle наследовался от Ellipse, потому что есть вещи, которые могут делать эллипсы, но круги не могут. Например, эллипсы могут растягиваться, но круги не могут. Таким образом, хотя вы можете иметь ellipse.stretch() , вы не можете иметь circle.stretch() .

Вот почему лучший тест: "Бар - это Foo, а Bar может делать все, что может сделать Foo". Это действительно означает, что Foo можно использовать полиморфно. Тест "is-a" является лишь необходимым условием полиморфного использования и обычно означает, что все геттеры Foo имеют смысл в Bar. Дополнительное тестирование "can-do-everything" означает, что все сеттеры Foo также имеют смысл в Bar. Этот дополнительный тест обычно терпит неудачу, когда класс Bar "is-a" Foo, но добавляет к нему некоторые ограничения, и в этом случае вы не должны использовать наследование, потому что Foo не может использоваться полиморфно. Другими словами, наследование заключается не в совместном использовании свойств, а в обмене функциональными возможностями. Производные классы должны расширять функциональность базовых классов, а не ограничивать их.

Это эквивалентно Принципу замещения Лискова :

Функции, которые используют указатели или ссылки на базовые классы, должны иметь возможность использовать объекты производных классов, не зная об этом

10
ответ дан Boris 04 июня '16 в 1:23 2016-06-04 01:23

"Предпочитайте композицию над наследованием" - это принцип дизайна, который говорит, что не злоупотреблять наследованием, когда он не подходит.

Если между двумя объектами не существует иерархии реального мира, не используйте наследование, а вместо этого используйте композицию. Композиция представляет отношения "HAS".

məsələn. Автомобиль HAS A Колеса, тело, двигатель и т.д. Но если вы наследуете здесь, он становится CAR IS A Wheel - что неверно.

Дополнительные пояснения см. в предпочитают состав над наследованием

9
ответ дан Ranganatha 21 мая '15 в 10:52 2015-05-21 10:52

Эти два способа могут жить вместе прекрасно и фактически поддерживать друг друга.

Композиция просто играет на модуле: вы создаете интерфейс, похожий на родительский, создаете новый объект и делегируете его. Если эти объекты не должны знать друг друга, это вполне безопасный и простой в использовании состав. Здесь так много возможностей.

Однако, если родительскому классу по какой-то причине требуется доступ к функциям, предоставляемым "дочерним классом" для неопытного программиста, может показаться, что это отличное место для использования наследования. Родительский класс может просто называть его собственным абстрактным "foo()" , который перезаписывается подклассом, а затем он может дать значение абстрактной базе.

Похоже на хорошую идею, но во многих случаях лучше просто дать классу объект, который реализует foo() (или даже задает значение, предоставленное foo() вручную), чем наследование нового класса с некоторой базы класс, который требует указания функции foo().

Niyə?

Поскольку наследование - это плохой способ перемещения информации .

Здесь композиция имеет реальное преимущество: отношения могут быть отменены: "родительский класс" или "абстрактный рабочий" могут агрегировать любые конкретные "дочерние" объекты, реализующие определенный интерфейс + , любой ребенок может быть установлен в любом другом тип родителя, который принимает его тип . И может быть любое количество объектов, например MergeSort или QuickSort могут сортировать любой список объектов, реализующих абстрактный Compare -interface. Или иначе: любая группа объектов, которые реализуют "foo()" и другую группу объектов, которые могут использовать объекты, имеющие "foo()" , могут играть вместе.

Я могу думать о трех реальных причинах использования наследования:

  • У вас много классов с одним и тем же интерфейсом , и вы хотите сэкономить время на их запись.
  • Вы должны использовать тот же базовый класс для каждого объекта
  • Вам нужно изменить частные переменные, которые не могут быть общедоступными в любом случае

Если они верны, то, вероятно, необходимо использовать наследование.

Нет ничего плохого в использовании причины 1, очень хорошо иметь сплошной интерфейс на ваших объектах. Это можно сделать с помощью композиции или с наследованием, без проблем - если этот интерфейс прост и не изменяется. Обычно наследование здесь довольно эффективно.

Если причина - номер 2, она становится немного сложной. Вам действительно нужен только тот же базовый класс? В общем, просто использование одного и того же базового класса недостаточно хорошо, но это может быть требование вашей структуры, соображения дизайна, которых нельзя избежать.

Однако, если вы хотите использовать частные переменные, случай 3, то у вас могут быть проблемы. Если вы считаете глобальные переменные небезопасными, вам следует рассмотреть возможность использования наследования, чтобы получить доступ к закрытым переменным, также небезопасным . Имейте в виду, что глобальные переменные не так уж плохи - базы данных по существу представляют собой большой набор глобальных переменных. Но если вы справитесь с этим, то это будет очень хорошо.

6
ответ дан Tero Tolonen 16 апр. '13 в 23:15 2013-04-16 23:15

Если вы хотите "скопировать" /выдать API базового класса, вы используете наследование. Когда вы хотите только "скопировать" функциональность, используйте делегирование.

Один из примеров: вы хотите создать Stack из списка. Стек имеет только pop, push и peek. Вы не должны использовать наследование, учитывая, что вам не нужны функции push_back, push_front, removeAt и др. В стеке.

6
ответ дан Anzurio 07 мая '09 в 4:43 2009-05-07 04:43

Простым способом понять это было бы то, что наследование должно использоваться, когда вам нужен объект вашего класса, чтобы иметь тот же интерфейс, что и его родительский класс, так что он может поэтому рассматриваться как объект родительского класса (приведение к базовому типу). Более того, вызовы функций для объекта производного класса будут оставаться одинаковыми везде в коде, но конкретный метод для вызова будет определяться во время выполнения (т.е. Реализация низкого уровня отличается, интерфейс высокого уровня остается тем же).

Композиция должна использоваться, когда новый класс не нужен, чтобы иметь тот же интерфейс, т.е. вы хотите скрыть некоторые аспекты реализации класса, о которых не должен знать пользователь этого класса. Таким образом, композиция в большей степени способна поддерживать инкапсуляцию (т.е. Скрывать реализацию), в то время как наследование предназначено для поддержки абстракции (т.е. Предоставления упрощенного представления чего-либо, в этом случае тот же интерфейс для ряда типов с разными внутренними элементами).

5
ответ дан YS 04 июня '14 в 14:36 2014-06-04 14:36

Помимо того, что a/имеет соображения, нужно также учитывать "глубину" наследования, которое должен пройти ваш объект. Все, что находится за пределами пяти или шести уровней наследования, может вызвать непредвиденные проблемы с литьем и боксом/распаковкой, и в таких случаях может быть разумным составить свой объект вместо этого.

5
ответ дан Jon Limjap 08 сент. '08 в 6:00 2008-09-08 06:00

Если у вас есть отношение is-a между двумя классами (например, собака - собака), вы идете для наследования.

С другой стороны, если у вас есть-a или некоторые отношения прилагательных между двумя классами (у студентов есть курсы) или (курсы для учителей), вы выбрали композицию.

5
ответ дан Amir Aslam 30 дек. '14 в 4:57 2013-12-30 04:57

Я согласен с @Pavel, когда он говорит, есть места для композиции и есть места для наследования.

Я думаю, что наследование должно использоваться, если ваш ответ является положительным для любого из этих вопросов.

  • Является ли ваша класс частью структуры, которая приносит пользу от полиморфизма? Например, если у вас есть класс Shape, который объявляет метод под названием draw(), то нам явно нужны классы Circle и Square для подклассов Shape, так что их клиентские классы будут зависеть от Shape, а не от определенных подклассов.
  • Требуется ли вашему классу повторное использование любых взаимодействий высокого уровня, определенных в другом классе? шаблонный метод шаблона проектирования невозможно реализовать без наследования. Я считаю, что все расширяемые структуры используют этот шаблон.

Однако, если ваше намерение состоит исключительно в повторном использовании кода, то состав, скорее всего, является лучшим выбором дизайна.

4
ответ дан Parag 07 дек. '11 в 13:40 2011-12-07 13:40

Подтипирование является подходящим и более мощным, где могут быть перечислены инварианты , иначе используйте состав функций для расширяемости.

4
ответ дан Shelby Moore III 02 дек. '11 в 10:40 2011-12-02 10:40

Правило большого пальца, которое я слышал, является наследованием, которое следует использовать, когда его отношение и состав "есть-a", когда оно имеет "has-a". Даже с этим я чувствую, что вы всегда должны склоняться к композиции, потому что это устраняет большую сложность.

3
ответ дан Evrhet Milam 08 сент. '08 в 5:14 2008-09-08 05:14

Чтобы решить этот вопрос с другой точки зрения для новых программистов:

Наследование часто начинается раньше, когда мы изучаем объектно-ориентированное программирование, поэтому оно рассматривается как простое решение общей проблемы.

У меня есть три класса, для которых нужна общая функциональность. Так что если я напишите базовый класс и унаследуйте их от него, тогда они будут все имеют эту функциональность, и мне нужно будет только ее поддерживать место.

Звучит здорово, но на практике это почти никогда не срабатывает по одной из следующих причин:

  • Мы обнаруживаем, что есть некоторые другие функции, которые мы хотим, чтобы наши классы имели. Если способ добавления функциональности к классам осуществляется через наследование, мы должны решить - добавим ли мы его в существующий базовый класс, хотя не каждый класс, который наследует от него, нуждается в этой функции? Создаем ли мы еще один базовый класс? Но как насчет классов, которые уже наследуются от другого базового класса?
  • Мы обнаруживаем, что только для одного из классов, наследуемых от нашего базового класса, мы хотим, чтобы базовый класс вел себя немного по-другому. Итак, теперь мы возвращаемся и возимся с нашим базовым классом, возможно, добавляем некоторые виртуальные методы или, что еще хуже, некоторый код, который говорит: "Если я унаследован тип A, сделайте это, но если я унаследован от типа B, сделайте это". Это плохо по многим причинам. Во-первых, каждый раз, когда мы меняем базовый класс, мы эффективно меняем каждый унаследованный класс. Таким образом, мы действительно меняем класс A, B, C и D, потому что нам нужно немного другое поведение в классе A. Как бы мы ни старались, мы можем сломать один из этих классов по причинам, которые не имеют никакого отношения к тем классы.
  • Мы могли бы знать, почему мы решили заставить все эти классы наследовать друг от друга, но это может не (вероятно, не будет) иметь смысл для кого-то, кто должен поддерживать наш код. Мы могли бы заставить их сделать трудный выбор - я делаю что-то действительно уродливое и беспорядочное, чтобы внести необходимые изменения (см. Предыдущую маркерную точку) или просто переписать кучу этого.

В конце концов, мы привязываем наш код к некоторым сложным узлам и не получаем никакой пользы от него, за исключением того, что мы можем сказать: "Круто, я узнал о наследовании, и теперь я его использовал". Это не значит быть снисходительным, потому что мы все это сделали. Но мы все это делали, потому что никто не сказал нам этого.

Как только кто-то объяснил мне "предпочтение композиции по наследованию", я подумал о том, чтобы каждый раз, когда я пытался делиться функциональностью между классами с использованием наследования, я понял, что большую часть времени он не очень хорошо работает.

Антидотом является принцип единой ответственности . Подумайте об этом как об ограничении. Мой класс должен сделать одно. Я должен быть в состоянии дать моему классу имя, которое каким-то образом описывает это одно. (Есть исключения во всем, но абсолютные правила иногда лучше, когда мы учимся.) Из этого следует, что я не могу написать базовый класс под названием ObjectBaseThatContainsVariousFunctionsNeededByDifferentClasses . Независимо от того, какую функциональность мне нужно, должен быть в своем классе, а затем другие классы, которые нуждаются в этой функции, могут зависеть от этого класса, а не наследовать от него.

С риском упрощения этой композиции - составление нескольких классов для совместной работы. И как только мы формируем эту привычку, мы обнаруживаем, что она гораздо более гибкая, поддерживаемая и проверяемая, чем использование наследования.

2
ответ дан Scott Hannen 13 мая '16 в 23:24 2016-05-13 23:24

Состав v/s Наследование - широкий предмет. Нет никакого реального ответа на то, что лучше, поскольку я думаю, что все зависит от дизайна системы.

Обычно тип отношения между объектом обеспечивает лучшую информацию для выбора одного из них.

Если тип отношения - это отношение "IS-A", тогда наследование лучше подходит. иначе тип отношения - отношение "HAS-A", тогда композиция будет лучше приближаться.

Его полностью зависит от отношения сущности.

2
ответ дан Shami Qureshi 18 марта '14 в 12:39 2014-03-18 12:39

Наследование - очень мощный маханизм для повторного использования кода. Но нужно правильно использовать. Я бы сказал, что наследование используется правильно, если подкласс также является подтипом родительского класса. Как уже упоминалось выше, принцип замены Лискова является ключевым моментом здесь.

Подкласс не совпадает с подтипом. Вы можете создавать подклассы, которые не являются подтипами (и это когда вы должны использовать композицию). Чтобы понять, что такое подтип, давайте начнем объяснять, что такое тип.

Когда мы говорим, что число 5 имеет тип integer, мы утверждаем, что 5 принадлежит множеству возможных значений (в качестве примера см. возможные значения для примитивных типов Java). Мы также заявляем, что существует допустимый набор методов, которые я могу выполнить для значения, такого как сложение и вычитание. И, наконец, мы заявляем, что существует набор свойств, которые всегда выполняются, например, если я добавлю значения 3 и 5, я получу 8 в результате.

Чтобы привести еще один пример, подумайте об абстрактных типах данных, наборе целых чисел и списке целых чисел, значения, которые они могут удерживать, ограничены целыми числами. Они поддерживают набор методов, таких как add (newValue) и size(). И они оба имеют разные свойства (инвариант класса), Sets не позволяет дублировать, в то время как List разрешает дубликаты (конечно, есть другие свойства, которые оба они удовлетворяют).

Подтип также является типом, который имеет отношение к другому типу, называемому родительским типом (или супертипом). Подтип должен удовлетворять свойствам (значениям, методам и свойствам) родительского типа. Отношение означает, что в любом контексте, где ожидается супертип, его можно подменить подтипом, не влияя на поведение выполнения. Давайте посмотрим на некоторый код, чтобы продемонстрировать, что я говорю. Предположим, что я пишу список целых чисел (в каком-то псевдоязыке):

 class List { data = new Array(); Integer size() { return data.length; } add(Integer anInteger) { data[data.length] = anInteger; } } 

Затем я пишу Набор целых чисел как подкласс списка целых чисел:

 class Set, inheriting from: List { add(Integer anInteger) { if (data.notContains(anInteger)) { super.add(anInteger); } } } 

Наш класс целых чисел является подклассом List of Integer, но не является подтипом, поскольку он не удовлетворяет всем функциям класса List. Значения и сигнатура методов выполняются, но свойства не являются. Поведение метода add (Integer) явно изменилось, не сохраняя свойства родительского типа. Подумайте, с точки зрения клиента ваших классов. Они могут получить набор целых чисел, где ожидается список целых чисел. Клиент может захотеть добавить значение и получить добавленное значение в список, даже если это значение уже существует в списке. Но она не получит такое поведение, если значение существует. Большой сюрприз для нее!

Это классический пример ненадлежащего использования наследования. Используйте композицию в этом случае.

(фрагмент из: правильно использует наследование ).

2
ответ дан Enrique Molinari 31 авг. '13 в 16:41 2013-08-31 16:41

Пример реального мира, когда композиция лучше, чем наследование.

Предположим, что у вас есть BaseClass, где T вводится конкретным классом.

 public BaseClass<T>{} 

На первый взгляд это выглядит хорошо, потому что вы можете вводить любой тип, который хотите, и использовать этот тип для изменения поведения свойств внутри класса; где T ссылается так:

 public T SomeProperty {get;set;} 

Возможный конкретный класс с использованием наследования выглядит следующим образом:

 public class SubClass : BaseClass<string> 

Но что произойдет, если вам нужно, чтобы это базовое свойство обрабатывало разные типы? Создаете ли вы один класс для каждого типа? Вместо этого просто не используйте сдерживание. Burada:

 public class SubClass{ private BaseClass<List<string>> BaseForList = new BaseClass<List<string>>(); private BaseClass<List<ofSomeType>> BaseForListOfSometype = new BaseClass<List<ofSomeType>(); } 

В приведенном выше примере вы можете видеть, что SubClass открыт для расширения любым необходимым образом. Это может быть, по сути, более чем одна реализация одного базового слоя. Он легко превращается в этот тип, дающий вам очень специфический доступ к типам собственности. Это лучше, чем наследование и создание нескольких классов, полученных из BaseClass, с одной оговоркой...

Адресация на "Содержимые" вещи принимает другую подпись, которая просто предшествует желаемой вещи (SomeProperty) с экземпляром класса. Burada:

 BaseForList.SomeProperty = something; BaseForListOfSomeType.SomeProperty = somethingelse; 

Из-за этого лучше всего создавать короткие имена для содержащихся вещей... так что ваш код может выглядеть так (просто убедитесь, что короткие имена имеют хороший смысл)...

 BA.SomeProperty = something; BL.SomeProperty = somethingelse; 
1
ответ дан John Peters 26 марта '15 в 20:20 2015-03-26 20:20

Как и многие люди, я сначала начну с проверки - существует ли связь "есть-а". Если он существует, я обычно проверяю следующее:

Можно ли создать базовый класс. То есть базовый класс может быть не абстрактный. Если это может быть не абстрактный, я обычно предпочитаю композицию

Например: 1. Бухгалтер - сотрудник. Но я использую не наследование, потому что объект Employee может быть создан.

Например, 2. Книга - это SellingItem. Нельзя создавать экземпляр SellingItem - это абстрактное понятие. Следовательно, я буду использовать inheritacne. SellingItem является абстрактным базовым классом (или интерфейсом на С#)

Что вы думаете об этом подходе?

Кроме того, я поддерживаю @anon ответ в Зачем использовать наследование вообще?

Основная причина использования наследования - это не форма композиции, поэтому вы можете получить полиморфное поведение. Если вам не нужен полиморфизм, вы, вероятно, не должны использовать наследование.

@MatthieuM. говорится в https://softwareengineering.stackexchange.com/questions/12439/code-smell-inheritance-abuse/12448#comment303759_12448

Проблема с наследованием заключается в том, что его можно использовать для двух ортогональных целей:

(для полиморфизма)

(для повторного использования кода)

ССЫЛКА

1
ответ дан Lijo 01 авг. '12 в 14:15 2012-08-01 14:15
  • 1
  • 2

Другие вопросы по меткам > или Задайте вопрос