Açık-kapalı prensibinin avantajlarından yararlanıyor musunuz?


12

Açık-kapalı prensibi (OCP), bir nesnenin uzatma için açık, ancak değişiklik için kapalı olması gerektiğini belirtir. Anlıyorum ve SRP ile birlikte kullanarak sadece bir şey yapan sınıflar yaratmak için kullanıyorum. Ve, tüm davranış denetimlerini bazı alt sınıflarda genişletilebilen veya geçersiz kılınabilecek yöntemlere çıkarmayı mümkün kılan birçok küçük yöntem oluşturmaya çalışıyorum. Böylece, bağımlılık enjeksiyonu ve kompozisyonu, olaylar, delegasyon vb. Yoluyla birçok uzatma noktasına sahip sınıflarla sonuçlanıyorum.

Aşağıdakileri basit, genişletilebilir bir sınıf olarak düşünün:

class PaycheckCalculator {
    // ...
    protected decimal GetOvertimeFactor() { return 2.0M; }
}

Şimdi, örneğin, OvertimeFactor1.5 olarak değiştiğini söyleyin . Yukarıdaki sınıf genişletilmek üzere tasarlandığından, kolayca alt sınıflara ayırıp farklı bir ürüne geri dönebilirim OvertimeFactor.

Ancak ... sınıf, OCP'ye genişletme ve yapıştırma için tasarlanmış olmasına rağmen, söz konusu yöntemi alt sınıflandırma ve geçersiz kılma ve ardından nesnelerimi IoC kapsayıcıma yeniden kablolama yerine söz konusu tek yöntemi değiştireceğim.

Sonuç olarak, OCP'nin neyi başarmaya çalıştığının bir kısmını ihlal ettim. Sadece tembel olduğumu hissediyorum çünkü yukarıda biraz daha kolay. OCP'yi yanlış mı anlıyorum? Gerçekten farklı bir şey yapmalı mıyım? OCP'nin avantajlarından farklı olarak yararlanıyor musunuz?

Güncelleme : cevaplara dayanarak, bu farklı örnek bir dizi farklı nedenden dolayı fakir bir örnek gibi görünüyor. Örneğin ana amacı sınıfı geçersiz kılınmış davranışını değiştirecek zaman bu yöntemler sağlamak suretiyle uzatılabilir tasarlanmıştır gösterilmesidir halka değiştirmek için gerek kalmadan yöntemler veya özel kod. Yine de, OCP'yi kesinlikle yanlış anladım.

Yanıtlar:


10

Temel sınıfı değiştiriyorsanız, gerçekten kapalı değil!

Kütüphaneyi dünyaya serbest bıraktığınız durumu düşünün. Eğer gidip fazla mesai faktörünü 1.5 olarak değiştirerek temel sınıfınızın davranışını değiştirirseniz, sınıfın kapalı olduğu varsayılarak kodunuzu kullanan tüm kişileri ihlal etmiş olursunuz.

Gerçekten sınıf kapalı ama açık yapmak için fazla mesai faktörü alternatif bir kaynaktan (yapılandırma dosyası belki) almak veya geçersiz kılınabilecek sanal bir yöntem kanıtlamak gerekir?

Sınıf gerçekten kapalıysa, değişikliğinizden sonra hiçbir test durumu başarısız olmaz (tüm test vakalarınızla% 100 kapsama sahip olduğunuz varsayılarak) ve kontrol eden bir test vakası olduğunu varsayarım GetOvertimeFactor() == 2.0M.

Aşırı Mühendis

Ancak bu açık-kapalı prensibini mantıksal sonuca götürmeyin ve her şeyi başlangıçtan itibaren yapılandırılabilir hale getirin (yani mühendislik üzerinde). Yalnızca şu anda ihtiyacınız olan bitleri tanımlayın.

Kapalı prensibi, nesneyi yeniden tasarlamanıza engel olmaz. Sadece tanımlanmış genel arabirimi nesnenize değiştirmenizi önler ( korumalı üyeler genel arabirimin bir parçasıdır). Eski işlevler bozuk olmadığı sürece daha fazla işlev ekleyebilirsiniz.


"Kapalı ilke, nesneyi yeniden yapılandırmanıza engel teşkil etmez." Aslında öyle . Açık-Kapalı İlke'nin ilk önerildiği kitabı veya "OCP" kısaltmasını tanıtan makaleyi okursanız, "Kimsenin kaynak kod değişikliği yapmasına izin verilmez" (hata hariç) giderir).
Rogério

@ Rogério: Bu doğru olabilir (1988'de). Ancak mevcut tanım (OO'nun popüler hale geldiği 1990'da popüler hale getirildi) Tamamen tutarlı bir kamu arayüzünü korumakla ilgilidir. During the 1990s, the open/closed principle became popularly redefined to refer to the use of abstracted interfaces, where the implementations can be changed and multiple implementations could be created and polymorphically substituted for each other. en.wikipedia.org/wiki/Open/closed_principle
Martin York

Wikipedia referansınız için teşekkürler. Ama hala "mevcut" tanımın gerçekten farklı olduğundan emin değilim, çünkü hala tip (sınıf veya arayüz) mirasına dayanıyor. Ve bahsettiğim "kaynak kodu değişikliği yok" alıntısı Robert Martin'in OCP 1996 makalesinden (sözde) "güncel tanım" ile aynı doğrultuda. Şahsen, sanırım Açık-Kapalı Prensibi bugüne kadar unutulacaktı, Martin ona görünüşte çok fazla pazarlama değerine sahip bir kısaltma vermeseydi. İlkenin kendisi eski ve zararlı, IMO.
Rogério

3

Bu yüzden Açık Kapalı Prensibi, özellikle YAGNI ile aynı anda uygulamaya çalışırsanız . Her ikisine aynı anda nasıl yapışabilirim? Üç kuralını uygulayın . İlk değişiklik yaptığınızda, doğrudan yapın. Ve ikinci kez de. Üçüncü kez, bu değişikliği soyutlama zamanı.

Başka bir yaklaşım, "beni bir kez kandırmak ...", bir değişiklik yapmanız gerektiğinde , gelecekte bu değişime karşı korumak için OCP'yi uygulayın . Neredeyse uzatma oranını değiştirmenin yeni bir hikaye olduğunu önerecek kadar ileri giderdim. "Bir bordro yöneticisi olarak, fazla mesai oranını, yürürlükteki iş kanunlarına uygun olabilmem için değiştirmek istiyorum". Artık fazla mesai oranını değiştirmek için yeni bir kullanıcı arayüzünüz var, bunu depolamanın bir yolu var ve GetOvertimeFactor () deposuna fazla mesai oranının ne olduğunu soruyor.


2

Gönderdiğiniz örnekte, fazla mesai faktörü bir değişken veya sabit olmalıdır. * (Java örneği)

class PaycheckCalculator {
   float overtimeFactor;

   protected float setOvertimeFactor(float overtimeFactor) {
      this.overtimeFactor = overtimeFactor;
   }

   protected float getOvertimeFactor() {
      return overtimeFactor;
   }
}

VEYA

class PaycheckCalculator {
   public static final float OVERTIME_FACTOR = 1.5f;
}

Daha sonra sınıfı genişlettiğinizde faktörü ayarlayın veya geçersiz kılın. "Sihirli sayılar" yalnızca bir kez görünmelidir. Bu, OCP ve DRY (Kendinizi Tekrarlama) tarzında çok daha fazladır, çünkü ilk yöntemi kullanırken farklı bir faktör için tamamen yeni bir sınıf yapmak ve sadece bir deyimdeki sabiti değiştirmek zorunda değildir. ikinci sırada yer.

Her biri farklı sabit değerlere ihtiyaç duyan birden fazla hesap makinesi türünün olacağı durumlarda ilkini kullanacağım. Örnek olarak, genellikle miras alınan türler kullanılarak uygulanan Sorumluluk Zinciri modeli verilebilir. Sadece arabirimi görebilen bir nesne (yani getOvertimeFactor()), ihtiyaç duyduğu tüm bilgileri elde etmek için onu kullanır, alt türler ise gerçek bilgiler sağlamak için endişelenir.

İkincisi, sabitin değiştirilmesinin muhtemel olmadığı, ancak birden fazla konumda kullanıldığı durumlarda kullanışlıdır. Değiştirmek için bir sabite sahip olmak (olası bir durumda), her yere ayarlamak veya bir özellik dosyasından almaktan çok daha kolaydır.

Açık-kapalı prensibi, mevcut nesneyi değiştirmeme çağrısını, arabirimi değiştirmeden bırakma uyarısından daha azdır. Bir sınıftan biraz farklı bir davranışa veya belirli bir vaka için eklenen işleve ihtiyacınız varsa, uzatın ve geçersiz kılın. Ancak sınıfın gereksinimleri değişiyorsa (faktörü değiştirmek gibi), sınıfı değiştirmeniz gerekir. Büyük bir sınıf hiyerarşisinde çoğu kullanılmayan bir anlam yoktur.


Bu bir veri değişikliğidir, kod değişikliği değildir. Fazla mesai oranı sabit kodlanmış olmamalıdır.
Jim C

Get'iniz ve Setiniz geriye dönük görünüyor.
Mason Wheeler

Tüh! test etmeliydim ...
Michael K

2

Örneğinizi gerçekten OCP'nin büyük bir temsili olarak görmüyorum. Kuralın gerçekten ne anlama geldiğini düşünüyorum:

Bir özellik eklemek istediğinizde, yalnızca bir sınıf eklemeniz gerekir ve başka bir sınıfı (muhtemelen bir yapılandırma dosyasını) değiştirmeniz gerekmez.

Aşağıda kötü bir uygulama. Her oyun eklediğinizde GamePlayer sınıfını değiştirmeniz gerekir.

class GamePlayer
{
   public void PlayGame(string game)
   {
      switch(game)
      {
          case "Poker":
              PlayPoker();
              break;

          case "Gin": 
              PlayGin();
              break;

          ...
      }
   }

   ...
}

GamePlayer sınıfının asla değiştirilmesine gerek yoktur

class GamePlayer
{
    ...

    public void PlayGame(string game)
    {
        Game g = GameFactory.GetByName(game); 
        g.Play();   
    }

    ...
}

Şimdi GameFactory'imin OCP'ye de uyduğunu varsayarsak, başka bir oyun eklemek istediğimde, sadece sınıftan miras kalan yeni bir sınıf oluşturmam gerekecek Gameve her şey işe yarayacak.

Yıllar süren "uzantılar" dan sonra çoğu zaman ilk sınıflar gibi inşa edilir ve orijinal versiyondan hiçbir zaman doğru şekilde yeniden düzenlenmez (veya daha da kötüsü, birden fazla sınıfın ne olması gerektiği büyük bir sınıf olarak kalır).

Sağladığınız örnek OCP-ish. Benim görüşüme göre, fazla mesai oranı değişikliklerini işlemenin doğru yolu, verilerin yeniden işlenmesi için geçmiş oranların tutulduğu bir veritabanında olacaktır. Kod her zaman aramadan uygun değeri yükleyeceği için değişiklik için kapatılmalıdır.

Gerçek bir dünya örneği olarak, örneğimin bir varyantını kullandım ve Açık-Kapalı Prensibi gerçekten parlıyor. İşlevsellik eklemek gerçekten kolay çünkü sadece soyut bir temel sınıftan türetmek zorundayım ve "fabrikam" bunu otomatik olarak alıyor ve "oyuncu" fabrikanın hangi somut uygulamayı döndürdüğü umrunda değil.


1

Bu özel örnekte, "Büyü Değeri" olarak bilinen şeye sahipsiniz. Esasen zaman içinde değişebilen veya değişmeyen sabit kodlanmış bir değer. Genel olarak ifade ettiğiniz bilmeceye hitap etmeye çalışacağım, ancak bu, bir alt sınıf oluşturmanın bir sınıftaki bir değeri değiştirmekten daha fazla iş olduğu bir şey örneğidir.

Muhtemelen, sınıf hiyerarşinizde çok erken davranış belirttiniz.

Diyelim ki bizde PaycheckCalculator. Büyük OvertimeFactorolasılıkla çalışanla ilgili bilgilerin kilitlenmesi gerekir. Saatlik bir çalışan fazla mesai bonusu alabilirken, maaşlı bir çalışan herhangi bir ücret ödemeyecektir. Yine de, bazı maaşlı çalışanlar üzerinde çalıştıkları sözleşme nedeniyle doğrudan zaman alacaktır. Bilinen bazı ödeme senaryo sınıfları olduğuna karar verebilirsiniz ve mantığınızı bu şekilde oluşturacaksınız.

Temel PaycheckCalculatorsınıfta soyut yaparsınız ve beklediğiniz yöntemleri belirtirsiniz. Temel hesaplamalar aynıdır, sadece belirli faktörlerin farklı hesaplanmasıdır. Daha HourlyPaycheckCalculatorsonra getOvertimeFactoryöntemi uygularsınız ve durumunuza göre 1.5 veya 2.0 döndürürsünüz. Sizin StraightTimePaycheckCalculatoruygulayacağını getOvertimeFactor1,0 dönmek için. Son olarak, üçüncü bir NoOvertimePaycheckCalculatoruygulama, getOvertimeFactor0 değerini döndürecek bir uygulama olacaktır .

Anahtar, yalnızca genişletilmesi amaçlanan temel sınıftaki davranışı tanımlamaktır. Genel algoritmanın parçalarının veya belirli değerlerin ayrıntıları alt sınıflarla doldurulur. getOvertimeFactorSınıfı istediğiniz gibi genişletmek yerine, bir satıra hızlı ve kolay "düzeltme" getirme için varsayılan bir değer eklemiş olmanız. Ayrıca, sınıfların genişletilmesi ile ilgili çabaların olduğu gerçeğini vurgulamaktadır. Başvurunuzdaki sınıfların hiyerarşisini anlama çabası da vardır. Sınıflarınızı alt sınıf oluşturma ihtiyacını en aza indirecek, ancak ihtiyacınız olan esnekliği sağlayacak şekilde tasarlamak istiyorsunuz.

Düşünce için yiyecek: Sınıflarımız OvertimeFactorörneğinizdeki gibi belirli veri faktörlerini kapsadığında , bu bilgileri başka bir kaynaktan almak için bir yola ihtiyacınız olabilir. Örneğin, bir özellikler dosyası (Java'ya benzediğinden) veya bir veritabanı değeri tutar ve PaycheckCalculatordeğerlerinizi çekmek için bir veri erişim nesnesi kullanırsınız. Bu, doğru kişilerin kodun yeniden yazılmasına gerek kalmadan sistemin davranışını değiştirmesini sağlar.

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.