Çocuğun ebeveyni referansını başlatmanın en iyi yolu nedir?


35

Çok sayıda farklı ebeveyn / çocuk sınıfı olan bir nesne modeli geliştiriyorum. Her alt nesnenin üst nesnesine bir başvurusu vardır. Ana referansı başlatmak için birkaç yol düşünebilirim (ve denedim), ancak her yaklaşımın önemli dezavantajlarını buldum. Aşağıda açıklanan yaklaşımlar göz önüne alındığında hangisi en iyisidir ... veya hangisi daha iyidir.

Aşağıdaki kodun derlendiğinden emin olamayacağım, bu yüzden kod sözdizimsel olarak doğru değilse lütfen niyetimi görmeye çalışın.

Her zaman göstermesem de, alt sınıf yapıcılarımın bazılarının (ebeveyn dışında) parametre aldığını unutmayın.

  1. Arayanın ebeveyni ayarlamaktan ve aynı ebeveyne eklemekten sorumludur.

    class Child {
      public Child(Parent parent) {Parent=parent;}
      public Parent Parent {get; private set;}
    }
    class Parent {
      // singleton child
      public Child Child {get; set;}
      //children
      private List<Child> _children = new List<Child>();
      public List<Child> Children { get {return _children;} }
    }
    

    Dezavantajı: ana ayarlama tüketici için iki aşamalı bir işlemdir.

    var child = new Child(parent);
    parent.Children.Add(child);
    

    Olumsuz: hata eğilimli. Arayan, çocuğu başlatmak için kullanılandan farklı bir ebeveyne çocuk ekleyebilir.

    var child = new Child(parent1);
    parent2.Children.Add(child);
    
  2. Ebeveyn, arayanın başlatıldığı ebeveyn için çocuğu eklediğini doğrular.

    class Child {
      public Child(Parent parent) {Parent = parent;}
      public Parent Parent {get; private set;}
    }
    class Parent {
      // singleton child
      private Child _child;
      public Child Child {
        get {return _child;}
        set {
          if (value.Parent != this) throw new Exception();
          _child=value;
        }
      }
      //children
      private List<Child> _children = new List<Child>();
      public ReadOnlyCollection<Child> Children { get {return _children;} }
      public void AddChild(Child child) {
        if (child.Parent != this) throw new Exception();
        _children.Add(child);
      }
    }
    

    Dezavantajı: Arayanın ebeveyn ayarlamak için hala iki aşamalı bir süreci var.

    Dezavantajı: çalışma zamanı kontrolü - performansı düşürür ve her ekleme / ayarlayıcıya kod ekler.

  3. Çocuk, ebeveyne eklendiğinde / atandığında ebeveyn, çocuğun ebeveyn referansını (kendisine) ayarlar. Üst ayarlayıcı dahilidir.

    class Child {
      public Parent Parent {get; internal set;}
    }
    class Parent {
      // singleton child
      private Child _child;
      public Child Child {
        get {return _child;}
        set {
          value.Parent = this;
          _child = value;
        }
      }
      //children
      private List<Child> _children = new List<Child>();
      public ReadOnlyCollection<Child> Children { get {return _children;} }
      public void AddChild(Child child) {
        child.Parent = this;
        _children.Add(child);
      }
    }
    

    Dezavantajı: Çocuk ebeveyn referansı olmadan yaratılır. Bazen başlatma / doğrulama ebeveyn gerektirir, bu da bazı başlangıç ​​/ doğrulama işlemlerinin çocuğun ebeveyn belirleyicisinde yapılması gerektiği anlamına gelir. Kod karmaşıklaşabilir. Çocuğun ebeveyni referansı olsaydı, çocuğu uygulamak çok daha kolay olurdu.

  4. Ebeveyn, fabrika ekleme yöntemlerini gösterir; böylece bir çocuğun her zaman ebeveyn referansı olur. Çocuk oyuncağı dahilidir. Üst ayarlayıcı özeldir.

    class Child {
      internal Child(Parent parent, init-params) {Parent = parent;}
      public Parent Parent {get; private set;}
    }
    class Parent {
      // singleton child
      public Child Child {get; private set;}
      public void CreateChild(init-params) {
          var child = new Child(this, init-params);
          Child = value;
      }
      //children
      private List<Child> _children = new List<Child>();
      public ReadOnlyCollection<Child> Children { get {return _children;} }
      public Child AddChild(init-params) {
        var child = new Child(this, init-params);
        _children.Add(child);
        return child;
      }
    }
    

    Olumsuz: gibi başlatma sözdizimi kullanamazsınız new Child(){prop = value}. Bunun yerine yapmak zorunda:

    var c = parent.AddChild(); 
    c.prop = value;
    

    Dezavantajı: Fabrika yapıcı yöntemlerde alt yapıcı parametrelerini çoğaltmanız gerekir.

    Dezavantajı: Bekar bir çocuk için mülk ayarlayıcısı kullanılamaz. Değeri belirlemek için bir yönteme ihtiyacım var, ancak özellik alıcı aracılığıyla okuma erişimi sağlıyor gibi görünüyor. Dengesiz.

  5. Alt yapıda başvurulan üst öğeye kendisini ekler. Çocuk bakıcısı halka açıktır. Genel, üst öğeden erişim eklemedi.

    //singleton
    class Child{
      public Child(ParentWithChild parent) {
        Parent = parent;
        Parent.Child = this;
      }
      public ParentWithChild Parent {get; private set;}
    }
    class ParentWithChild {
      public Child Child {get; internal set;}
    }
    
    //children
    class Child {
      public Child(ParentWithChildren parent) {
        Parent = parent;
        Parent._children.Add(this);
      }
      public ParentWithChildren Parent {get; private set;}
    }
    class ParentWithChildren {
      internal List<Child> _children = new List<Child>();
      public ReadOnlyCollection<Child> Children { get {return _children;} }
    }
    

    Olumsuz: arama sözdizimi çok iyi değil. Normalde biri add, yalnızca bunun gibi bir nesne oluşturmak yerine, üst öğe üzerinde bir yöntem çağırır :

    var parent = new ParentWithChildren();
    new Child(parent); //adds child to parent
    new Child(parent);
    new Child(parent);
    

    Ve böyle bir nesne oluşturmak yerine bir özellik ayarlar:

    var parent = new ParentWithChild();
    new Child(parent); // sets parent.Child
    

...

SE'nin bazı subjektif sorulara izin vermediğini ve açıkça bu subjektif bir soru olduğunu öğrendim. Ama belki de bu öznel bir sorudur.


14
En iyi uygulama, çocukların ebeveynleri hakkında bilgi sahibi olmaması gerektiğidir.
Telastyn


2
@Telastyn Yardım edemem ama bunu yanağında bir dil olarak okudum ve çok komik. Ayrıca tamamen ölü kanlı doğru. Steven, aranacak terim "asiklik" tir, çünkü grafiklerin neden mümkünse asik yapması gerektiğine dair birçok literatür vardır.
Jimmy Hoffa,

10
@Telastyn parenting.stackexchange hakkındaki bu yorumu kullanmaya çalışmalısın
Fabio Marcolini

2
Hmm. Bir gönderinin nasıl taşınacağını bilmiyorum (bayrak kontrolü görmüyorum). Birisi bana ait olduğunu söylediğinden beri programcılara yeniden mesaj attım.
Steven Broshar

Yanıtlar:


18

Çocuğun ebeveyni bilmesini gerektiren her türlü senaryodan uzak dururum.

Mesajları çocuktan ebeveyne olaylarla geçirmenin yolları vardır. Bu şekilde ebeveyn, eklemenin üzerine, çocuğun doğrudan ebeveyn hakkında bir şeyler bilmesi gerekmeden çocuğun tetiklediği bir olaya kaydolmalıdır. Bundan sonra, bu, ebeveyni bir şekilde etkileyebilmek için ebeveyini bilen bir çocuğun amaçlanan kullanımıdır. Öyleyse, ebeveynin işini gerçekleştiren çocuk istemem hariç gerçekten sadece bir durumun ortaya çıktığını ebeveyn söylememizi ne yapmak istediğinizi. Bu nedenle, üstesinden gelmeniz gereken, çocuk üzerinde ebeveynin faydalanabileceği bir olaydır.

Bu örüntü aynı zamanda bu olayın diğer sınıflar için yararlı olması durumunda çok iyi ölçeklenir. Belki de biraz fazladan bir engeldir, fakat aynı zamanda kendinizi daha sonra vurmanızı da engeller, çünkü çocuk sınıfınızda ebeveynleri sadece iki sınıfı daha fazla birleştiren kullanmak isteme cazip hale gelir. Daha sonra bu tür sınıfların yeniden düzenlenmesi zaman alır ve programınızda kolayca hatalar yaratabilir.

Umarım yardımcı olur!


6
Çocuğun ebeveyni hakkında mutlaka bilgi sahibi olmasını gerektiren herhangi bir senaryodan uzak durun ” - neden? Cevabınız, dairesel nesne grafiklerinin kötü bir fikir olduğu varsayımına dayanıyor. Bu bazen böyle olsa da (örneğin, saf ref-sayma ile hafıza yönetimi sırasında - C # 'da değil), genel olarak kötü bir şey değil. Özellikle, (genellikle olayları göndermek için kullanılan) Gözlemci Kalıbı, gözlemlenebilirliği ( Child) Parentdaireselliği geriye doğru bir şekilde yeniden ortaya koyan (ve kendine ait bazı sorunları ortaya çıkaran) bir dizi gözlemciyi ( ) muhafaza etmeyi içerir .
04

1
Çünkü döngüsel bağımlılıklar, kodunuzu, biri olmadan kullanamayacağınız şekilde yapılandırmak anlamına gelir. Ebeveyn-çocuk ilişkisinin doğası gereği, ayrı varlıklar olmalı ya da tasarımına verilen tüm özenli bir listeye sahip tek bir devasa sınıf olabileceği gibi sıkı bir şekilde birbirine bağlanmış iki sınıfa girme riski de vardır. Gözlemci modelinin, bir sınıfın diğerlerine referans vermesi dışında ebeveyn-çocukla aynı olduğunu göremiyorum. Benim için ebeveyn-çocuk, çocuğa güçlü bir bağımlılığa sahip olan bir ebeveyndir, tersine değil.
Neil

Katılıyorum. Olaylar, bu durumda ebeveyn-çocuk ilişkisini ele almanın en iyi yoludur. Bu, çok sık kullandığım kalıp ve çocuk sınıfının referans yoluyla ebeveyne ne yaptığı konusunda endişelenmek yerine, kodu korumayı çok kolaylaştırıyor.
Ebedi21

@Neil: Nesne örneklerinin karşılıklı bağımlılıkları , birçok veri modelinin doğal bir parçasını oluşturur. Bir arabanın mekanik simülasyonunda, arabanın farklı kısımlarının birbirine kuvvet aktarması gerekecektir; bu genellikle, otomobilin tüm parçalarının simülasyon motorunun kendisini bir "ana" nesne olarak görmesini sağlayarak, tüm bileşenler arasında döngüsel bağımlılıklara sahip olmaktan daha iyi kullanılır, ancak eğer bileşenler ana baba dışından gelen herhangi bir uyarana yanıt verebilirse Bu tür uyaranların ebeveynin bilmesi gereken herhangi bir etkisi varsa, ebeveyni bilgilendirecek bir yola ihtiyaçları olacaktır.
supercat

2
@Neil: Etki alanı, indirgenemez döngüsel veri bağımlılığına sahip bir orman nesnesi içeriyorsa, etki alanının herhangi bir modeli de bunu yapar. Çoğu durumda bu, ormanın, ister istese de istemese de tek bir dev nesne olarak davranacağını ima edecektir . Agrega deseni, ormanın karmaşıklığını Agrega Kökü adı verilen tek bir sınıf nesnesine yoğunlaştırmaya hizmet eder. Etki karmaşıklığına bağlı olarak ... daha iyi karmaşıklığı kaçınılmaz ise (bazı alanların olduğu gibi) agrega kökü biraz büyük ve hantal olsun, ama olabileceğini, modellenen
SuperCat

10

Bence seçenek 3 en temiz olabilir. Sen yazdın.

Dezavantajı: Çocuk ebeveyn referansı olmadan yaratılır.

Bunu bir dezavantaj olarak görmüyorum. Aslında, programınızın tasarımı, önce ebeveyn olmadan oluşturulabilecek alt nesnelerden yararlanabilir. Örneğin, izolasyonlu çocukların test edilmesini çok daha kolay hale getirebilir. Modelinizin bir kullanıcısı bir çocuğu ebeveyne eklemeyi unutursa ve ebeveyn özelliğini alt sınıfınızda başlatılmasını bekleyen bir yöntem çağırırsa, boş bir istisna alır - bu tam olarak ne istediğinizi: yanlış bir erken çökme kullanımı.

Bir çocuğun teknik nedenlerden dolayı her koşulda kurucuda başlatılması için ana niteliğine ihtiyacı olduğunu düşünüyorsanız, varsayılan değer olarak "boş ana nesne" gibi bir şey kullanın (bunun maskeleme riski vardır).


Bir çocuk nesnesi ebeveyn olmadan yararlı bir şey yapamazsa, bir çocuk nesnesinin ebeveyn olmadan başlaması SetParent, mevcut bir çocuğun ebeveyni değiştirmeyi desteklemesi gereken bir yönteme sahip olmasını gerektirir (ki bu yapılması zor olabilir ve / veya saçmalık) veya sadece bir kez çağrılabilir. Bir araya getirilen durumların (Mike Brown'un önerdiği gibi) modellenmesi, çocukların ebeveynleri olmadan başlamasından çok daha iyi olabilir.
supercat

Bir kullanım çantası bir defa bile bir şey ararsa, tasarım her zaman buna izin vermelidir. Ardından, bu yeteneği özel durumlarla sınırlamak kolaydır. Daha sonra böyle bir yetenek eklemek, ancak genellikle mümkün değildir. En iyi çözüm Seçenek 3'tür. Muhtemelen ebeveyn ile çocuk arasında üçüncü bir "İlişki" nesnesiyle [Ebeveyn] ---> [İlişki (Ebeveyn Çocuğun Sahibi)] <--- [Çocuk] olur. Bu aynı zamanda [Çocuk] ---> [İlişki (Çocuk Ebeveynlere aittir)] <--- [Ebeveyn] gibi birden çok [İlişki] örneğine izin verir.
DocSalvager

3

Yaygın olarak birlikte kullanılan iki sınıf arasında yüksek uyumu önleyen hiçbir şey yoktur (örneğin, bir Order ve LineItem genellikle birbirlerini referans alacaktır). Bununla birlikte, bu durumlarda, Etki Alanına Dayalı Tasarım kurallarına uyma ve bunları Ana Topluluğun Toplayıcı Kökü olarak Toplama olarak modelleme eğilimindeyim . Bu bize, AR'nin topluluğundaki tüm nesnelerin ömründen sorumlu olduğunu söyler.

Bu nedenle, dördüncü senaryodaki gibi olur; burada ebeveyn, çocuklarını uygun şekilde başlatmak ve koleksiyona eklemek için çocuklarını gerekli parametreleri kabul etmek için oluşturmak için bir yöntem sunar.


1
Dış kaynaklı bir gözlemcinin bakış açısından davranışın tutarlı olması şartıyla, dış kaynaklı köklerin dışındaki kısımlara dış referansların varlığına izin verecek, biraz daha gevşek bir toplu tanım kullanırdım. agreganın her bir kısmı sadece herhangi bir başka parçaya değil, köke referansta bulunur. Aklıma göre, temel ilke, değiştirilebilir her nesnenin bir sahibinin olması gerektiğidir ; bir toplama, tümü tek bir nesneye ("toplu kök") ait olan ve parçalarına ait tüm referansları bilmesi gereken nesneler toplamıdır.
supercat,

3

“Çocuk-fabrika” nesnesini kullanarak, çocuk yaratan bir ana yönteme aktarılan “çocuk-fabrika” nesnelerine sahip olmayı, onu eklemeyi ve bir görünüm döndürmeyi öneriyorum. Alt nesnenin kendisi asla ebeveynin dışına çıkarılmaz. Bu yaklaşım simülasyonlar gibi şeyler için iyi çalışabilir. Elektronik simülasyonda, belirli bir "çocuk fabrikası" nesnesi, bir tür transistörün özelliklerini temsil edebilir; bir başkası, bir direnç için spesifikasyonları temsil edebilir; İki transistör ve dört direnç gerektiren bir devre aşağıdaki gibi kodlarla oluşturulabilir:

var q2N3904 = new TransistorSpec(TransistorType.NPN, 0.691, 40);
var idealResistor4K7 = new IdealResistorSpec(4700.0);
var idealResistor47K = new IdealResistorSpec(47000.0);

var Q1 = Circuit.AddComponent(q2N3904);
var Q2 = Circuit.AddComponent(q2N3904);
var R1 = Circuit.AddComponent(idealResistor4K7);
var R2 = Circuit.AddComponent(idealResistor4K7);
var R3 = Circuit.AddComponent(idealResistor47K);
var R4 = Circuit.AddComponent(idealResistor47K);

Simülatörün, çocuk yaratıcısı nesnesine herhangi bir referansı tutması gerekmediğini ve simülatör AddComponenttarafından oluşturulan ve tutulan nesneye bir referans vermeyeceğini, bunun yerine bir görünümü temsil eden bir nesneye geri dönmeyeceğini unutmayın. Eğer AddComponentyöntem genel olduğu, görünüm nesne bileşene özgü işlevleri içerebilir ama olur değil ebeveyn kullandığı eki yönetmek için üye maruz.


2

Harika liste. Hangi yöntemin "en iyi" olduğunu bilmiyorum ama burada en etkileyici yöntemleri bulmak için bir tane var.

Mümkün olan en basit ebeveyn ve çocuk sınıfıyla başlayın. Kodunuzu bunlarla yazın. Adlandırılabilecek kod çoğaltmanın farkına vardığınızda, bunu bir yönteme koyarsınız.

Belki de alırsın addChild(). Belki böyle bir şey almak addChildren(List<Child>)ya addChildrenNamed(List<String>)ya loadChildrenFrom(String)ya newTwins(String, String)ya Child.replicate(int).

Eğer problemin gerçekten bire bir ilişkiyi zorlamaksa, belki de

  • karışıklığa veya atma maddelerine yol açabilecek belirleyicilerde zorla
  • belirleyicileri kaldırır ve özel kopyala veya taşı yöntemlerini oluşturursunuz - bu açık ve anlaşılır

Bu bir cevap değil ama umarım bunu okurken bir tane bulursunuz.


0

Çocuktan ebeveyne olan bağlantılara sahip olmanın yukarıda belirtildiği gibi olumsuz tarafları olduğunu takdir ediyorum.

Bununla birlikte, birçok senaryoda, olaylar ve diğer "bağlantısız" mekaniklerle ilgili geçici çözümler de kendi karmaşıklıklarını ve ek kod satırlarını getirir.

Örneğin, Ebeveyn tarafından alınacak olan Çocuktan bir etkinlik düzenlemek, gevşek bir şekilde de olsa her ikisini de birbirine bağlayacaktır.

Belki de birçok senaryo için bir Child.Parent özelliğinin ne anlama geldiği tüm geliştiriciler için açıktır. Bu konuda çalıştığım sistemlerin çoğu için gayet iyi çalıştı. Aşırı mühendislik zaman alıcı olabilir ve…. Kafa karıştırıcı!

Çocuğu ebeveyne bağlamak için gereken tüm işleri yapan bir Parent.AttachChild () yöntemine sahip olun. Herkes bu "ne anlama geliyor" konusunda net

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.