Mantıklı (ama) karmaşık kalıtımla ilgili pek çok küçük sınıf


24

Gelecekte iyi OOP tasarımı, temiz kod, esneklik ve kod kokularından kaçınmak açısından neyin daha iyi olduğunu merak ediyorum. Sınıf olarak temsil etmeniz gereken çok sayıda benzer nesneye sahip olduğunuz görüntü durumu. Bu sınıflar belirli bir işlevselliğe sahip değildir, sadece veri sınıflarıdır ve sadece adlarına (ve bağlamına) göre farklıdır

Class A
{
  String name;
  string description;
}

Class B
{
  String name;
  String count;
  String description;
}

Class C
{
  String name;
  String count;
  String description;
  String imageUrl;
}

Class D
{
  String name;
  String count;
}

Class E
{
  String name;
  String count;
  String imageUrl;
  String age;
}

Onları ayrı sınıflarda tutmak, "daha iyi" okunabilirlik elde etmek, ancak birçok kod tekrarına katılmak daha mı iyidir, yoksa daha fazla KURU olmak için miras kullanmak daha mı iyidir?

Kalıtım kullanarak, bunun gibi bir şeyle sonuçlanacak, ancak sınıf isimleri onu bağlamsal anlamını yitirecektir (is-a nedeniyle-a-a):

Class A
{
  String name;
  String description;
}

Class B : A
{
  String count;
}

Class C : B
{
  String imageUrl;
}

Class D : C
{
  String age;
}

Kalıtımın kodun yeniden kullanımı için kullanılmadığını ve kullanılmaması gerektiğini biliyorum, ancak bu durumda kod tekrarını azaltmanın başka bir olası yolunu görmüyorum.

Yanıtlar:


58

Genel kural, "tüm mirastan kaçınma" değil, "Mirastan delegasyonu tercih et" şeklindedir. Nesnelerin mantıklı bir ilişkisi varsa ve A, A'nın beklendiği her yerde kullanılabilirse, kalıtım kullanmak iyi bir uygulamadır. Ancak, nesneler sadece aynı ada sahip alanlara sahipse ve etki alanı ilişkisine sahip değilse, miras kullanmayın.

Sık sık, tasarım sürecinde "değişimin nedeni" sorusunu sormak için karşılığını verir: A sınıfı fazladan bir alan alırsa, B sınıfının da almasını bekler misiniz? Bu doğruysa, nesneler bir ilişkiyi paylaşır ve kalıtım iyi bir fikirdir. Değilse, farklı kavramları farklı kodlarda tutmak için küçük tekrarlara katlanın.


Tabii, değişimin nedeni iyi bir nokta, ama bu durumda, bu sınıfların nerede olduğu ve her zaman sabit olacağı durumda ne var?
user969153

20
@ user969153: Sınıflar gerçekten sabit olacağı zaman farketmez. Tüm iyi yazılım mühendisliği, değişiklik yapması gereken gelecek bakımı içindir. Gelecekte değişiklik olmazsa, her şey yolunda gider, ama güven bana, çöp kutusuna program yapmadığınız sürece her zaman değişiklik yaparsınız.
thiton

@ user969153: Söylendiği gibi, "değişimin nedeni" bir sezgiseldir. Karar vermenize yardımcı olur, başka bir şey değil.
thiton

2
İyi cevap. Değişim nedenleri, iyi bir yazılım tasarlanmasında yardımcı olacak amca Bob'un SOLID kısaltmasında S'dir.
Klee

4
@ user969153, Tecrübelerime göre, "bu sınıflar nerede ve her zaman sabit olacak?" geçerli bir kullanım örneği değil :) Henüz beklenmedik bir değişiklik yaşamadım anlamlı bir büyüklükteki uygulama üzerinde çalışmak için henüz.
cdkMoose

18

Kalıtım kullanarak, bunun gibi bir şeyle sonuçlanacak, ancak sınıf isimleri onu bağlamsal anlamını yitirecektir (is-a nedeniyle-a-a):

Kesinlikle. Şuna bir bak, bağlam dışı:

Class D : C
{
    String age;
}

Bir D hangi özelliklere sahiptir? Hatırlayabiliyor musun? Ya hiyerarşiyi daha da karmaşıklaştırırsanız:

Class E : B
{
    int numberOfWheels;
}

Class F : E
{
    String favouriteColour;
}

Ve sonra ya, korku üzerine korku, F'ye bir imageUrl alanı eklemek istiyorsan E'ye? C ve E'den türetemezsiniz.

Birçok farklı Bileşen türünün hepsinin bir adı, bir kimliği ve bir Açıklaması olduğu gerçek bir durum gördüm. Ancak bu, varlıklarının doğası gereği değildi, sadece bir tesadüf idi. Her birinin bir kimliği vardı çünkü bir veritabanında saklanmışlardı. İsim ve açıklama gösterim içindi.

Ürünlerin de benzer nedenlerle bir kimliği, adı ve açıklaması vardı. Ama onlar bileşen değil, bileşen ürün değildi. İki şeyi bir arada göremezsin.

Ancak, bazı geliştiriciler DRY hakkında bir şeyler okudular ve her bir çoğaltma ipucunu koddan kaldırmaya karar verdiler. Bu yüzden her şeyi bir ürün olarak adlandırdı ve bu alanları tanımlama ihtiyacını ortadan kaldırdı. Üründe tanımlanmışlar ve bu alanlara ihtiyaç duyan her şey Üründen kaynaklanıyordu.

Bunu yeterince kuvvetli bir şekilde söyleyemem: Bu iyi bir fikir değil.

KURU, ortak özelliklere duyulan ihtiyacı ortadan kaldırmakla ilgili değildir. Bir nesne Ürün değilse, Ürün olarak adlandırmayın. Bir Ürün bir Tanımlamayı GEREKMEZse, bir Ürün olması nedeniyle, Tanımı bir Ürünün Maliyeti olarak tanımlamayın.

Sonunda, açıklama olmadan Bileşenlere sahip olduk. Böylece bu nesnelerde boş açıklamalara sahip olduk. Null değil. Boş. Her zaman. X ... veya daha sonra Y ... ve N tipi ürünler için.

Bu, Liskov Değişim İlkesinin korkunç bir ihlalidir.

DRY, kendinizi asla bir hatayı tek bir yere sabitleyebileceğiniz ve başka bir yerde yapmayı unutacağınız bir pozisyona sokmamakla ilgilidir. Bu olmayacak çünkü aynı özelliğe sahip iki nesneniz var. Asla. Bu yüzden endişelenme.

Sınıfların bağlamı yapmanız gerektiğini açıkça ortaya koyuyorsa, ki bu çok nadir olacaktır, o zaman ve ancak o zaman ortak bir ebeveyn sınıfı düşünmelisiniz. Ancak, bu özellikse, neden bir arabirim kullanmadığınızı düşünün.


4

Kalıtım polimorfik davranışa ulaşmanın bir yoludur. Sınıflarınızın herhangi bir davranışı yoksa, miras işe yaramaz. Bu durumda, onları nesneler / sınıflar olarak değil, yapıları dikkate alırdım.

Davranışınızın farklı kısımlarına farklı yapılar koyabiliyorsanız, IHasName, IHasAge, vb. Gibi get / set metodları veya özellikleri içeren arayüzleri göz önünde bulundurun. Bir çeşit başarı.


3

Arayüzler bunun için değil mi? Farklı işlevlere sahip ancak benzer özelliklere sahip alakasız sınıflar.

IName = Interface(IInterface)
  function Getname : String;
  property Name : String read Getname
end;

Class Dog(InterfacedObject, IName)
private
  FName : String;
  Function GetName : String;
Public
  Name : Read Getname;
end;

Class Movie(InterfacedObject, IName)
private
  FCount;
  FName : String;
  Function GetName : String;
Public
  Name : String Read Getname;
  Count : read FCount; 
end;

1

Sorunuz, birden fazla kalıtımı desteklemeyen bir dil bağlamında çerçevelenmiş gibi görünüyor, ancak bunu özellikle belirtmiyor. Birden fazla kalıtım destekleyen bir dille çalışıyorsanız, yalnızca tek bir kalıtım düzeyinde çözülmesi çok kolaydır.

Birden çok kalıtım kullanarak bunun nasıl çözülebileceğine bir örnek.

trait Nameable { String name; }
trait Describable { String description; }
trait Countable { Integer count; }
trait HasImageUrl { String imageUrl; }
trait Living { String age; }

class A : Nameable, Describable;
class B : Nameable, Describable, Countable;
class C : Nameable, Describable, Countable, HasImageUrl;
class D : Nameable, Countable;
class E : Nameable, Countable, HasImageUrl, Living;
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.