En az bilgi ilkesi


32

En az bilgi ilkesinin arkasındaki nedeni anlıyorum ancak tasarımımda uygulamaya çalışırsam bazı dezavantajları buluyorum.

Birinci Tasarım Desenleri Başlığı kitabında bulduğum bu ilkenin örneklerinden biri (aslında onu nasıl kullanmayacağımı), bu yöntemi başka yöntemlerden çağırmaktan döndürülen nesneler üzerinde bir yöntem çağırmanın yanlış olduğunu belirtiyor. .

Ancak, bazen bu yetenekleri kullanmak çok gerekli görünüyor.

Örneğin: Birkaç sınıfa sahibim: video yakalama sınıfı, kodlayıcı sınıfı, flama sınıfı ve hepsi temel bazı diğer sınıfları, VideoFrame kullanıyor ve birbirleriyle etkileşime girdiklerinden, örneğin şöyle bir şey yapabilirler:

streamer sınıf kodu

...
frame = encoder->WaitEncoderFrame()
frame->DoOrGetSomething();
....

Gördüğünüz gibi, bu ilke burada uygulanmadı. Bu ilke burada uygulanabilir mi, yoksa bu ilkenin böyle bir tasarımda her zaman uygulanamaması mı?




4
Bu muhtemelen daha yakın bir kopya.
Robert Harvey

Yanıtlar:


21

Fonksiyonlar için bahsettiğiniz prensip (daha iyi Demeter Yasası olarak bilinir ) , streamer sınıfınıza başka bir yardımcı metot ekleyerek uygulanabilir.

  {
    frame = encoder->WaitEncoderFrame()
    DoOrGetSomethingForFrame(frame); 
    ...
  }

  void DoOrGetSomethingForFrame(Frame *frame)
  {
     frame->DoOrGetSomething();
  }  

Şimdi, her işlev "arkadaşlarla" değil, yalnızca "arkadaşlarla konuşuyor".

IMHO, tek sorumluluk ilkesini daha katı takip eden yöntemler oluşturmaya yardımcı olabilecek kaba bir kılavuzdur. Yukarıdaki gibi basit bir durumda, bunun gerçekten uğraşmaya değip değmeyeceği ve ortaya çıkan kodun gerçekten "temiz" olup olmadığı ya da kodunuzu kayda değer bir kazanç olmadan resmen genişletecek olması muhtemeldir.


Bu yaklaşım, ünite testi, hata ayıklama ve kodunuzun nedenini çok daha kolay hale getirme avantajına sahiptir.
gntskn

+1. Yine de, bu kod değişikliğini yapmamak için çok iyi bir neden var, çünkü sınıf arayüzü, işi sadece bir ya da birkaç çağrıyı birkaç başka yöntemle (ve ek mantık olmadan) yapan yöntemlerle şişirilmiş hale gelebilir. Bazı programlama ortamlarında, C ++ COM (özellikle WIC ve DirectX) diyelim, her bir yöntemin bir COM arayüzüne diğer dillere kıyasla eklenmesiyle ilgili yüksek bir maliyet vardır.
rwong

1
C ++ COM arayüz tasarımında, büyük sınıfları küçük sınıflara ayırmak (bu, birden fazla nesneyle konuşma şansınız daha yüksek olur) ve arayüz yöntemlerinin sayısını en aza indirgemek, gerçek bir anlayışla (maliyet azaltma) derinlemesine anlayışlı iki tasarım hedefidir. iç mekaniğin (sanal tablolar, kodun tekrar kullanılabilirliği, diğer birçok şey). Bu nedenle, C ++ COM programcıları genellikle LoD'yi gözardı etmelidir.
rwong

10
+1: Demeter Yasası gerçekten Demeter'in Önerisi
Binary Worrier

Demeter yasası, mimari seçimler yapmak için bir öneridir; kozmetik bir değişim önerirken, bu yasaya uyduğunuz anlaşılıyor. Bu bir yanlış anlama olabilir, çünkü sözdizimsel bir değişiklik birdenbire her şeyin yolunda gideceği anlamına gelmez. Demeter yasasını bildiğim kadarıyla temelde: OOP yapmak istiyorsanız, her yerde alıcı işlevleriyle prodecural kod yazmayı bırakın.
user2180613

39

En az bilgi veya Demeter Yasası ilkesi, sınıfınızı katmandan sonra geçen diğer sınıfların ayrıntılarıyla dolaştırmaya karşı bir uyarıdır. Size sadece "arkadaşların" ile değil, "arkadaşların" ile konuşmanın daha iyi olduğunu söyler.

Parlak plaka zırhındaki bir şövalye heykeli üzerine bir kalkan kaynaklamanız istendiğini hayal edin. Kalkanı dikkatlice sol kola yerleştirirsiniz, böylece doğal görünür. Önkolda, dirsekte ve kalkanın zırha temas ettiği üst kolda üç küçük yer olduğunu fark edersiniz. Üç yeri de kaynakladınız çünkü bağlantının güçlü olduğundan emin olmak istersiniz. Şimdi patronunun delirdiğini hayal et, çünkü zırhının dirseğini hareket ettiremiyor. Zırhın asla hareket etmeyeceğini varsaydın ve böylece önkol ile üst kol arasında hareketsiz bir bağlantı yarattın. Kalkan sadece arkadaşına bağlanmalı, önkol. Arkadaşların kollarına değil. Dokunmaları için bir metal parçası eklemeniz gerekse bile.

Metaforlar iyi ama arkadaşça ne kastediyoruz? Bir nesnenin nasıl yaratılacağını veya bulunacağını bildiği herhangi bir şey bir arkadaştır. Alternatif olarak, bir nesne sadece onun sadece arayüzü bildiği başka nesnelerin kullanılmasını isteyebilir . Bunlar arkadaş olarak sayılmaz çünkü onları nasıl empoze edeceğine dair hiçbir beklenti yoktur. Nesne, nereden geldiklerini bilmediği için başka bir şey geçti / enjekte ettiğinden bir arkadaşın arkadaşı değil, arkadaş bile değildir. Nesnenin sadece nasıl kullanılacağını bildiği bir şey. Bu iyi birşey.

Bu gibi prensipleri uygulamaya çalışırken, bir şeyi başarmanızı asla yasaklamadıklarını anlamak önemlidir. Aynı şeyi gerçekleştiren daha iyi bir tasarım elde etmek için daha fazla iş yapmayı ihmal ettiğinize dair bir uyarıdır.

Hiç kimse sebepsiz yere çalışmak istemez, bu yüzden neyin peşinden gittiğini anlamak önemlidir. Bu durumda kodunuzu esnek tutar. Değişiklikler yapabilir ve endişelenmeniz gereken değişikliklerden etkilenen daha az sayıda başka sınıfa sahip olabilirsiniz. Kulağa hoş geliyor ama bir tür dini doktrin olarak almadığınız sürece ne yapacağınıza karar vermenize yardımcı olmuyor.

Bu prensibi kör takip etmek yerine, bu problemin basit bir versiyonunu alın. Prensibi takip etmeyen ve uygulayan bir çözüm yazın. Artık iki çözümünüz olduğuna göre, ikisini de yapmaya çalışarak değişikliklerin ne kadar alıcı olduğunu karşılaştırabilirsiniz.

Bu prensibi uygularken bir problemi çözemezseniz, kaçırdığınız bir başka beceri daha vardır.

Özel probleminize bir çözüm, enjekte etmek. frame çerçevelerle nasıl konuşulacağını bilen bir şeye (bir sınıf veya yöntem) , böylece sınıfınızdaki tüm bu çerçeve sohbet ayrıntılarını yaymak zorunda kalmazsınız. bir çerçeve al.

Bu aslında başka bir prensibi izler: İnşaattan ayrı kullanım.

frame = encoder->WaitEncoderFrame()

Bu kodu kullanarak, bir şekilde a Frame. A ile konuşmak için henüz bir sorumluluk üstlenmediniz Frame.

frame->DoOrGetSomething(); 

Şimdi nasıl konuşacağınızı bilmek zorundasınız Frameama bununla değiştirin:

new FrameHandler(frame)->DoOrGetSomething();

Ve şimdi sadece arkadaşın FrameHandler ile nasıl konuşacağını bilmen gerekiyor.

Bunu başarmanın birçok yolu vardır ve bu belki de en iyisi değildir, ancak prensibi izlemenin sorunu çözülemez hale getirmediğini gösterir. Sadece senden daha fazla iş istiyor.

Her iyi kuralın bir istisnası vardır. Bildiğim en iyi örnekler, Dahili Etki Alanı Özel Dilleridir . Bir DSL s yöntem zinciriHer zaman Demeter Yasasını kötüye kullanıyor gibi görünüyor, çünkü sürekli olarak farklı türleri döndüren ve bunları doğrudan kullanan yöntemleri çağırıyorsunuz. Bu neden iyi? Çünkü DSL'de iade edilen her şey doğrudan konuşmanız gereken özenle tasarlanmış bir arkadaş. Tasarım gereği, bir DSL'nin metod zincirinin değişmemesini bekleme hakkınız bulunmaktadır. Ne bulursanız birlikte zincirleme kod tabanına rastgele rastlarsanız, bu hakkınız yoktur. En iyi DSL'ler, ikisine de girmemelisiniz, çok ince sunumlar veya diğer nesnelere arayüzlerdir. Bunu sadece söylüyorum çünkü DSL'lerin neden iyi bir tasarım olduğunu öğrendiğimde demeter yasasını çok daha iyi anladığımı gördüm. Bazıları DSL’lerin demeterlerin gerçek yasalarını bile ihlal etmediğini söyleyecek kadar ileri gidiyor.

Başka bir çözüm, başka bir şeyin framesize girmesine izin vermektir . Eğer framebir setter veya sonra inşa veya bir çerçeve elde hiçbir sorumluluk almıyorsun tercihen bir yapıcı önce geldi. Bu, sizin burada rolünüzün olması gibi bir FrameHandlersşey olduğu anlamına geliyor . Bunun yerine, şimdi onunla sohbet eden Frameve başka bir şey yapan kişi sizsiniz. Nasıl elde edeceğinizi düşünen kişi Frame bu şekilde basit bir bakış açısı değişikliği ile aynı çözümdür.

KATI ilkeler takip etmeye çalıştığım büyük olanlar. Burada saygı duyulan iki kişi Tek Sorumluluk ve Bağımlılık İnversiyon İlkeleridir. Bu ikisine saygı duymak gerçekten zor ama yine de Demeter Yasasını ihlal ediyor.

Demeter'i ihlal eden zihniyet, ne istersen aldığın bir büfe restoranda yemek yemek gibidir. Küçük bir çalışma ile kendinize bir menü ve istediğiniz gibi bir şeyler getirecek bir sunucu sağlayabilirsiniz. Arkanıza yaslanın, rahatlayın ve iyi bahşiş verin.


2
"Size sadece" arkadaşlarınız "ile değil," arkadaşlarınız "ile konuşmanın daha iyi olacağını söyler" ++
RubberDuck

1
İkinci paragraf, bu bir yerden çalındı ​​mı, yoksa telafi mi ettin? Bu bana kavramı tamamen anlamamı sağladı . Hangi "arkadaş arkadaş" ın ne olduğunu ve çok fazla dersi (birleşme yeri) karıştırmanın sakıncaları olduğunu açıkça belirledi. A + teklifi.
maus

2
@ diemaus Eğer bu kalkan paragrafını bir yerden çaldıysam, o zamandan beri kaynağı kafamdan sızdırıyor. O sırada beynim zekice olduğunu düşündü. Hatırladığım şey, çok fazla oy kullandıktan sonra ekledim, bu yüzden doğrulanmış olduğunu görmek güzel. Yardım etmesine sevindim.
candied_orange

19

İşlevsel tasarım nesne yönelimli tasarımdan daha mı iyidir? Değişir.

MVVM, MVC'den daha mı iyi? Değişir.

Amos ve Andy veya Martin ve Lewis? Değişir.

Bu neye bağlıdır? Yaptığınız seçimler, her bir tekniğin veya teknolojinin yazılımınızın işlevsel ve işlevsel olmayan gereksinimlerini ne kadar iyi karşıladığına, tasarımınızı, performansınızı ve bakım hedeflerinizi yeterince karşılar.

[Bazı kitaplar] [bir şeyin] yanlış olduğunu söylüyor.

Bunu bir kitapta veya blogda okuduğunuzda hak talebine göre talebi değerlendirin; bu, neden diye sor. Yazılım geliştirmede doğru ya da yanlış teknik yoktur, sadece "bu teknik hedeflerime ne kadar iyi geliyor? Etkili mi, etkisiz mi? Bir sorunu çözüyor mu, yeni bir tane yaratıyor mu? Bütünüyle iyi anlaşılıyor mu? geliştirme ekibi mi yoksa çok mu belirsiz? "

Bu özel durumda - başka bir yöntemin döndürdüğü bir nesneye bir yöntem çağırma - bu uygulamayı kodlayan gerçek bir tasarım deseni olduğundan (Fabrika), bunun kategorik olarak nasıl bir iddiada bulunabileceğini hayal etmek zor yanlış.

Bunun “En Az Bilgi Prensibi” olarak adlandırılmasının sebebi, “Düşük Eşleşmenin” arzulanan bir sistemin kalitesi olmasıdır. Birbirine sıkıca bağlı olmayan nesneler daha bağımsız çalışır ve bu nedenle ayrı ayrı bakımı ve değiştirilmesi kolaydır. Ancak örneğinizin gösterdiği gibi, yüksek eşleştirmenin daha fazla istendiği zamanlar vardır, böylece nesneler çabalarını daha etkin bir şekilde koordine edebilir.


2

Doktor Brown'un cevabı , Demeter Hukukunun klasik bir ders kitabı uygulamasını gösteriyor - ve bu şekilde onlarca yöntem eklemenin can sıkıntısı / düzensiz kod bloğu, muhtemelen kendileri de dahil olmak üzere programcıların sık sık bunu yapmak zorunda kalmasalar bile zahmet etmiyor olmalarıdır.

Nesnelerin hiyerarşisini ayırmanın alternatif bir yolu var:

Yöntemleriniz ve özellikleriniz aracılığıyla türler interfaceyerine türleri gösterin class.

Orijinal Poster'in (OP) durumunda, yerine a değerini encoder->WaitEncoderFrame()döndürür ve hangi işlemlere izin verileceğini tanımlar.IEncoderFrameFrame


ÇÖZÜM 1

En kolay durumda Frameve Encodersınıflar her ikisi de sizin kontrolünüz altındadır, IEncoderFrameFrame zaten kamuya açık bir şekilde ortaya konan yöntemlerin bir alt kümesidir ve Encodersınıf aslında o nesneye ne yaptığınızla ilgilenmez. O zaman, uygulama önemsizdir ( c # kodu ):

interface IEncoderFrame {
    void DoOrGetSomething();
}

class Frame : IEncoderFrame {
    // A method that already exists in Frame.
    public void DoOrGetSomething() { ... }
}

class Encoder {
    private Frame _frame;
    public IEncoderFrame TheFrame { get { return _frame; } }
    ...
}

ÇÖZÜM 2

FrameTanımın kontrolünüz altında olmadığı veya IEncoderFrameyöntemlerin eklenmesinin uygun olmadığı bir orta durumda, o Framezaman iyi bir çözüm bir Adaptördür . Yani ne CandiedOrange cevabı olarak, adı geçen new FrameHandler( frame ). ÖNEMLİ: Bunu yaparsanız , bir sınıf olarak değil, bir arabirim olarak göstermeniz daha esnektir . bilmek zorunda , ama müşterilerin sadece bilmesi gerekiyor . Ya da adlandırdığım gibi, - Kodlayıcının POV'sinden görüldüğü gibi özel olarak Frame olduğunu belirtmek için :Encoderclass FrameHandlerinterface IFrameHandlerinterface IEncoderFrame

interface IEncoderFrame {
    void DoOrGetSomething();
}

// Adapter pattern. Appropriate if no access needed to Encoder.
class EncoderFrameWrapper : IEncoderFrame {
    Frame _frame;
    public EncoderFrameWrapper( Frame frame ) {
        _frame = frame;
    }
    public void DoOrGetSomething() {
        _frame....;
    }
}

class Encoder {
    private Frame _frame;

    // Adapter pattern. Appropriate if no access needed to Encoder.
    public IEncoderFrame TheFrame { get { return new EncoderFrameWrapper( _frame ); } }

    ...
}

MALİYET: Yeni bir nesnenin tahsisi ve GC'si, EncoderFrameWrapper, her zaman encoder.TheFrameçağrılır. (Bu sargıcıyı önbelleğe alabilirsiniz, ancak bu daha fazla kod ekler. Ayrıca, kodlayıcının kare alanı yeni bir kareyle değiştirilemezse yalnızca güvenilir bir şekilde kodlamak kolaydır.)


ÇÖZÜM 3

Daha zor durumda, yeni ambalajın hem Encoderve hem de hakkında bilmesi gerekir Frame. Bu nesnenin kendisi LoD'yi ihlal eder - Encoder ile Frame arasında, Encoder'ın sorumluluğunda olması gereken bir ilişkiyi manipüle ediyor - ve muhtemelen haklı olmak için acı verici. İşte o yola başlarsanız neler olabilir:

interface IEncoderFrame {
    void DoOrGetSomething();
}

// *** You will end up regretting this. See next code snippet instead ***
class EncoderFrameWrapper : IEncoderFrame {
    Encoder _owner;
    Frame _frame;
    public EncoderFrameWrapper( Encoder owner, Frame frame ) {
        _owner = owner;   _frame = frame;
    }
    public void DoOrGetSomething() {
        _frame.DoOrGetSomething();
        // Hmm, maybe this wrapper class should be nested inside Encoder...
        _owner... some work inside owner; maybe should be owner-internal details ...
    }
}

class Encoder {
    private Frame _frame;

    ...
}

Bu çirkindi. Sargının yaratıcısının / sahibinin (Encoder) ayrıntılarına dokunması gerektiğinde daha az karmaşık bir uygulama vardır:

interface IEncoderFrame {
    void DoOrGetSomething();
}

class Encoder : IEncoderFrame {
    private Frame _frame;

    // HA! Client gets to think of this as "the frame object",
    // but its really me, intercepting it.
    public IEncoderFrame TheFrame { get { return this; } }

    // This is the method that the LoD approach suggests writing,
    // except that we are exposing it only when the instance is accessed as an IEncoderFrame,
    // to avoid extending Encoder's already large API surface.
    public void IEncoderFrame.DoOrGetSomething() {
        _frame.DoOrGetSomething();
       ... make some change within current Encoder instance ...
    }
    ...
}

Kabul ediyorum, buraya geleceğimi bilseydim, bunu yapmayabilirdim. LoD yöntemlerini yazabilir ve onunla yapılabilir. Arayüz tanımlamanıza gerek yok. Öte yandan, arayüzün ilgili yöntemleri bir araya getirmesini seviyorum. Çerçeve gibi hissettiren "çerçeve benzeri işlemleri" yapmanın nasıl bir his olduğunu seviyorum.


SON YORUMLAR

Bunu göz önünde bulundurun: Maruz kalmanın keçe uygulayıcısı genel mimarisine uygunsa veya "LoD uygulamasından çok daha kolay" olsaydı, bunun yerine gösterdiğim ilk pasajı yaparlarsa çok daha güvenli olurdu - sınırlı bir alt kümesini ortaya çıkardı Arayüz olarak çerçeve. EncoderFrame frame Benim tecrübeme göre, bu genellikle tamamen uygulanabilir bir çözümdür. Arayüze gerekli yöntemleri ekleyin. (Frame'in zaten gerekli metotlara sahip olduğunu veya bilmemiz gereken tartışmasız olacağını bildiğimiz bir senaryodan bahsediyorum. Her yöntemin "uygulama" çalışması, arayüz tanımına bir satır ekliyor.) Ve burada, kaldırarak - hatta en kötü gelecek senaryosunda, API çalışmaya devam etmek mümkün olduğunu biliyoruz IEncoderFramedanFrameEncoder.

Ayrıca dikkat Eklemek izniniz yok eğer IEncoderFrameiçin Frameveya gerekli yöntemler genel iyi uymaz Frameçözüm 2. belki de ekstra nesne oluşturma-ve-imha, size uygun değildir sınıfı ve, çözüm # 3, EncoderLoD'yi başarmanın yöntemlerini düzenlemenin basit bir yolu olarak görülebilir . Sadece düzinelerce yöntemi geçmeyin. Bunları bir Arabirime sarın ve "açık arabirim uygulaması" (c # kullanıyorsanız) kullanın; böylece nesneye yalnızca bu arabirimden bakıldığında erişilebilirler.

Vurgulamak istediğim bir diğer nokta , işlevselliği bir arayüz olarak gösterme kararının yukarıda açıklanan durumların 3'ünü de ele almasıdır. İlki, IEncoderFramesadece Frameişlevselliğinin bir alt kümesidir . İkincisi, IEncoderFramebir adaptördür. Üçüncüsü, işlevselliğine IEncoderFramebir bölümdür Encoder. Gereksinimlerinizin bu üç durum arasında değişip değişmediği önemli değildir: API aynı kalır.


Sınıfınızı, somut bir sınıf yerine ortak çalışanlarından birinin döndürdüğü bir arayüze bağlamak, aynı zamanda bir gelişme de bir bağlantı kaynağıdır. Arayüzün değişmesi gerekiyorsa, sınıfınızın değişmesi gerektiği anlamına gelir. Götürmek önemli nokta olduğunu kavrama doğal kötü değil sürece emin önlemek yaptıkça gereksiz çift için kararsız nesneler ya da iç yapıları . Bu yüzden Demeter Yasasının büyük bir tutam tuzla alınması gerekir; şartlara bağlı olarak her zaman problem yaratabilecek bir şeyden kaçınmanızı ister.
Periata Breatta

@PeriataBreatta - Buna kesinlikle katılıyorum ve katılmıyorum. Ama bir şey işaret etmek istiyorum: Bir Arayüz tanımı gereği, ne temsil ihtiyacı iki sınıf arasındaki sınır olarak bilinmek. Eğer "değişmesi gerekiyorsa", bu esastır - hiçbir alternatif yaklaşım gerekli kodlamayı sihirli bir şekilde önleyemezdi. Açıkladığım üç durumdan herhangi birini yaparak ancak bir arayüz kullanmıyorsanız, bunun yerine somut bir sınıf vermenin aksine. 1, Kare, 2, EncoderFrameWrapper, 3, Encoder. Kendini bu yaklaşıma kilitledin . Arayüz hepsine adapte olabilir.
ToolmakerSteve

@PeriataBreatta ... açıkça bir arayüz olarak tanımlamanın faydasını gösterir. Sonunda bu kadar rahat hale getirmek için bir IDE geliştirmek için umarım, arayüzler çok daha yoğun kullanılır. Çok seviyeli erişimlerin çoğu, bir arabirim aracılığıyla gerçekleşir, bu nedenle değişiklikleri yönetmek çok daha kolaydır. (Bu, bazı durumlarda "aşırı derecede" ise, küçük performans artışı karşılığında bir arayüze sahip olmama riskini almak istediğimiz yerdeki açıklamalarla birlikte kod analizi, "onu derlemek" olabilir - yerine 3 "çözüm" den bu 3 somut sınıftan biri.)
ToolmakerSteve

@PeriataBreatta - ve "arayüz hepsine adapte olabilir" dediğimde, "ahhhh, bu bir dil özelliğinin bu farklı durumları kapsaması harika" demiyorum. Arayüzleri tanımlamanın , yapmanız gerekebilecek değişiklikleri en aza indirdiğini söylüyorum . En iyi durumda, tasarım, en basit durumdan (çözüm 1), en zor duruma (çözüm 3) kadar tüm yolu değiştirebilir, ara yüz hiç değişmeden - sadece üreticinin iç ihtiyaçları daha karmaşık hale geldi. Değişime ihtiyaç duyulduğunda bile, daha az yaygın olma eğilimindedirler, IMHO.
ToolmakerSteve
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.