Bir kurucuda meşru "gerçek iş"?


23

Bir tasarım üzerinde çalışıyorum, ancak bir barikatı vurmaya devam ediyorum. Temelde bir XML şema ayrıştırma (DOM DOM) tarafından oluşturulan karmaşık bir düğüm ağacının sahibi olan belirli bir sınıf (ModelDef) var. İyi tasarım ilkelerini (SOLID) takip etmek ve ortaya çıkan sistemin kolayca test edilebilir olmasını sağlamak istiyorum. Bağımlılıkları ModelDef'in yapıcısına iletmek için DI kullanma niyetine sahibim.

Bununla birlikte uğraştığım şey, düğüm ağacının oluşturulması. Bu ağaç tamamen bağımsız bir şekilde test edilmesi gerekmeyen basit “değer” nesnelerden oluşacak. (Bununla birlikte, bu nesnelerin oluşturulmasına yardımcı olmak için yine bir Soyut Fabrikayı ModelDef'e geçirebilirim.)

Ancak bir yapıcının herhangi bir gerçek iş yapmaması gerektiğini okudum (ör. Flaw: Yapıcı Gerçek İş yapıyor ). "Gerçek iş", birinin daha sonra test etmek için saplamak isteyebileceği ağır tartıma bağımlı nesneler oluşturmak anlamına gelirse, bu benim için mükemmel bir anlam ifade eder. (Bunlar DI üzerinden iletilmelidir.)

Ancak, bu düğüm ağacı gibi hafif değer nesnelerine ne dersiniz? Ağaç bir yerlerde yaratılmalı, değil mi? Neden ModelDef'in yapıcısı aracılığıyla değil (bir buildNodeTree () yöntemi kullanarak)?

Düğüm ağacını ModelDef'in dışında oluşturmak ve bunu iletmek istemiyorum (yapıcı DI aracılığıyla), çünkü şema ayrıştırılarak düğüm ağacının oluşturulması, kapsamlı bir şekilde test edilmesi gereken önemli miktarda karmaşık kod - kod gerektiriyor . Ben onu "yapıştırıcı" kodla (istemeden önemsiz ve muhtemelen doğrudan test edilmeyecek) koduna vermek istemiyorum.

Düğüm ağacını oluşturmak için ayrı bir "oluşturucu" nesnesine kod eklemeyi düşündüm, ancak "oluşturucu" olarak adlandırmakta tereddüt ettim, çünkü Oluşturucu Deseni ile gerçekten eşleşmiyor (teleskopların kaldırılmasıyla daha fazla ilgileniyor gibi görünüyor) kurucular). Ama farklı bir şey (ör. NodeTreeConstructor) olarak adlandırsam bile, ModelDef yapıcısının düğüm ağacını inşa etmesini önlemek için hala bir kesmek gibi hissediyor. Bir yere inşa edilmesi gerekiyor; neden kendine ait olacak nesnede değil?


7
Yine de böyle battaniye ifadelerine karşı her zaman dikkatli olmalısın. Genel kural, durumunuza bağlı olarak, hangi şekilde olursa olsun, net, işlevsel, test edilmesi, yeniden kullanımı ve bakımı kolay olan bir koddur. Eğer böyle bir "kural" takip etmeye çalışmak karmaşıklığı ve karmaşayı kodlamaktan başka bir şey kazanmazsanız, durumunuz için uygun bir kural değildi. Tüm bu "kalıplar" ve dil özellikleri araçlardır; işiniz için en iyisini kullanın.
Jason C

Yanıtlar:


26

Ve, Ross Patterson'un önerdiklerinin yanı sıra, tam tersi olan bu konumu göz önünde bulundurun:

  1. "Tuzlarınızla yapıcılarınızda gerçek bir iş yapmazsınız" gibi maksimumları alın.

  2. Bir yapıcı, gerçekten, statik bir yöntemden başka bir şey değildir. Yani, yapısal olarak, gerçekten arasında pek bir fark yoktur:

    a) basit bir kurucu ve bir grup karmaşık statik fabrika yöntemi, ve

    b) basit bir kurucu ve daha karmaşık kuruculardan oluşan bir demet.

Yapıcılarda herhangi bir gerçek iş yapmaya yönelik olumsuz duygunun önemli bir kısmı, kurucuya bir istisna atılırsa nesnenin hangi durumda bırakılacağına dair tam bir tartışma olduğu zaman C ++ tarihinin belirli bir döneminden gelir. yıkıcı böyle bir durumda çağrılmalıdır. C ++ tarihinin bu kısmı sona erdi ve sorun çözüldü, Java gibi dillerde ise başlarında hiçbir zaman böyle bir konu yoktu.

Benim fikrim, kurucuda kullanmaktan kaçınmanız durumunda new(Bağımlılık Enjeksiyonunu kullanma niyetinizin belirttiği gibi) iyi olmanız gerektiğidir. "Bir yapıcıdaki koşullu veya döngüsel mantık bir kusurun uyarı işaretidir" gibi ifadelere gülerim.

Tüm bunların yanı sıra, şahsen, bir yapıcıda karmaşık bir mantığa sahip olduğu için değil, "kaygıların ayrılması" ilkesini izlemenin iyi olduğu için yapıcıdan XML ayrıştırma mantığını alırdım. Bu yüzden, XML ayrıştırma mantığını, sınıfınıza ait bazı statik yöntemlere değil, tamamen ayrı bir sınıfa taşıyacaktım ModelDef.

düzeltme

Sanırım dışında XML'den bir yöntem ModelDefyaratan bir yönteminiz ModelDefvarsa, bazı dinamik geçici ağaç veri yapısını başlatmanız, XML'nizi ayrıştırıp doldurmanız ve sonra ModelDefbu yapıyı yapıcı parametresi olarak yeni geçidinizi oluşturmanız gerekecek . Yani, belki de "Oluşturucu" modelinin bir uygulaması olarak düşünülebilir. Yapmak istediklerinizle String& StringBuilderçifti arasında çok yakın bir benzetme var . Ancak, bana açık olmayan nedenlerden dolayı, katılmıyorum görünen bu soru-cevap buldum: Stackoverflow - StringBuilder ve Builder Pattern . Bu yüzden, burada StringBuilder"inşaatçı" modelini uygulayıp uygulamamaya yönelik uzun bir tartışmadan kaçınmak için nasıl ilham aldığımı söyleyebilirim.StrungBuilder İhtiyaçlarınıza uygun bir çözüm bulmak için çalışır ve bu küçük ayrıntı çözülene kadar "Oluşturucu" modelinin bir uygulamasını çağırmayı ertelersiniz.

Bu yepyeni soruya bakın: Programcılar SE: “StringBuilder”, Builder Design Pattern'in bir uygulaması mı?


3
@RichardLevasseur Ben sadece C ++ programcıları doksanlı yılların başlarında ortalarında bir endişe konusu ve tartışma konusu olduğunu hatırlıyorum. Bu yazıya bakarsanız: gotw.ca/gotw/066.htm oldukça karmaşık olduğunu göreceksiniz ve oldukça karmaşık şeyler tartışmalıdır. Emin bilmiyorum, ama ben düşünüyorum o şeyler erken doksanlı kısmen bile henüz standardize edilmediğini. Fakat üzgünüm, iyi bir referans sağlayamıyorum.
Mike Nakis

1
@Gurtz Uygulamaya özel "xml yardımcı programları" gibi bir sınıf düşünecektim, çünkü xml dosyasının formatı (veya belgenin yapısı) muhtemelen, geliştirdiğiniz belirli bir uygulamaya bağlı olarak, olasılıkları ne olursa olsun "ModelDef" inizi yeniden kullanın.
Mike Nakis

1
@Gurtz, muhtemelen onlara ana "Uygulama" sınıfının örnek metotlarını ya da çok fazla güçlük çekiyorsa, o zaman bazı yardımcı sınıfların statik yöntemlerini, Ross Patterson'un önerdiğine çok benzer şekilde yapardım.
Mike Nakis

1
@Gurtz, daha önce “üretici” yaklaşımını özellikle ele almadığı için özür diler. Cevabımı değiştirdim.
Mike Nakis

3
@ Gurtz Mümkün ancak akademik merak dışında, farketmez. "Patern karşıtı patern" e girmeyin. Kalıplar, ortak / yararlı kodlama tekniklerini başkalarına rahatça tanımlayan isimlerdir. Yapmanız gerekeni yapın, daha sonra açıklamanız gerekiyorsa üzerine bir etiket yapıştırın. Kodunuz mantıklı olduğu müddetçe, "bir şekilde, bir şekilde, belki de oluşturucu paterni" olan bir şeyi uygulamak tamamen sorun değil. Yeni teknikler öğrenirken kalıplara odaklanmak mantıklıdır, sadece yaptığınız her şeyin adlandırılmış kalıp olması gerektiğini düşünme tuzağına düşmeyin.
Jason C

9

Bu işi ModelDefyapıcıda yapmamak için zaten en iyi nedenleri veriyorsunuz :

  1. Bir XML belgesini bir düğüm ağacına ayrıştırmada "hafif" bir şey yoktur.
  2. ModelDefBunun yalnızca bir XML belgesinden oluşturulabileceğini söyleyen hiçbir şey olmadığı ortada .

Sınıf gibi statik çeşitli yöntemler vardır gerek yani ModelDef.FromXmlString(string xmlDocument), ModelDef.FromXmlDoc(XmlDoc parsedNodeTree), vb


Yanıt için teşekkürler! Statik yöntemlerin önerisi ile ilgili olarak. Bunlar bir ModelDef örneği oluşturan statik fabrikalar mıdır (çeşitli xml kaynaklarından)? Yoksa önceden yaratılmış bir ModelDef nesnesini yüklemekle mi sorumlu olacaklar? İkincisi ise, nesnenin yalnızca kısmen başlatılması konusunda endişeliyim (çünkü ModelDef'in tamamen başlatılması için bir düğüm ağacına ihtiyacı var). Düşünceler?
Gurtz

3
Kusura bakma, ama evet, Ross'un anlamı, tamamen inşa edilmiş örnekleri veren statik fabrika yöntemleri. Tam prototip gibi bir şey olurdu public static ModelDef createFromXmlString( string xmlDocument ). Bu oldukça yaygın bir uygulamadır. Bazen ben de yapıyorum. Sadece müteahhitleri de yapabileceğiniz önerim, alternatif yaklaşımların iyi bir sebep olmaksızın "kosher değil" olarak reddedildiğinden şüphelendiğim durumlarda standart bir cevaptır.
Mike Nakis

1
@ Mike-Nakis, açıkladığınız için teşekkürler. Dolayısıyla, bu durumda statik fabrika yöntemi düğüm ağacını kuracak ve sonra bunu ModelDef'in yapıcısına (muhtemelen özel) geçirecektir. Mantıklı olmak. Teşekkürler.
Gurtz

@Gurtz Kesinlikle.
Ross Patterson,

5

Bu "kuralı" daha önce duymuştum. Tecrübelerime göre hem doğru hem de yanlış.

Daha "klasik" nesne yönelimliğinde, durumu ve davranışı içine alan nesnelerden bahsediyoruz. Bu nedenle, bir nesne yapıcısı, nesnenin geçerli bir duruma başlatıldığından emin olmalıdır (ve sağlanan argümanlar nesneyi geçerli kılmazsa bir hatayı işaret eder). Bir nesnenin geçerli bir duruma başlatıldığından emin olmak bana gerçek iş gibi geliyor. Ve bu fikir sadece yapıcı aracılığıyla geçerli duruma başlatma izin veren bir nesne varsa, yararları vardır ve nesne düzgün de denetler halini değiştirdiği her yöntem bir şey bad devleti değişmemesini böylece devlet kapsüller ... o zaman özünde esas olan nesne "her zaman geçerli" olduğunu garanti eder. Bu gerçekten güzel bir özellik!

Bu yüzden sorun genellikle bir şeyi test etmek ve alay etmek için her şeyi küçük parçalara ayırmaya çalıştığımızda ortaya çıkar. Çünkü eğer bir nesne gerçekten doğru bir şekilde kapsüllenmişse, o zaman gerçekten oraya giremez ve FooBarService'i alaylı FooBarService ile değiştiremezsiniz ve (muhtemelen) sadece testlerinize göre willy-nilly değerlerini değiştiremezsiniz (veya basit bir ödevden çok daha fazla kod).

Böylece, KATI olan diğer “düşünce okulu” nu alırız. Ve bu düşünce okulunda, yapıcıda gerçek iş yapmamamız gerektiği daha büyük olasılıkla daha doğrudur. SOLID kodu genellikle (ancak her zaman değil) test edilmesi daha kolaydır. Ancak bununla ilgili olarak daha zor olabilir. Kodumuzu tek bir sorumlulukla küçük nesnelere ayırıyoruz ve bu nedenle nesnelerimizin çoğu artık devletlerini kapsıyor (ve genellikle ya devlet ya da davranış içermiyorlar). Validasyon kodu genellikle validatör sınıfına çıkarılır ve durumdan ayrı tutulur. Onları almak ve olmaya Ama şimdi Kaybettiğimiz uyum, artık emin bizim nesneler geçerli olduğunu olabilir tamamencisimle ilgili bir şey yapmadan önce, cisim hakkında sahip olduğumuzu düşündüğümüz ön koşulların doğru olduğunu daima doğrulamalıyız. (Elbette, genel olarak bir katmanda doğrulama yaparsınız ve nesnenin daha düşük katlarda geçerli olduğunu varsayarsınız.) Ancak test edilmesi daha kolaydır!

Peki kim haklı?

Gerçekten kimse yok. Her iki düşünce okulunun da kendi değerleri vardır. Şu anda SOLID tüm öfke ve herkes SRP ve Açık / Kapalı ve tüm bu caz hakkında konuşuyor. Ancak bir şeyin popüler olması, her uygulama için doğru tasarım seçimi olduğu anlamına gelmez. Bu yüzden değişir. SOLID ilkelerini yoğun şekilde takip eden bir kod tabanında çalışıyorsanız, evet, yapıcıdaki gerçek iş muhtemelen kötü bir fikirdir. Ama aksi takdirde, duruma bakın ve kararınızı kullanmaya çalışın. Nesneniz kurucuda çalışarak hangi özellikleri kazanır , hangi özellikleri kaybeder ? Uygulamanızın genel mimarisine ne kadar uyuyor?

Yapıcıdaki gerçek iş bir antipattern değildir, doğru yerlerde kullanıldığında tam tersi olabilir. Ancak açıkça belgelenmelidir (varsa istisnalar birlikte verilebilir) ve herhangi bir tasarım kararında olduğu gibi - mevcut kod tabanında kullanılan genel stile uygun olmalıdır.


Bu harika bir cevap.
jrahhali

0

Bu kurala ilişkin temel bir sorun var ve bu da “gerçek işi” neyin nesi?

Yazarın “gerçek iş” in ne olduğunu tanımlamaya çalıştığı sorusuna gönderilen orijinal makaleden görebilirsiniz , ancak ciddi bir şekilde kusurlu. Bir uygulamanın iyi olması için iyi tanımlanmış bir ilke olması gerekir. Bununla demek istediğim, yazılım mühendisliği açısından, fikrin taşınabilir (herhangi bir dile agnostik), test edilmiş ve kanıtlanmış olması gerektiğidir. Bu makalede tartışılanların çoğu bu ilk kritere uymuyor. İşte yazarın “gerçek işi” neyin neyin oluşturduğuna ve niçin kötü tanımların olmadığına dair bu makalede bahsettiği bazı göstergeler.

Kullanımına newanahtar kelime . Bu tanım temel olarak hatalı çünkü alana özel. Bazı diller newanahtar kelimeyi kullanmaz . Nihayetinde ima ettiği şey, başka nesneler inşa etmemesi gerektiğidir. Ancak birçok dilde en temel değerler bile nesnedir. Dolayısıyla yapıcıya atanan herhangi bir değer, yeni bir nesne de inşa ediyordur. Bu, bu fikri bazı dillerle sınırlı kılar ve “gerçek işi” neyin oluşturduğuna dair kötü bir göstergedir.

Oluşturucu tamamlandıktan sonra nesne tam olarak başlatılmadı . Bu iyi bir kuraldır, ancak aynı zamanda bu makalede belirtilen diğer kurallardan birkaçıyla çelişmektedir. Bunun diğerleriyle nasıl çelişebileceğine dair iyi bir örnek, beni buraya getiren soruya değiniyor. Bu soruda birisi, sortyöntemi, bu ilkeden dolayı JavaScript gibi görünen bir yapıcıda kullanma konusunda endişeli . Bu örnekte, kişi, diğer nesnelerin sıralanmış bir listesini temsil eden bir nesne yaratıyordu. Tartışma amacıyla, sıralanmamış bir nesne listesine sahip olduğumuzu ve sıralanmış bir listeyi temsil etmek için yeni bir nesneye ihtiyacımız olduğunu hayal edin. Bu yeni nesneye ihtiyacımız var, çünkü yazılımımızın bir kısmı sıralanmış bir liste bekliyor ve bu nesneyi çağıralımSortedList. Bu yeni nesne sıralanmamış bir listeyi kabul eder ve elde edilen nesne şimdi sıralanan nesne listesini temsil etmelidir. Bu dokümanda belirtilen diğer kuralları, yani statik yöntem çağrılarını, kontrol akış yapılarını, atamaktan başka bir şeyi izlemememiz durumunda, sonuçta elde edilen nesne, diğer kuralı tamamen baştan sona erdiren geçerli bir durumda inşa edilmeyecektir. Yapıcı bittikten sonra. Bunu düzeltmek için, sıralanmamış listeyi yapıcıda sıralamak için bazı temel çalışmaları yapmamız gerekir. Bunu yapmak diğer 3 kuralı ihlal eder, diğer kuralları alakasız yapar.

Sonuçta, bir kurucuda "gerçek iş" yapmama kuralı tanımlanmamıştır ve hatalıdır. Gerçek işin ne olduğunu tanımlamaya çalışmak boşuna bir alıştırmadır. Bu makaledeki en iyi kural, bir inşaatçı bitirdiğinde tamamen başlatılması gerektiğidir. Bir yapıcıda ne kadar çalışıldığını sınırlayacak başka en iyi uygulamaların bir bolluğu vardır. Bunların çoğu SOLID ilkelerinde özetlenebilir ve bu aynı ilkeler kurucuda çalışmanızı engellemez.

PS. Yapıcıda bazı işler yapmakta yanlış bir şey olmadığını iddia ederken, bir sürü iş yapmanın da bir yer olmadığını söylemek zorunda olduğumu söylemek zorunda hissediyorum. SRP, bir kurucunun geçerli kılmak için yeterli işi yapması gerektiğini önerecektir. Eğer kurucunuz çok fazla kod satırına sahipse (bildiğim kadar öznel), o zaman muhtemelen bu prensibi ihlal ediyordur ve muhtemelen daha küçük daha iyi tanımlanmış yöntem ve nesnelere bölünebilir.

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.