Bloch'un Oluşturucu Örüntüsünde iyileştirilebilir, genişletilebilir sınıflarda kullanıma daha uygun hale getirmek için


34

Joshua Bloch'un Etkili Java kitabı (2. baskı), muhtemelen okuduğum herhangi bir programlama kitabından çok daha fazla etkilenmiştim. Özellikle, Oluşturucu Deseni (madde 2) en büyük etkiye sahipti.

Bloch'un kurucusunun, son on yıllık programlamama göre birkaç ay içinde beni daha da ileriye götürmesine rağmen, kendimi hala aynı duvara çarptığımı görüyorum: Kendi kendine dönen yöntem zincirleriyle sınıfları genişletmek en iyi ve en kötü kabus jenerik devreye girer ve --especially zaman özellikle birlikte özüne jenerik (gibi Comparable<T extends Comparable<T>>).

Sahip olduğum iki temel ihtiyaç var, sadece ikincisi bu soruya odaklanmak istiyorum:

  1. İlk sorun, “her biri ... sınıfında yeniden uygulamak zorunda kalmadan kendi kendine dönen yöntem zincirlerini nasıl paylaşacağız?”. Meraklı olanlar için, bu kısmı bu cevap yazısının alt kısmında ele aldım, fakat burada odaklanmak istediğim şey bu değil.

  2. Yorum yapmak istediğim ikinci sorun, "bir inşaatçıyı, başka birçok sınıf tarafından genişletilmesi amaçlanan sınıflara nasıl uygulayabilirim?" Dir. Bir sınıfı bir oluşturucu ile genişletmek, doğal olarak, bir tane olmadan genişletmekten daha zordur. Aynı zamanda uygulayan Needableve bu nedenle de bununla ilişkili önemli jenerik ilaçları olan bir kurucuya sahip bir sınıfı genişletmek hantaldır.

Bu yüzden benim sorum şu: Bloch Builder'ı nasıl geliştirebilirim (ne dediğim), böylece herhangi bir sınıfa bir oluşturucu eklemekten çekinmeyebilirim - o sınıf olabilecek bir "temel sınıf" olması durumunda bile Oluşturucu (ve potansiyel jeneriklerinin) kendilerine uyguladığı ekstra bagaj nedeniyle , geleceğimi veya kütüphanemdeki kullanıcıları rahatsız etmeden, defalarca uzatıldı ve uzatıldı.


Zeyilname
Benim sorum yukarıdaki 2. bölüme odaklanıyor, ancak nasıl ele aldım da dahil olmak üzere, birinci soruyu biraz ele almak istedim:

İlk sorun, “her biri ... sınıfında yeniden uygulamak zorunda kalmadan kendinden dönen yöntem zincirlerini nasıl paylaşacağız?”. Bu, genişleyen sınıfların , bu yöntem zincirlerinden yararlanmak isteyen alt sınıfları engellemekten kaçınmak zorunda kalmaları gereken elbette, bu zincirleri yeniden uygulamak zorunda kalmalarını engellemek değildir. için sırayla her kendinden dönen fonksiyonu -Birden onların kullanıcıların bunlardan yararlanmak için muktedir? Bunun için arayüz iskeletlerini burada basacağım ve şimdilik bunun için bırakacağım, ihtiyacı olan bir tasarımla geldim. Benim için iyi çalıştı (bu tasarım yapımında yıllardı ... en zor kısmı dairesel bağımlılıklardan kaçınıyordu):

public interface Chainable  {  
    Chainable chainID(boolean b_setStatic, Object o_id);  
    Object getChainID();  
    Object getStaticChainID();  
}
public interface Needable<O,R extends Needer> extends Chainable  {
    boolean isAvailableToNeeder();
    Needable<O,R> startConfigReturnNeedable(R n_eeder);
    R getActiveNeeder();
    boolean isNeededUsable();
    R endCfg();
}
public interface Needer  {
    void startConfig(Class<?> cls_needed);
    boolean isConfigActive();
    Class getNeededType();
    void neeadableSetsNeeded(Object o_fullyConfigured);
}

Yanıtlar:


21

Josh Bloch'un Oluşturucu Deseni'nde benim için büyük bir gelişme olanı yarattım. Hiçbir şekilde "daha iyi" olduğunu söylememek, sadece çok özel bir durumda , bazı avantajlar sağlar - en büyüğü, kurucuyu inşa edilecek sınıfından ayırmasıdır.

Kör Oluşturucu Deseni olarak adlandırdığım bu alternatifi iyice belgeledim.


Tasarım Deseni: Kör Oluşturucu

Joshua Bloch'un Oluşturucu Paternine alternatif olarak (Etkin Java'daki 2. madde, 2. baskı), Bloch Builder'ın faydalarının çoğunu paylaşan ve tek bir karakterden başka bir şey olan "Kör Oluşturucu Paterni" dediğim şeyi yarattım. tamamen aynı şekilde kullanılır. Kör Inşaatçılar avantajı var

  • yapıcıyı çevreleme sınıfından ayırmak, dairesel bir bağımlılığı ortadan kaldırmak,
  • ek sınıfının kaynak kodunun boyutunu ( artık ne değildir ) büyük ölçüde azaltır ve
  • sağlar ToBeBuiltsınıfı uzatılabilir olan kurucu uzatmak kalmadan .

Bu belgede, " ToBeBuilt" sınıfı olarak inşa edilen sınıfa atıfta bulunacağım.

Bloch Builder ile uygulanan bir sınıf

Bloch Builder, public static classoluşturduğu sınıfın içinde bulunur. Bir örnek:

genel sınıf UserConfig {
   özel final Dize sName;
   özel final int iAge;
   özel final Dize sFavColor;
   genel UserConfig (UserConfig.Cfg uc_c) {// CONSTRUCTOR
      //Aktar
         Deneyin {
            sName = uc_c.sName;
         } catch (NullPointerException rx) {
            yeni NullPointerException ("uc_c") at;
         }
         iAge = uc_c.iAge;
         sFavColor = uc_c.sFavColor;
      // BURADA TÜM ALANLARI DEĞERLENDİR
   }
   public Dize toString () {
      return "name =" + sName + ", age =" + iAge + ", sFavColor =" + sFavColor;
   }
   //builder...START
   genel statik sınıf Cfg {
      özel Dize sName;
      özel int iAge;
      private Dize sFavColor;
      public Cfg (Dize s_name) {
         sName = s_name;
      }
      // kendini ayarlayan ayarlayıcılar ... BAŞLAT
         genel CFG yaşı (int i_age) {
            iAge = i_age;
            bunu geri ver;
         }
         genel Cfg favoriteColor (Dize s_color) {
            sFavColor = s_color;
            bunu geri ver;
         }
      // kendinden dönen ayarlayıcılar ... SON
      genel UserConfig build () {
         return (yeni UserConfig (bu));
      }
   }
   //builder...END
}

Bloch Builder ile bir sınıf oluşturma

UserConfig uc = yeni UserConfig.Cfg ("Kermit"). Yaş (50) .favoriteColor ("green"). Build ();

Aynı sınıf, Kör Oluşturucu olarak uygulandı

Her biri ayrı bir kaynak kodu dosyasında olan bir Kör Oluşturucu için üç bölüm vardır:

  1. ToBeBuilt(Bu örnekte: sınıf UserConfig)
  2. Onun " Fieldable" arayüzü
  3. Oluşturucu

1. inşa edilecek sınıf

Oluşturulacak sınıf, Fieldablearayüzünü tek yapıcı parametresi olarak kabul eder . Yapıcı, tüm iç alanları ondan ayarlar ve her birini doğrular . En önemlisi, bu ToBeBuiltsınıfın kurucusuyla ilgili bilgisi yok.

genel sınıf UserConfig {
   özel final Dize sName;
   özel final int iAge;
   özel final Dize sFavColor;
    genel UserConfig (UserConfig_Fieldable uc_f) {// CONSTRUCTOR
      //Aktar
         Deneyin {
            sName = uc_f.getName ();
         } catch (NullPointerException rx) {
            yeni NullPointerException ("uc_f") atma;
         }
         iAge = uc_f.getAge ();
         sFavColor = uc_f.getFavoriteColor ();
      // BURADA TÜM ALANLARI DEĞERLENDİR
   }
   public Dize toString () {
      return "name =" + sName + ", age =" + iAge + ", sFavColor =" + sFavColor;
   }
}

(Açıklanamaz onların cevabını silinmiş) bir akıllı yorumcu tarafından belirtildiği gibi, eğer ToBeBuiltsınıfı da onun uygular Fieldableonun tek ve sadece yapıcı birincil hem olarak kullanılabilir, ve olsa bile, bir dezavantaj olduğunu alanların her zaman doğrulanır ise (kopya yapıcısı Orijinaldeki ToBeBuiltalanların geçerli olduğu bilinmektedir ).

2. " Fieldable" arayüzü

Fieldable arayüzü, ToBeBuiltnesneyi oluşturmak için gerekli tüm alanları tanımlayan , sınıf ile oluşturucu arasındaki "köprüdür" . Bu arayüz, ToBeBuiltsınıf yapıcısı tarafından talep edilir ve oluşturucu tarafından uygulanır. Bu arayüz, oluşturucu dışındaki sınıflar tarafından uygulanabileceği için, herhangi bir sınıf ToBeBuilt, oluşturucuyu kullanmaya zorlanmadan kolayca sınıfı başlatabilir . Bu aynı zamanda ToBeBuilt, kurucusunu genişletmek arzu edildiğinde veya gerekli olmadığında sınıfı genişletmeyi kolaylaştırır .

Aşağıdaki bölümde açıklandığı gibi, bu arayüzdeki işlevleri hiçbir şekilde belgelemiyorum.

ortak arayüz UserConfig_Fieldable {
   Dize getName ();
   int getAge ();
   Dize getFavoriteColor ();
}

3. Oluşturucu

Oluşturucu Fieldablesınıfı uygular . Hiç bir onaylama yapmamakta ve bu gerçeği vurgulamak için tüm alanları kamuya açık ve değişkendir. Bu halka açık erişilebilirlik bir zorunluluk olmasa da, tercih ediyorum ve tavsiye ediyorum, çünkü onayın ToBeBuilt'kurucu' çağrılıncaya kadar gerçekleşmemesi gerçeğini yeniden uyguluyor . Bu önemlidir, çünkü başka bir dişlinin yapıcıya geçmeden önce yapıcıyı daha fazla manipüle etmesi mümkündürToBeBuilt . Alanları garanti altına almanın tek yolu geçerlidir - yapıcının bir şekilde durumunu "kilitleyemeyeceği" varsayımı - ToBeBuiltsınıfın son kontrolü yapması gerektiğidir.

Son olarak, Fieldablearayüzde olduğu gibi , alıcılarından hiçbirini belgelemiyorum.

public class UserConfig_Cfg, UserConfig_Fieldable'ı uygular {
   public Dize sName;
   public int iAge;
    public Dize sFavColor;
    public UserConfig_Cfg (Dize s_name) {
       sName = s_name;
    }
    // kendini ayarlayan ayarlayıcılar ... BAŞLAT
       genel UserConfig_Cfg yaş (int i_age) {
          iAge = i_age;
          bunu geri ver;
       }
       public UserConfig_Cfg favoriteColor (Dize s_color) {
          sFavColor = s_color;
          bunu geri ver;
       }
    // kendinden dönen ayarlayıcılar ... SON
    //getters...START
       Genel Dize getName () {
          sName döndür;
       }
       genel int getAge () {
          iAge döndürür;
       }
       Genel Dize getFavoriteColor () {
          sFavColor döndürür;
       }
    //getters...END
    genel UserConfig build () {
       return (yeni UserConfig (bu));
    }
}

Kör Oluşturucu ile bir sınıf oluşturma

UserConfig uc = yeni UserConfig_Cfg ("Kermit"). Yaş (50) .favoriteColor ("green"). Build ();

Tek fark " UserConfig_Cfg" yerine " UserConfig.Cfg" dir.

notlar

Dezavantajları:

  • Kör İnşaatçılar ToBeBuiltsınıfının özel üyelerine erişemiyor ,
  • Artık hem yapımcı hem de arayüzde alıcılar gerektiğinden daha ayrıntılı.
  • Tek bir sınıf için her şey artık tek bir yerde değil .

Bir Blind Builder'ı derlemek basittir:

  1. ToBeBuilt_Fieldable
  2. ToBeBuilt
  3. ToBeBuilt_Cfg

FieldableArayüzü tamamen isteğe bağlıdır

ToBeBuiltBirkaç zorunlu alana sahip bir sınıf için - bu UserConfigörnek sınıf gibi, yapıcı basit bir şekilde olabilir.

public UserConfig (Dize s_name, int i_age, Dize s_favColor) {

Ve oluşturucu ile denilen

genel UserConfig build () {
   return (yeni UserConfig (getName (), getAge (), getFavoriteColor ()));
}

Veya alıcıları (inşaatçıda) tamamen ortadan kaldırarak:

   return (yeni UserConfig (sName, iAge, sFavoriteColor));

Doğrudan alanları geçerek, ToBeBuiltsınıf, Fieldablearayüzde olduğu gibi, "oluşturucusundan haberi" kadar kördür . Bununla birlikte, ToBeBuilt"birçok kez uzatılmış ve alt uzatılmış olması amaçlanan" (bu gönderinin başlığında) olan sınıflar için, herhangi bir alanda yapılan değişiklikler , her yapıcı ve yapıcıdaki her alt sınıfta değişiklik yapılmasını gerektirir . Alanların ve alt sınıfların sayısı arttıkça, bu sürdürülmesi pratik olmaz.ToBeBuilt

(Nitekim, birkaç gerekli alanla, bir inşaatçı kullanmak çok müstehcen olabilir. İlgilenenler için, işte kişisel kütüphanemdeki daha büyük Fieldable arayüzlerinin bir örneklemesi .)

Alt paketteki ikincil sınıflar

FieldableTüm Blind Builders için tüm kurucu ve sınıfların sınıflarının bir alt paketinde olmasını tercih ediyorum ToBeBuilt. Alt paket her zaman " z" olarak adlandırılır . Bu, bu ikincil sınıfların JavaDoc paket listesini karıştırmasını önler. Örneğin

  • library.class.my.UserConfig
  • library.class.my.z.UserConfig_Fieldable
  • library.class.my.z.UserConfig_Cfg

Doğrulama örneği

Yukarıda bahsedildiği gibi, tüm validasyon işlemleri kurucuda gerçekleşir ToBeBuilt. Örnek doğrulama koduyla tekrar yapıcı:

genel UserConfig (UserConfig_Fieldable uc_f) {
   //Aktar
      Deneyin {
         sName = uc_f.getName ();
      } catch (NullPointerException rx) {
         yeni NullPointerException ("uc_f") atma;
      }
      iAge = uc_f.getAge ();
      sFavColor = uc_f.getFavoriteColor ();
   // doğrulayın (desenleri gerçekten önceden derlemelisiniz ...)
      Deneyin {
         if (! Pattern.compile ("\\ w +"). eşleştirici (sName) .matches ()) {
            yeni IllegalArgumentException ("uc_f.getName () (\" "+ sName +" \ ") boş bırakılamaz ve yalnızca harf rakamları ve alt çizgiler içermelidir.");
         }
      } catch (NullPointerException rx) {
         yeni NullPointerException ("uc_f.getName ()") atın;
      }
      if (iAge <0) {
         yeni IllegalArgumentException ("uc_f.getAge () (" + iAge + ") sıfırdan daha az.");
      }
      Deneyin {
         if (! Pattern.compile ("(?: kırmızı | mavi | yeşil | sıcak pembe)"). eşleştirici (sFavColor) .matches ()) {
            yeni IllegalArgumentException ("uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +" \ ") kırmızı, mavi, yeşil veya pembe değildir.");
         }
      } catch (NullPointerException rx) {
         yeni NullPointerException ("uc_f.getFavoriteColor ()") at;
      }
}

Belgeleyici İnşaatçılar

Bu bölüm hem Bloch Builders hem de Blind Builders için geçerlidir. Bu tasarımdaki sınıfları nasıl belgelendiğimi, ayarlayıcıları (inşaatçıda) ve alıcılarını ( ToBeBuiltsınıfta) doğrudan birbirine nasıl referans gösterdiğini gösteriyor - tek bir fare tıklamasıyla ve kullanıcının nerede olduğunu bilmesi gerekmiyor Bu işlevler aslında mevcuttur - ve geliştiricinin herhangi bir şeyi gereksiz yere belgelemesi gerekmez.

Alıcılar: Sadece ToBeBuiltsınıflarda

Alıcılar sadece ToBeBuiltsınıfta belgelenmiştir . Hem eşdeğer getters _Fieldableve_Cfg sınıflar dikkate alınmaz. Onları hiç belgelendirmiyorum.

/ **
   <P> Kullanıcının yaşı. </P>
   @return Kullanıcının yaşını temsil eden bir int.
   @see UserConfig_Cfg # yaş (int)
   @see getName ()
 ** /
genel int getAge () {
   iAge döndürür;
}

Birincisi @see, oluşturucu sınıfında olan setter'ına bir link.

Setleyiciler: Oluşturucu sınıfında

Setter belgelenmiştir içinde bulunduğu sanki ToBeBuiltsınıfta ve aynı zamanda sanki o (gerçekten yapılır doğrulama yapar ToBeBuilt'ın yapıcısı). Yıldız işareti (" *"), bağlantının hedefinin başka bir sınıfta olduğunu gösteren görsel bir ipucudur.

/ **
   <P> Kullanıcının yaşını ayarlayın. </P>
   @param i_age Sıfırdan az olamaz. {@Code UserConfig # getName () getName ()} * ile alın.
   @see #favoriteColor (String)
 ** /
genel UserConfig_Cfg yaş (int i_age) {
   iAge = i_age;
   bunu geri ver;
}

Daha fazla bilgi

Hepsini bir araya getirmek: Tam dokümantasyon ile birlikte Blind Builder örneğinin tam kaynağı

UserConfig.java

ithalat java.util.regex.Pattern;
/ **
   <P> Bir kullanıcı hakkında bilgi - <I> [oluşturucu: UserConfig_Cfg] </I> </P>
   <P> Tüm alanların doğrulanması bu sınıf kurucuda gerçekleşir. Ancak, her doğrulama gereksinimi yalnızca üreticinin belirleyici işlevlerinde belgelenir. </P>
   <P> {@code java xbn.z.xmpl.lang.builder.finalv.UserConfig} </P>
 ** /
genel sınıf UserConfig {
   genel statik son boşluk ana (Dize [] igno_red) {
      UserConfig uc = yeni UserConfig_Cfg ("Kermit"). Yaş (50) .favoriteColor ("green"). Build ();
      System.out.println (UC);
   }
   özel final Dize sName;
   özel final int iAge;
   özel final Dize sFavColor;
   / **
      <P> Yeni bir örnek oluşturun. Bu, tüm alanları ayarlar ve doğrular. </P>
      @param uc_f {@code null} olamaz.
    ** /
   genel UserConfig (UserConfig_Fieldable uc_f) {
      //Aktar
         Deneyin {
            sName = uc_f.getName ();
         } catch (NullPointerException rx) {
            yeni NullPointerException ("uc_f") atma;
         }
         iAge = uc_f.getAge ();
         sFavColor = uc_f.getFavoriteColor ();
      // validate
         Deneyin {
            if (! Pattern.compile ("\\ w +"). eşleştirici (sName) .matches ()) {
               yeni IllegalArgumentException ("uc_f.getName () (\" "+ sName +" \ ") boş bırakılamaz ve yalnızca harf rakamları ve alt çizgiler içermelidir.");
            }
         } catch (NullPointerException rx) {
            yeni NullPointerException ("uc_f.getName ()") atın;
         }
         if (iAge <0) {
            yeni IllegalArgumentException ("uc_f.getAge () (" + iAge + ") sıfırdan daha az.");
         }
         Deneyin {
            if (! Pattern.compile ("(?: kırmızı | mavi | yeşil | sıcak pembe)"). eşleştirici (sFavColor) .matches ()) {
               yeni IllegalArgumentException ("uc_f.getFavoriteColor () (\" "+ uc_f.getFavoriteColor () +" \ ") kırmızı, mavi, yeşil veya pembe değildir.");
            }
         } catch (NullPointerException rx) {
            yeni NullPointerException ("uc_f.getFavoriteColor ()") at;
         }
   }
   //getters...START
      / **
         <P> Kullanıcının adı. </P>
         @return Bir olmayan - {@ code null}, boş olmayan bir dize.
         @see UserConfig_Cfg # UserConfig_Cfg (Dize)
         @see #getAge ()
         @see #getFavoriteColor ()
       ** /
      Genel Dize getName () {
         sName döndür;
      }
      / **
         <P> Kullanıcının yaşı. </P>
         @return Sıfırdan büyük ya da eşit bir sayı.
         @see UserConfig_Cfg # yaş (int)
         @see #getName ()
       ** /
      genel int getAge () {
         iAge döndürür;
      }
      / **
         <P> Kullanıcının favori rengi. </P>
         @return Bir olmayan - {@ code null}, boş olmayan bir dize.
         @see UserConfig_Cfg # yaş (int)
         @see #getName ()
       ** /
      Genel Dize getFavoriteColor () {
         sFavColor döndürür;
      }
   //getters...END
   public Dize toString () {
      return "getName () =" + getName () + ", getAge () =" + getAge () + ", getFavoriteColor () =" + getFavoriteColor ();
   }
}

UserConfig_Fieldable.java

/ **
   <P> {@link UserConfig} {@code UserConfig # UserConfig (UserConfig_Fieldable) yapıcısı tarafından zorunludur}. </P>
 ** /
ortak arayüz UserConfig_Fieldable {
   Dize getName ();
   int getAge ();
   Dize getFavoriteColor ();
}

UserConfig_Cfg.java

ithalat java.util.regex.Pattern;
/ **
   <P> {@link UserConfig} için oluşturucu. </P>
   <P> Tüm alanların doğrulanması <CODE> UserConfig </CODE> yapıcısında gerçekleşir. Ancak, her bir doğrulama gereksinimi yalnızca bu sınıflar ayarlayıcı işlevlerinde belgelenir. </P>
 ** /
public class UserConfig_Cfg, UserConfig_Fieldable'ı uygular {
   public Dize sName;
   public int iAge;
   public Dize sFavColor;
   / **
      <P> Kullanıcı adıyla yeni bir örnek oluşturun. </P>
      @param s_name {@code null} veya boş olamaz ve sadece harf, rakam ve alt çizgi içermelidir. {@Code UserConfig # getName () getName ()} {@ code ()} ile alın .
    ** /
   public UserConfig_Cfg (Dize s_name) {
      sName = s_name;
   }
   // kendini ayarlayan ayarlayıcılar ... BAŞLAT
      / **
         <P> Kullanıcının yaşını ayarlayın. </P>
         @param i_age Sıfırdan az olamaz. {@Code UserConfig # getName () getName ()} {@ code ()} ile alın .
         @see #favoriteColor (String)
       ** /
      genel UserConfig_Cfg yaş (int i_age) {
         iAge = i_age;
         bunu geri ver;
      }
      / **
         <P> Kullanıcının favori rengini ayarlayın. </P>
         @param s_color {@code "red"}, {@code "blue"}, {@code green} veya {@code "hot pink"} olmalıdır. {@Code UserConfig # getName () getName ()} {@ code ()} * ile alın.
         @see #age (int)
       ** /
      public UserConfig_Cfg favoriteColor (Dize s_color) {
         sFavColor = s_color;
         bunu geri ver;
      }
   // kendinden dönen ayarlayıcılar ... SON
   //getters...START
      Genel Dize getName () {
         sName döndür;
      }
      genel int getAge () {
         iAge döndürür;
      }
      Genel Dize getFavoriteColor () {
         sFavColor döndürür;
      }
   //getters...END
   / **
      <P> UserConfig'ü yapılandırıldığı gibi oluşturun. </P>
      @return <CODE> (yeni {@link UserConfig # UserConfig (UserConfig_Fieldable) UserConfig} (bu)) </CODE>
    ** /
   genel UserConfig build () {
      return (yeni UserConfig (bu));
   }
}


1
Kesinlikle, bu bir gelişme. Bloch'un Oluşturucusu, burada uygulandığı gibi , bunlar kurulacak olan ve kurucu olan iki somut sınıfı birleştiriyor . Bu başlı başına kötü bir tasarım . Kör Builder alarak birleştirme olduğunu sonları tarif inşa edilecek- sınıfı bir olarak inşaat bağımlılığını tanımlayan soyutlama diğer sınıflar bir ayrılmış şekilde uygulayabilirsiniz. Temel bir nesne yönelimli tasarım rehberi olanı büyük ölçüde uyguladınız.
rucamzu

3
Daha önce hiç yapmadıysanız, bu konuda gerçekten güzel bir algoritma yapmalısınız! Şimdi paylaşmaya gidiyorum :-).
Martijn Verburg

4
Nazik sözlerin için teşekkür ederim. Bu şimdi yeni blogumdaki
aliteralmind

Oluşturucu ve inşa edilmiş nesnelerin her ikisi de Fieldable uygularsa, kalıp, ReatableFoo / MutableFoo / ImmutableFoo olarak adlandırdığım bir şeye benzemeye başlarsa, değiştirilebilir bir şeyi yapma yönteminin oluşturucunun "yapı" üyesi olması yerine, onu çağırın asImmutableve ReadableFooarayüze ekleyin (bu felsefeyi kullanarak, builddeğişmez bir nesneyi çağırmak , aynı nesneye bir referans getirecektir).
supercat

1
@TomasN Buna *_Fieldableyeni alıcılar ekleyip eklemeniz, genişletip *_Cfgyeni ayarlayıcılar eklemeniz gerekir, ancak neden mevcut alıcıları ve ayarlayıcıları çoğaltmanız gerekeceğini bilmiyorum. Miras kalırlar ve farklı işlevlere ihtiyaç duymazlarsa, onları yeniden yaratmaya gerek yoktur.
aliteralmind

13

Bence buradaki soru, başlangıçtan itibaren, kurucu düzenin doğal olarak iyi olduğunu kanıtlamadan bir şey olduğunu varsayıyor.

tl; dr bence üretici düzeni nadiren iyi bir fikirse.


Oluşturucu desen amacı

Oluşturucu deseninin amacı, sınıfınızı tüketmeyi kolaylaştıracak iki kuralı sürdürmektir:

  1. Nesneler, tutarsız / kullanılamaz / geçersiz durumlarda oluşturulamamalıdır.

    • Bu, örneğin bir Personnesnenin Iddoldurulmadan oluşturulabildiği senaryolar anlamına gelirken, bu nesneyi kullanan tüm kod parçaları , sadece düzgün bir şekilde çalışmasını gerektirebilir .IdPerson
  2. Nesne yapıcıları çok fazla parametre gerektirmemelidir .

Dolayısıyla, oluşturucu paterninin amacı tartışmasız olarak iyidir. Bunun arzusu ve kullanımının çoğunun temelde bu kadar ileri giden analizlere dayandığını düşünüyorum: Bu iki kuralı istiyoruz, bu iki kuralı veriyor - bu iki kuralı yerine getirmenin başka yollarını araştırmaya değeceğini düşünüyorum.


Neden diğer yaklaşımlara bakmaktan rahatsız?

Sebebin, bu sorunun kendisi tarafından iyi bir şekilde gösterildiğini düşünüyorum; Oluşturucu paterni uygularken yapılara karmaşıklık ve birçok tören eklendi. Bu soru, bu karmaşıklığın bir kısmının nasıl çözüleceğini soruyor, çünkü karmaşıklığı olduğu gibi garip davranan (kalıtımsal) bir senaryo ortaya çıkarıyor. Bu karmaşıklık aynı zamanda bakım ek yükünü de arttırır (özellik ekleme, değiştirme veya kaldırma), diğerlerinden çok daha karmaşıktır.


Diğer yaklaşımlar

Öyleyse yukarıdaki bir numaralı kural için, hangi yaklaşımlar var? Bu kurala değinilen anahtar, yapım sırasında, bir nesnenin düzgün çalışması için ihtiyaç duyduğu tüm bilgilere sahip olmasıdır - ve inşaat sonrasında bu bilgiler harici olarak değiştirilemez (bu nedenle değişmez bilgidir).

Yapım aşamasında bir nesneye gerekli tüm bilgileri vermenin bir yolu basitçe yapıcıya parametreler eklemektir. Bu bilgi yapıcı tarafından talep edilirse, bu nesneyi tüm bilgiler olmadan oluşturamazsınız, bu nedenle geçerli bir duruma inşa edilir. Fakat eğer nesne geçerli olmak için çok fazla bilgi isterse? Oh dang, bu durumda bu yaklaşım yukarıdaki 2. kuralı ihlal edecektir .

Tamam, başka ne var? Şey, objenizin tutarlı bir durumda olması için gerekli tüm bilgileri basitçe alabilir ve inşaat sırasında alınan başka bir nesneye toplayabilirsiniz. Oluşturucu desenine sahip olmak yerine yukarıdaki kodunuz şöyle olacaktır:

//DTO...START
public class Cfg  {
   public String sName    ;
   public int    iAge     ;
   public String sFavColor;
}
//DTO...END

public class UserConfig  {
   private final String sName    ;
   private final int    iAge     ;
   private final String sFavColor;
   public UserConfig(Cfg uc_c)  {
      ...
   }

   public String toString()  {
      return  "name=" + sName + ", age=" + iAge + ", sFavColor=" + sFavColor;
   }
}

Bu, oluşturucu düzeninden çok farklı değil, biraz daha basit olsa da ve en önemlisi, şimdi kural # 1 ve kural # 2'yi tatmin ediyoruz. .

Öyleyse neden biraz fazladan gidip onu tam anlamıyla geliştirmeyin Bu sadece gereksiz . Bu yaklaşımda oluşturucu modelinin her iki amacını da, daha basit, bakımı kolay ve tekrar kullanılabilir bir şeyle tatmin ettim . Bu son kısım anahtardır, kullanılan bu örnek hayalidir ve kendisini gerçek dünyadaki anlamsal amaçlara borç vermez, bu yüzden bu yaklaşımın tek bir amaç sınıfı yerine yeniden kullanılabilir bir DTO ile sonuçlandığını gösterelim .

public class NetworkAddress {
   public String Ip;
   public int Port;
   public NetworkAddress Proxy;
}

public class SocketConnection {
   public SocketConnection(NetworkAddress address) {
      ...
   }
}

public class FtpClient {
   public FtpClient(NetworkAddress address) {
      ...
   }
}

Eğer oluştururken Yani yapışkan böyle DTOs, ikisi de, daha basit ve daha geniş değer / kullanışlılığı ile oluşturucu yönteminin amacını tatmin edebilir. Bundan başka, bu yaklaşım, oluşturucu paterninin ortaya çıkardığı kalıtım karmaşıklığını çözer:

public class SslCert {
   public NetworkAddress Authority;
   public byte[] PrivateKey;
   public byte[] PublicKey;
}

public class FtpsClient extends FtpClient {
   public FtpsClient(NetworkAddress address, SslCert cert) {
      super(address);
      ...
   }
}

DTO'ların her zaman yapışkan olmadığını veya birden çok DTO'da kırılması gereken özellik gruplarını uyumlu hale getirmek için bulabilirsiniz - bu gerçekten bir sorun değildir. Nesneniz 18 özellik gerektiriyorsa ve bu özelliklere sahip 3 uyumlu DTO oluşturabilirseniz, inşaatçıların amaçlarını karşılayan basit bir konstrüksiyona sahip olursunuz. Yapışkan gruplar oluşturamazsanız, bu nesneler birbiriyle tamamen ilişkili olmayan özelliklere sahiplerse, nesnelerinizin yapışkan olmadığının işareti olabilir - ancak daha sonra daha basit bir uygulamadan dolayı tek bir yapışmaz DTO yapmak bile tercih edilir. miras probleminizi çözme.


Oluşturucu deseni nasıl geliştirilir

Tamam, bu yüzden bütün jant başıboşluğu bir yana, bir sorunun var ve çözmek için bir tasarım yaklaşımı arıyoruz. Önerim: kalıtımsal sınıflar, süper sınıfın oluşturucu sınıfından miras alan iç içe bir sınıfa sahip olabilir, bu nedenle kalıtım sınıfının temelde süper sınıf ile aynı yapıya sahip olması ve tam olarak ek işlevlerle aynı şekilde çalışması gereken bir yapıcı modeline sahip olması alt sınıfın ek özellikleri için ..


İyi bir fikir olduğunda

Kenara çekildiğinde , oluşturucu düzeninde bir niş vardır . Hepimiz biliyoruz çünkü bu oluşturucuyu bir noktada veya başka bir yerde öğrendik: StringBuilder- burada amaç basit bir yapı değil çünkü dizgilerin oluşturulması ve birleştirilmesi daha kolay olamazdı. Bu, büyük bir kurucudur çünkü performans avantajı vardır. .

Dolayısıyla performans avantajı şudur: Bir sürü nesneniz var, bunlar değişmez bir türdendir, onları değişmez türdeki bir nesneye daraltmanız gerekir. Bunu aşamalı olarak yaparsanız, burada yaratılan birçok aracı nesneyi bulacaksınız, bu yüzden hepsini bir kerede yapmak çok daha performanslı ve ideal.

Bu yüzden , bunun iyi bir fikir olduğu zamanın anahtarının şu problem alanında olduğunu düşünüyorum StringBuilder: Değişmez türlerin birden fazla örneğini değişmez türün tek bir örneğine dönüştürmek gerekiyor .


Verilen örneğinizin her iki kuralı da karşıladığını sanmıyorum. Geçersiz bir durumda bir Cfg oluşturmamı durduracak hiçbir şey yok ve parametreler kontrolden çıkarılırken daha az salak ve daha ayrıntılı bir yere taşındılar. fooBuilder.withBar(2).withBang("Hello").withBaz(someComplexObject).build()foo oluşturmak için özlü bir API sunar ve inşaatçının kendisinde gerçek hata kontrolü sunabilir. Oluşturucu olmadan nesnenin kendisi girdilerini kontrol etmek zorunda kalır, bu da eskiden olduğundan daha iyi olmadığımız anlamına gelir.
Phoshi

DTO'lar mülklerini, belirleyicilere ek olarak açıklayıcı olarak sayısız yolla doğrulayabilirler, ancak düzenleyicide, ancak bunu yapmak istersiniz - doğrulama ayrı bir sorundur ve geliştirici yaklaşımında kurucuda gerçekleşen doğrulamanın aynı mantığın tam olarak uyduğunu gösterdiğini gösterir. benim yaklaşımımda Bununla birlikte, DTO'yu onaylamak için kullanmak genellikle daha iyi olacaktır çünkü gösterdiğim gibi - DTO birden fazla tip oluşturmak için kullanılabilir ve bu nedenle üzerinde doğrulama yapılması birden fazla tipin doğrulanması için kendisini ödünç verebilir. Oluşturucu, yalnızca için oluşturulduğu belirli bir tür için geçerlidir.
Jimmy Hoffa

Belki de en esnek yol, yapıcıda tek bir Fieldableparametreyi kabul eden statik bir doğrulama işlevine sahip olmak olabilir . Ben bu doğrulama işlevini çağırır ToBeBuiltyapıcı, ancak herhangi bir yerden, herhangi bir şey ile adlandırılabilir. Bu, belirli bir uygulamayı zorlamadan fazlalık kod potansiyelini ortadan kaldırır. ( FieldableKonseptten hoşlanmıyorsanız, bireysel alanlardan validasyon fonksiyonuna geçmenizi engelleyecek hiçbir şey yoktur - ama şimdi saha listesinin korunması gereken en az üç yer olacaktır.)
aliteralmind

+1 Ve kurucusunda çok fazla bağımlılığı olan bir sınıf yeterince açık değildir ve daha küçük sınıflara yeniden yerleştirilmelidir.
Basilevs

@ JimmyHoffa: Ah, anlıyorum, bunu ihmal ettiniz. Bunun ile bir inşaatçı arasındaki farkı gördüğümden emin değilim, o zaman, bunun dışında, bazı inşaatçılar üzerinde .build'i çağırmak yerine ctor'a bir config örneği geçirir ve bir inşaatçının tümünde doğruluğu kontrol etmek için daha belirgin bir yolu vardır. veri. Her bir değişken değişken aralıkları dahilinde olabilir, ancak bu belirli permütasyonda geçersiz. .build bunu kontrol edebilir, ancak öğeyi ctor'a iletmek, nesnenin kendisinin kontrolünde hata gerektirir - icky!
Phoshi
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.