Argümanlarını doğrulayan bir yapıcı SRP'yi ihlal ediyor mu?


66

Tek Sorumluluk Prensibi'ne (SRP) mümkün olduğunca bağlı kalmaya çalışıyorum ve delegelere büyük ölçüde güvenerek belirli bir düzende (yöntemlerin SRP'si için) alıştım. Bu yaklaşımın sağlam olup olmadığını veya bununla ilgili ciddi sorunlar olup olmadığını bilmek istiyorum.

Örneğin, bir kurucu için girişi kontrol etmek için, aşağıdaki yöntemi tanıtabilirim ( Streamgiriş rastgele, herhangi bir şey olabilir)

private void CheckInput(Stream stream)
{
    if(stream == null)
    {
        throw new ArgumentNullException();
    }

    if(!stream.CanWrite)
    {
        throw new ArgumentException();
    }
}

Bu yöntem (tartışmalı) birden fazla şey yapar

  • Girişleri kontrol et
  • Farklı istisnalar at

SRP'ye bağlı kalmak için bu nedenle mantığı değiştirdim.

private void CheckInput(Stream stream, 
                        params (Predicate<Stream> predicate, Action action)[] inputCheckers)
{
    foreach(var inputChecker in inputCheckers)
    {
        if(inputChecker.predicate(stream))
        {
            inputChecker.action();
        }
    }
}

Sözde hangi bir şey yapar (öyle mi?): Girişi kontrol edin. Girdilerin gerçek kontrolü ve istisnaların atılması için aşağıdaki gibi yöntemler getirdim

bool StreamIsNull(Stream s)
{
    return s == null;
}

bool StreamIsReadonly(Stream s)
{
    return !s.CanWrite;
}

void Throw<TException>() where TException : Exception, new()
{
    throw new TException();
}

ve çağırabilir CheckInputgibi

CheckInput(stream,
    (this.StreamIsNull, this.Throw<ArgumentNullException>),
    (this.StreamIsReadonly, this.Throw<ArgumentException>))

Bu, ilk seçenekten daha iyi midir, yoksa gereksiz bir karmaşıklık getirebilir miyim? Eğer uygulanabilirse, bu kalıbı geliştirebilmemin bir yolu var mı?


26
Ben iddia olabilir CheckInputyine de birden fazla şeyler yapıyor: Hem dizisi üzerinde yineleme olduğunu ve bir yüklem işlevinin çağrılması ve bir eylem fonksiyonunu çağırarak. Öyleyse bu SRP'nin ihlali değil midir?
Bart van Ingen Schenau

8
Evet, yapmaya çalıştığım nokta buydu.
Bart van Ingen Schenau

135
bunun tek sorumluluk ilkesi olduğunu hatırlamak önemlidir ; tek eylem prensibi değil. Bir sorumluluğu var: Akışın tanımlanmış ve yazılabilir olduğunu doğrulayın.
David Arno

40
Bu yazılım ilkelerinin asıl amacının kodu daha okunaklı ve sürdürülebilir hale getirmek olduğunu unutmayın. Orijinal CheckInput'unuzun okunması ve bakımı, düzeltilmiş sürümünüzden çok daha kolaydır. Aslında, son CheckInput yönteminize bir kod tabanında rastladım, hepsini hurdaya çıkardım ve orijinal olarak sahip olduğunuza uyacak şekilde yeniden yazdım.
26

17
Bu “ilkeler” pratik olarak işe yaramaz çünkü orijinal fikriniz ne olursa olsun devam etmek istediğiniz şekilde “tek bir sorumluluğu” tanımlayabilirsiniz. Fakat katı bir şekilde bunları uygulamaya çalışırsanız, açıkçası, anlaşılması zor olan bu tür bir kod ile bitirdiniz sanırım.
Casey,

Yanıtlar:


151

SRP belki de en yanlış anlaşılan yazılım prensibidir.

Bir yazılım uygulaması, inşa edilmiş modüllerden yapılmış modüller ...

Alt kısımda, CheckInputsadece çok küçük bir mantık içerecek gibi tek bir fonksiyon vardır , ancak yukarı doğru ilerledikçe, art arda gelen her modül daha fazla mantığı içine alır ve bu normaldir .

SRP, tek bir atomik eylem yapmakla ilgili değildir . Tek bir sorumluluğa sahip olmakla ilgili, bu sorumluluk birden çok eylem gerektirse bile ... ve sonuçta bakım ve test edilebilirlikle ilgili :

  • kapsüllemeyi teşvik eder (Tanrı Nesneleri'nden kaçınır),
  • kaygıların ayrılmasını teşvik eder (tüm kod tabanındaki dalgaların değişmesinden kaçınılması),
  • Sorumlulukların kapsamını daraltarak test edilebilirliğe yardımcı olur.

Aslında CheckInputiki çeki ile uygulanan ve iki farklı özel durumlar oluşturmak olduğunu ilgisiz bir ölçüde.

CheckInputdar bir sorumluluğu vardır: girdinin gereksinimlere uymasını sağlamak. Evet, birden fazla gereksinim var, ancak bu birden fazla sorumluluk olduğu anlamına gelmez. Evet, çekleri bölebilirsiniz ama bu nasıl yardımcı olabilir? Bir noktada çekler bir şekilde listelenmelidir.

Hadi karşılaştıralım:

Constructor(Stream stream) {
    CheckInput(stream);
    // ...
}

e karşı:

Constructor(Stream stream) {
    CheckInput(stream,
        (this.StreamIsNull, this.Throw<ArgumentNullException>),
        (this.StreamIsReadonly, this.Throw<ArgumentException>));
    // ...
}

Şimdi, CheckInputdaha az yapar ... ama arayan daha fazlasını yapar!

Gereksinimler listesini kapsüldükleri CheckInputyerlerden göründükleri yerlere kaydırdınız Constructor.

İyi bir değişiklik mi? Değişir:

  • Eğer CheckInputsadece orada denir: o kodu Clutterlar diğer taraftan, bu şartlar görünür kılan bir taraftan tartışılır;
  • Eğer CheckInputbirden çok kez denir aynı şartlara , o zaman KURU ihlal ve bir kapsülleme sorunu var.

Tek bir sorumluluğun çok fazla iş gerektirebileceğini anlamak önemlidir . Kendi kendine süren bir arabanın "beyni" nin tek bir sorumluluğu var:

Arabayı gideceği yere sürmek.

Bu tek bir sorumluluktur, ancak bir sürü sensör ve aktörün koordinasyonunu gerektirir, çok fazla karar alır ve hatta muhtemelen çelişen gereksinimleri de vardır 1 ...

... ancak, hepsi kapsüllenmiş. Yani müşteri umursamıyor.

1 yolcuların güvenliği, başkalarının güvenliği, yönetmeliklere saygı, ...


2
Bence "kapsülleme" kelimesini kullanma şekliniz ve türevleri kafa karıştırıcı. Bunun dışında büyük cevap!
Fabio Turati

4
Cevabınıza katılıyorum, ancak kendi kendini süren araba beyin argümanı insanları SRP'yi kırmaya teşvik ediyor. Söylediğin gibi, modüllerden yapılmış modüller. Tüm sistemin amacını tanımlayabilirsiniz, ancak bu sistemin kendinden ayrılması gerekir. Hemen hemen her sorunu çözebilirsiniz.
Sava B.

13
@SavaB .: Tabii, ancak prensibi aynı kalır. Bir modül, bileşenlerinden daha geniş kapsamlı olmasına rağmen, tek bir sorumluluğa sahip olmalıdır.
Matthieu M.

3
@ user949300 Tamam, peki ya sadece "sürüş". Gerçekten, "araba kullanmak" sorumluluktur ve "güvenli bir şekilde" ve "yasal olarak" bu sorumluluğu nasıl yerine getirdiğiyle ilgili şartlardır. Ve genellikle bir sorumluluk belirlerken gereksinimleri listeleriz.
Brian McCutchon

1
"SRP, belki de en yanlış anlaşılan yazılım ilkesidir." Bu cevabın kanıtladığı gibi :)
Michael

41

Bob Amca’nın SRP’den ( https://8thlight.com/blog/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html ) Gönderilmesi :

Tek Sorumluluk İlkesi (SRP), her yazılım modülünün değişmesi için tek bir nedene sahip olması gerektiğini belirtir.

... Bu ilke insanlarla ilgilidir.

... Bir yazılım modülü yazdığınızda, değişiklik talep edildiğinde, bu değişikliklerin yalnızca tek bir kişiden, daha doğrusu tek bir dar tanımlanmış iş işlevini temsil eden sıkı bir şekilde birleştirilmiş bir grup insandan kaynaklanabileceğinden emin olmak istersiniz.

... Bu, JSP'lere SQL koymamamızın nedenidir. Bu, sonuçları hesaplayan modüllerde HTML üretmememizin nedenidir. İş kurallarının veritabanı şemasını bilmemesinin nedeni budur. Endişeleri ayırmamızın nedeni budur.

Yazılım modüllerinin belirli paydaş endişelerini ele alması gerektiğini açıklıyor. Bu nedenle, sorunuza cevap verin:

Bu, ilk seçenekten daha iyi midir, yoksa gereksiz bir karmaşıklık getirebilir miyim? Eğer uygulanabilirse, bu kalıbı geliştirebilmemin bir yolu var mı?

IMO, sadece bir yönteme bakıyorsun, daha yüksek bir seviyeye bakmalısın (bu durumda sınıf seviyesi). Belki de sınıfınızın şu anda ne yaptığını bir göz atmalıyız (ve bu senaryo hakkında daha fazla açıklama gerektirir). Şimdilik, sınıfınız hala aynı şeyi yapıyor. Örneğin, yarın bazı onaylama konusunda bazı değişiklik talepleri varsa (örneğin: "şimdi akış boş olabilir"), o zaman hala bu sınıfa gitmeniz ve içindeki öğeleri değiştirmeniz gerekir.


4
En iyi cevap. Bekçi kontroller iki farklı paydaşlar / bölümlerinden geliyorsa, OP ile ilgili ayrıntılı için, daha sonra checkInputs()bölünmüş olmalıdır, içine söylemek checkMarketingInputs()ve checkRegulatoryInputs(). Aksi takdirde, hepsini tek bir yöntemde birleştirmek iyidir.
user949300, 19:17

36

Hayır, bu değişiklik SRP tarafından bildirilmez.

Kendinize neden "iletilen nesnenin bir yayın olduğunu" kontrol etmiyorsanız kendinize sorun . Cevap açıktır: Dil, arayan kişinin yayın akışında olmayan bir programı derlemesini önler .

C # tip sistemi ihtiyaçlarınızı karşılamak için yeterli değil; Çekleriniz bugün tip sistemde ifade edilemeyecek değişmezlerin uygulanmasını uyguluyor . Yöntemin geri çevrilemez, yazılabilir bir akış aldığını söylemenin bir yolu olsaydı, bunu yazdın, ama olmadı, o yüzden bir sonraki en iyi şeyi yaptın: çalışma zamanında tip kısıtlamasını zorladın. Umarım bunu da belgelendirmişsinizdir, böylelikle yönteminizi kullanan geliştiricilerin ihlal etmesi, test durumlarında başarısız olması ve ardından sorunu çözmesi gerekmez.

Bir yönteme tür koymak Tek Sorumluluk İlkesinin ihlali değildir; yöntem ön koşullarını uygulayan ya da ön koşullarını ortaya koyan yöntem değildir.


1
Ayrıca, oluşturulan nesneyi geçerli bir durumda bırakmak, bir kurucunun temelde her zaman sahip olduğu sorumluluktur. Bahsettiğiniz gibi, çalışma zamanının ve / veya derleyicinin sağlayamadığı ek kontroller gerektiriyorsa, bunun gerçekten bir yolu yoktur.
SBI

23

Tüm sorumluluklar eşit yaratılmamıştır.

görüntü tanımını buraya girin

görüntü tanımını buraya girin

İşte iki çekmece. İkisinin de tek sorumluluğu var. Her birinin, onlara neyin ait olduğunu bilmenizi sağlayan isimleri var. Birincisi gümüş çekmecesi. Diğeri hurda çekmecesi.

Öyleyse fark nedir? Gümüş eşya çekmecesi, neyin ait olmadığını açıkça ortaya koyuyor. Önemsiz çekmece ancak sığacak olan her şeyi kabul eder. Kaşıkları gümüş çekmeceden çıkarmak çok yanlış görünüyor. Yine de önemsiz çekmeceden çıkarılırsa kaçırılacak bir şey düşünmeye zorlanıyorum. Gerçek şu ki, herhangi bir şeyin tek bir sorumluluğu olduğunu iddia edebilirsiniz, ancak hangisinin daha odaklı tek bir sorumluluğu olduğunu düşünüyorsunuz?

Tek sorumluluğu olan bir nesne, burada sadece bir şeyin olabileceği anlamına gelmez. Sorumluluklar iç içe geçebilir. Fakat bu yuvalama sorumlulukları anlam ifade etmeli, onları burada bulduğunda sizi şaşırtmamalı ve gitmişse onları özlemelisiniz.

Yani teklif ettiğin zaman

CheckInput(Stream stream);

Hem girişi kontrol etmekten hem de istisnalar atmaktan endişelenmiyorum. Hem girişi kontrol edip hem de girişi kaydedebiliyor olmasından endişe duyarım. Bu iğrenç bir sürpriz. Biri gitmiş olsaydı özlemem.


21

Kendinizi budaklara bağladığınızda ve Önemli Yazılım İlkesine uymak için garip kodlar yazdığınızda, genellikle prensibi yanlış anlarsınız (bazen prensip yanlış olsa da). Matthieu'nin mükemmel cevabının işaret ettiği gibi, SRP'nin bütün anlamı "sorumluluk" tanımına bağlıdır.

Deneyimli programcılar bu prensipleri görür ve onları batırdığımız kodun anılarıyla ilişkilendirir; daha az deneyimli programcılar onları görür ve onlarla hiçbir ilgisi yoktur. Uzayda yüzen bir soyutlama, hepsi sırıtıyor ve kedisi yok. Bu yüzden tahmin ediyorlar ve genellikle çok kötüye gidiyor. Programlama at duygusu geliştirmeden önce, garip aşırı karmaşık kod ve normal kod arasındaki fark açık değildir.

Bu kişisel sonuçlardan bağımsız olarak uymanız gereken dini bir emir değildir. Programlama atı anlamında bir öğeyi resmileştirmek ve kodunuzu olabildiğince basit ve açık tutmanıza yardımcı olmak için kullanılan bir kuraldır. Tam tersi bir etkiye sahipse, bazı dış girişleri aramaya haklısınız.

Programlamada, bir tanımlayıcının anlamını yalnızca ilkelere bakarak ilk prensiplerden çıkarmaya çalışmaktan çok daha yanlış gidemezsiniz ve bu tanımlayıcılar için gerçek koddaki tanımlayıcılar kadar programlama hakkında yazarken de geçerlidir .


14

CheckInput rolü

Birincisi, ben, orada bariz koyalım CheckInput edilir çeşitli yönlerini kontrol ediyor olsa bile bir şey yapıyor. Sonunda girişi kontrol eder . Denilen yöntemlerle ilgileniyorsanız, bunun tek bir şey olmadığı iddia edilebilir DoSomething, ancak girdi kontrolünün çok belirsiz olmadığını varsaymanın güvenli olduğunu düşünüyorum.

Tahminler için bu kalıbı eklemek, girişi kontrol etmek için mantığın sınıfınıza yerleştirilmesini istemiyorsanız faydalı olabilir, ancak bu kalıbı başarmaya çalıştığınız şey için oldukça ayrıntılı görünüyor. Elde etmek istediğiniz şey buysa, IStreamValidatortek bir yöntemle basitçe bir arayüz iletmek çok daha doğrudan olabilir isValid(Stream). Uygulayan herhangi bir sınıf IStreamValidatorgibi yüklemler kullanabilir StreamIsNullveya StreamIsReadonlyisterlerse ama geri santral Sadede, tek bir sorumluluk ilkesini koruyarak çıkarları yapmak için oldukça saçma bir değişimdir.

Aklı kontrol

Benim fikrim, en azından boş ve yazılabilir olmayan bir Akış ile uğraşmanızı sağlamak için bir "aklı kontrolüne izin vermemiz" ve bu temel kontrolün bir şekilde sınıfınızı akışların doğrulayıcısı yapması değil . Dikkat edin, daha sofistike kontroller sınıfınızın dışında kalsa iyi olur, ama çizginin çizildiği yer burasıdır. Akışınızın durumunu okumaya başlayarak veya kaynakları doğrulamaya adayarak başlamaya ihtiyaç duyduğunuzda, akışınızın resmi bir doğrulamasını gerçekleştirmeye başladınız ve bu kendi sınıfına girmesi gereken şey.

Sonuç

Düşüncelerim, eğer sınıfınızın bir yönünü daha iyi organize etmek için bir kalıp uyguluyorsanız, kendi sınıfında olmaya değer. Bir kalıp uymadığından, ilk önce kendi sınıfına ait olup olmadığını da sorgulamanız gerekir. Düşüncelerim şu ki, akışın geçerliliğinin ileride değişebileceğine inanmıyorsanız ve özellikle bu geçerliliğin doğada dinamik olabileceğine inanıyorsanız, o zaman tarif ettiğiniz model iyi bir fikir olabilir. başlangıçta önemsiz olmak. Aksi takdirde, programınızı keyfi olarak daha karmaşık hale getirmeye gerek yoktur. Bir kürek olarak kürek diyelim. Doğrulama bir şeydir, ancak boş girdiyi denetlemek doğrulama değildir ve bu nedenle tek sorumluluk ilkesini ihlal etmeden onu sınıfınızda tutabileceğinizi düşünüyorum.


4

İlke, empatik olarak bir kod parçasının "sadece tek bir şey yapması" gerektiğini belirtmez.

SRP'deki "Sorumluluk" gereksinimler düzeyinde anlaşılmalıdır. Kodun sorumluluğu iş gereksinimlerini karşılamaktır. Bir nesne birden fazla bağımsız iş gereksinimini karşılarsa, SRP ihlal edilir . Bağımsız olarak, bir şartın yerine geçebileceği, bir şartın değişebileceği anlamına gelir.

Yeni bir işletme gereksiniminin ortaya konması düşünülebilir, bu da belirli bir nesnenin okunabilirliği kontrol etmemesi gerektiği anlamına gelirken, başka bir işletme gereksinimi yine de okunabilir olup olmadığını kontrol etmeyi gerektirir mi? Hayır, çünkü işletme gereksinimleri bu seviyede uygulama ayrıntılarını belirtmemektedir.

Gerçek bir SRP ihlali örneği şunun gibi bir kod olacaktır:

var message = "Your package will arrive before " + DateTime.Now.AddDays(14);

Bu kod çok basittir, ancak yine de, işin farklı bölümleri tarafından kararlaştırıldığından, metnin beklenen teslim tarihinden bağımsız olarak değişeceği düşünülebilir.


Hemen hemen her gereksinim için farklı bir sınıf kutsal olmayan bir kabusa benziyor.
whatsisname,

@ whatsname: O zaman belki de SRP sizin için değil. Her çeşit ve büyüklükteki proje için tasarım ilkesi uygulanmaz. (Ancak yalnızca bağımsız gereksinimlerden bahsettiğimizi unutmayın (yani bağımsız olarak değişebilir), yalnızca o zamandan beri herhangi bir gereksinim değil, sadece ne kadar ince taneli olduklarına bağlı olacaktır.)
JacquesB 23:17

Bence, SRP, tek bir akılda kalıcı ifadeyle tanımlanması zor olan durumsal bir yargılama elemanı gerektirdiğini düşünüyor.
whatsisname,

@whatsisname: Tamamen katılıyorum.
JacquesB

Bir nesne birden fazla bağımsız iş gereksinimi karşılarsa, SRP
geçmesi

3

@ EricLippert'in cevabındaki noktayı beğendim :

Kendinize, denetleyicinizde neden iletilen nesnenin bir akış olduğunu kontrol etmediğini sorun . Cevap açıktır: Dil, arayan kişinin yayın akışında olmayan bir programı derlemesini önler .

C # tip sistemi ihtiyaçlarınızı karşılamak için yeterli değil; Çekleriniz bugün tip sistemde ifade edilemeyecek değişmezlerin uygulanmasını uyguluyor . Yöntemin geri çevrilemez, yazılabilir bir akış aldığını söylemenin bir yolu olsaydı, bunu yazdın, ama olmadı, o yüzden bir sonraki en iyi şeyi yaptın: çalışma zamanında tip kısıtlamasını zorladın. Umarım bunu da belgelendirmişsinizdir, böylelikle yönteminizi kullanan geliştiricilerin ihlal etmesi, test durumlarında başarısız olması ve ardından sorunu çözmesi gerekmez.

EricLippert, bu tip sistem için bir sorun olduğunu haklı. Tek sorumluluk ilkesini (SRP) kullanmak istediğiniz için, temelde bu işten sorumlu tip sisteme ihtiyacınız vardır.

Bunu C # ile yapmak istemek mümkün. Hazırlananları nullderleme zamanında yakalayabiliriz, sonra nullçalışma anında hazır olmayanları yakalayabiliriz . Bu, bir derleme zamanı kontrolü kadar iyi değildir, ancak derleme zamanında yakalanmamaktan dolayı katı bir gelişmedir.

Peki, C # nasıl biliyor Nullable<T>? Bunu tersine çevirelim ve yapalım NonNullable<T>:

public struct NonNullable<T> where T : class
{
    public T Value { get; private set; }
    public NonNullable(T value)
    {
        if (value == null) { throw new NullArgumentException(); }
        this.Value = value;
    }
    //  Ease-of-use:
    public static implicit operator T(NonNullable<T> value) { return value.Value; }
    public static implicit operator NonNullable<T>(T value) { return new NonNullable<T>(value); }

    //  Hack-ish overloads that prevent null-literals from being implicitly converted into NonNullable<T>'s.
    public static implicit operator NonNullable<T>(Tuple<T> value) { return new NonNullable<T>(value.Item1); }
    public static implicit operator NonNullable<T>(Tuple<T, T> value) { return new NonNullable<T>(value.Item1); }
}

Şimdi yazmak yerine

public void Foo(Stream stream)
{
  if (stream == null) { throw new NullArgumentException(); }

  // ...method code...
}

, sadece yaz:

public void Foo(NonNullable<Stream> stream)
{
  // ...method code...
}

O zaman üç kullanım durumu var:

  1. Foo()Null olmayan kullanıcı çağrıları Stream:

    Stream stream = new Stream();
    Foo(stream);
    

    Bu istenen kullanım durumudur ve -le-olmadan çalışmaktadır NonNullable<>.

  2. Kullanıcı Foo()null ile çağrılar Stream:

    Stream stream = null;
    Foo(stream);
    

    Bu çağıran bir hatadır. İşte NonNullable<>, kullanıcıya bunu yapmaması gerektiği konusunda bilgilendirmeye yardımcı oluyor, ancak aslında onları durdurmuyor. Her iki durumda da, bu bir çalışma zamanı ile sonuçlanır NullArgumentException.

  3. Kullanıcı aramaları Foo()ile null:

    Foo(null);

    nulldolaylı olarak a'ya dönüştürmez NonNullable<>, bu nedenle kullanıcı IDE'de çalışma zamanından önce bir hata alır . Bu, SRP'nin önerdiği gibi boş denetlemeyi tip sistemine devrediyor.

Argümanlarınız hakkında başka şeyler de iddia etmek için bu yöntemi genişletebilirsiniz. Eğer yazılabilir akışı istiyorum çünkü Örneğin, bir tanımlayabilirsiniz struct WriteableStream<T> where T:Streamikisi için kontrol ettiğinden nullve stream.CanWriteyapıcı içinde. Bu hala bir çalışma zamanı türü kontrolü olabilir, ancak:

  1. WriteableStreamArayanlara olan ihtiyacı bildirerek türü niteleyici ile dekore eder .

  2. Kontrol kodu tek bir yerde yapar, böylece kontrolü ve throw InvalidArgumentExceptionher seferinde tekrar etmenize gerek yoktur .

  3. Tip kontrol görevlerini tip sistemine iterek SRP'ye daha iyi uyum sağlar (jenerik dekoratörler tarafından uzatıldığı gibi).


3

Yaklaşımınız şu anda prosedürlü. StreamNesneyi parçalara ayırıyor ve dışarıdan doğrulıyorsunuz. Bunu yapma - bu kapsüllemeyi kırar. StreamKendi geçerliliğinden sorumlu olalım . Uygulamak için bazı sınıflarımız oluncaya kadar SRP'yi uygulayamayız.

İşte Streamdoğrulama işlemini geçerse eylem gerçekleştirir:

class Stream
{
    public void someAction()
    {
        if(!stream.canWrite)
        {
            throw new ArgumentException();
        }

        System.out.println("My action");
    }
}

Ama şimdi SRP'yi ihlal ediyoruz! "Bir sınıfın değişmesi için tek bir nedeni olmalı." 1) doğrulama ve 2) gerçek mantığın bir karışımını elde ettik. Değişmesi gerekebilecek iki nedenimiz var.

Bunu onaylayıcı dekoratörler ile çözebiliriz . Öncelikle, Streambir arayüze dönüştürmemiz ve bunu somut bir sınıf olarak uygulamamız gerekiyor.

interface Stream
{
    void someAction();
}

class DefaultStream implements Stream
{
    @Override
    public void someAction()
    {
        System.out.println("My action");
    }
}

Şimdi a'yı saran Stream, doğrulama yapan ve Streameylemin fiili mantığı için verilenleri savunan bir dekoratör yazabiliriz .

class WritableStream implements Stream
{
    private final Stream stream;

    public WritableStream(final Stream stream)
    {
        this.stream = stream;
    }

    @Override
    public void someAction()
    {
        if(!stream.canWrite)
        {
            throw new ArgumentException();
        }
        stream.someAction();
    }
}

Şimdi bunları istediğimiz şekilde oluşturabiliriz:

final Stream myStream = new WritableStream(
    new DefaultStream()
);

Ek doğrulama ister misiniz? Başka bir dekoratör ekleyin.


1

Bir sınıfın işi, bir sözleşmeyi karşılayan bir hizmet sağlamaktır . Bir sınıfın daima bir sözleşmesi vardır: onu kullanmak için bir takım şartlar vardır ve şartların yerine getirilmesi şartıyla durumu ve çıktılarını ortaya koyacağına söz verir. Bu sözleşme, belgeler ve / veya iddialar yoluyla açık veya dolaylı olabilir ancak her zaman mevcuttur.

Sınıfınızın sözleşmesinin bir kısmı, arayan kişinin yapıcıya boş olmaması gereken bazı argümanları vermesidir. Sözleşmeyi uygulamak , sınıfın sorumluluğundadır, bu nedenle arayanın sözleşmenin bir bölümünü yerine getirip getirmediğini kontrol etmek, kolaylıkla sınıfın sorumluluğu kapsamında sayılabilir.

Bir sınıfın bir sözleşme uyguladığı fikri , Eyfel programlama dilinin tasarımcısı Bertrand Meyer ve sözleşmeye göre tasarım fikridir . Eyfel dili, sözleşmenin şartlarını ve kontrolünü dilin bir parçası yapar.


0

Diğer cevaplarda da belirtildiği gibi, SRP sıklıkla yanlış anlaşılmaktadır. Sadece bir fonksiyon yapan atom koduna sahip olmakla ilgili değil. Bu, nesnelerinizin ve metotlarınızın sadece bir şeyi yaptığından ve bir şeyin sadece bir yerde yapıldığından emin olmakla ilgilidir.

Sözde koddaki kötü bir örneğe bakalım.

class Math
    private int a;
    private int b;
    def constructor(int x, int y) 
        if(x != null)
          a = x
        else if(x < 0)
          a = abs(x)
        else if (x == -1)
          throw "Some Silly Error"
        else
          a = 0
        end
        if(y != null)
           b = y
        else if(y < 0)
           b = abs(y)
        else if(y == -1)
           throw "Some Silly Error"
        else
         b = 0
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

Bizim oldukça saçma örneğimizde, Math # yapıcısının "sorumluluğu" matematik nesnesini kullanılabilir hale getirmektir. Bunu ilk önce girişi sterilize ederek, ardından değerlerin -1 olmadığından emin olarak yapar.

Bu, geçerli SRP'dir, çünkü yapıcı yalnızca bir şey yapıyor. Math nesnesini hazırlıyor. Ancak çok sürdürülemez. DRY'yi ihlal ediyor.

O zaman başka bir paso alalım

class Math
    private int a;
    private int b;
    def constructor(int x, int y)
        cleanX(x)
        cleanY(y)
    end
    def cleanX(int x)
        if(x != null)
          a = x
        else if(x < 0)
          a = abs(x)
        else if (x == -1)
          throw "Some Silly Error"
        else
          a = 0
        end
   end
   def cleanY(int y)
        if(y != null)
           b = y
        else if(y < 0)
           b = abs(y)
        else if(y == -1)
           throw "Some Silly Error"
        else
         b = 0
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

Bu geçişte DRY konusunda biraz daha iyiydik ama yine de DRY ile devam etmenin bir yolunu bulduk. Öte yandan SRP biraz uzakta görünüyor. Şimdi aynı işte iki işleve sahibiz. Hem cleanX hem de cleanY girişi temizler.

Başka bir yol verelim

class Math
    private int a;
    private int b;
    def constructor(int x, int y)
        a = clean(x)
        b = clean(y)
    end
    def clean(int i)
        if(i != null)
          return i
        else if(i < 0)
          return abs(i)
        else if (i == -1)
          throw "Some Silly Error"
        else
          return 0
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

Şimdi nihayet DRY konusunda daha iyiydi ve SRP de aynı fikirde. "Temizlik" işini yapan tek bir yerimiz var.

Kod teoride daha sürdürülebilir ve daha iyidir, ancak hatayı düzeltip kodu sıktığımızda, sadece bir yerde yapmamız gerekir.

class Math
    private int a;
    private int b;
    def constructor(int x, int y)
        a = clean(x)
        b = clean(y)
    end
    def clean(int i)
        if(i == null)
          return 0
        else if (i == -1)
          throw "Some Silly Error"
        else
          return abs(i)
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

Gerçek dünyadaki çoğu durumda, nesneler daha karmaşık olacak ve SRP bir dizi nesneye uygulanacaktır. Örneğin yaş Baba, Anne, Oğul, Kız'a ait olabilir, bu nedenle doğum tarihinden itibaren yaşı belirleyen 4 sınıf yerine, bunu yapan bir Kişi sınıfınız var ve 4 sınıf da bundan miras kaldı. Ama umarım bu örnek açıklamaya yardımcı olur. SRP, atomik eylemlerle ilgili değil, “yapılması gereken” bir işle ilgilidir.


-3

SRP’nin konuşan Bob Amca, her yere serpilmiş boş çeklerden hoşlanmıyor. Genel olarak, bir ekip olarak, yapıcılara mümkün olduğunda boş parametreler kullanmaktan kaçınmalısınız. Kodunuzu ekibinizin dışında yayınladığınızda işler değişebilir.

Yapıcı parametrelerinin geçersiz kılınmamasının, önce söz konusu sınıfın tutarlılığını sağlamadan önce arama kodunda, özellikle de testlerde şişkinlikle sonuçlanmasını sağlama.

Bu tür sözleşmeleri gerçekten uygulamak istiyorsanız, Debug.Assertkarmaşayı azaltmak için kullanmayı ya da benzeri bir şey kullanmayı düşünün :

public AClassThatDefinitelyNeedsAWritableStream(Stream stream)
{
   Assert.That(stream.CanWrite, "Put crucial information here, and not inane bloat.");

   // Go on normal operation.
}
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.