Bir sınıf, tek sorumluluk ilkesini ihlal etmeden nasıl birden fazla yönteme sahip olabilir?


64

Tek sorumluluk ilkesi wikipedia'da şöyle tanımlanır:

Tek sorumluluk ilkesi, her modül, sınıf veya işlevin yazılım tarafından sağlanan işlevselliğin tek bir parçası üzerinde sorumluluğa sahip olması gerektiğini ve bu sorumluluğun tamamen sınıf tarafından kapsanması gerektiğini belirten bir bilgisayar programlama ilkesidir.

Bir sınıf sadece tek bir sorumluluğa sahipse, nasıl birden fazla metoda sahip olabilir? Her yöntemin farklı bir sorumluluğu olmaz mıydı, o zaman sınıfın 1'den fazla sorumluluğu olduğu anlamına gelirdi.

Tek sorumluluk ilkesini gösterirken gördüğüm her örnek, yalnızca bir yöntemi olan örnek bir sınıf kullanır. Bir örneği görmeye veya bir sorumluluğu üstlenebilecek birden fazla yöntemle bir sınıfın açıklamasına yardımcı olabilir.


11
Neden bir aşağı oy? SE.SE için ideal bir soru gibi görünüyor; kişi konuyu araştırdı ve soruyu netleştirmeye çalıştı. Bunun yerine upvotes hak ediyor.
Arseni Mourzenko

19
Zayıf nokta muhtemelen bunun birkaç kez sorulmuş ve cevaplanmış bir soru olmasından kaynaklanıyordu, örneğin bkz. Softwareengineering.stackexchange.com/questions/345018/… . Benim düşünceme göre, önemli yeni yönler eklemiyor.
Hans-Martin Mosner


9
Bu sadece indirim ve saçma. Eğer her sınıf anlamıyla tek yöntem izin verildi, daha sonra tam anlamıyla herhangi bir program her zamankinden fazla bir şey yapmak mümkün olacak hiçbir yolu yoktur.
Darrel Hoffman,

6
@DarrelHoffman Bu doğru değil. Her sınıf sadece bir "call ()" metoduna sahip bir functor ise, temelde nesne yönelimli programlama ile basit prosedürel programlama taklit etmiş olursunuz. Bir sınıfın "call ()" yöntemi birçok diğer sınıfın "call ()" yöntemini çağırabildiğinden, başka bir şekilde yapabileceğiniz her şeyi yapabilirsiniz.
Vaelus

Yanıtlar:


29

Tek sorumluluk, tek bir işlevin yerine getirebileceği bir şey olmayabilir.

 class Location { 
     public int getX() { 
         return x;
     } 
     public int getY() { 
         return y; 
     } 
 }

Bu sınıf tek sorumluluk ilkesini bozabilir. İki işlevi olduğu için değil, kodun değişmesi gerekebilecek farklı paydaşları memnun etmek getX()ve getY()yerine getirmek zorunda kalması durumunda. Eğer Başkan Yardımcısı X, tüm sayıların kayan nokta sayısı olarak ifade edileceği bir not gönderirse ve Muhasebe Direktörü Bayan Y, X'in iyi bir şekilde düşündüğüne bakılmaksızın, departman incelemelerinin tüm sayılarının tamsayı olarak kalması konusunda ısrar ediyor. Kimin sorumlusu olduğuna dair tek bir fikir, çünkü işler karışmak üzere.

SRP izlenmiş olsaydı, Location sınıfı, X ve grubunun maruz kaldığı şeylere katkıda bulunup bulunmadığı açık olurdu. Sınıfın sorumlu olduğunu açıkça belirtin ve hangi direktifin bu sınıfı etkilediğini biliyorsunuz. Her ikisi de bu sınıfı etkilerse, değişimin etkisini en aza indirecek şekilde zayıf bir şekilde tasarlanmıştır. “Bir sınıfın değişmesi için tek bir sebep olmalı”, tüm sınıfın sadece küçük bir şey yapabileceği anlamına gelmez. Bu, sınıfa bakıp hem Bay X'in hem de Bayan Y'nin bu sınıfa ilgisinin olduğunu söylememem gerektiği anlamına geliyor.

Bunun gibi şeyler dışında. Hayır, çoklu yöntemler iyi. Sadece sınıfa hangi yöntemlerin ait olduğunu ve hangilerinin olmadığını belirten bir isim verin.

Bob Amca'nın SRP'si Conly Yasası hakkında Curly Yasası'ndan daha fazla . Bob Amca, Curly Yasası'nın (bir şey yapmayı) sınıfları değil fonksiyonlara uygulanmasını savunuyor. SRP, karışım nedenlerinin birlikte değişmemesine dikkat eder. Conway Yasası, sistemin bir organizasyonun bilgilerinin nasıl aktığını takip edeceğini söylüyor. Bu, SRP'nin izlenmesine yol açar çünkü asla duymadığınız şeyleri umursamıyorsunuz.

"Bir modül bir oyuncuya ve bir oyuncuya karşı sorumlu olmalıdır"

Robert C Martin - Temiz Mimari

İnsanlar, SRP'nin kapsamı sınırlandırmak için her sebeple ilgili olmasını istemeye devam ediyor. Kapsamı sınırlamak için SRP'den daha fazla neden var. Sınıfın, içeriye bakmanın sizi şaşırtmayacağına dair bir isim alabilecek bir soyutlama olduğunu vurgulayarak kapsamı daha da sınırlandırıyorum .

Kıvırcık Yasasını derslere uygulayabilirsiniz. Bob Amca'nın bahsettiği şeyin dışındasın ama yapabilirsin. Yanlış gittiğin şey, bunun bir işlev anlamına geldiğini düşünmeye başladığın zamandır. Bu bir ailenin sadece bir çocuğu olması gerektiğini düşünmek gibidir. Birden fazla çocuğa sahip olmak, onun aile olmasını engellemez.

Curly yasasını bir sınıfa uygularsanız, sınıftaki her şey tek bir birleştirici fikir hakkında olmalıdır. Bu fikir geniş olabilir. Fikir sebat olabilir. Bazı kayıt yardımcı programı işlevleri oradaysa, o zaman açıkça kullanım dışıdır. Bu kodla ilgilenen tek kişinin Bay X olması önemli değil.

Burada uygulanacak klasik ilke , Endişelerin Ayrılması olarak adlandırılmaktadır . Tüm endişelerinizi ayırırsanız, herhangi bir yerde kalanın tek bir endişe olduğu söylenebilir. 1991 fikri City Slickers'ın bizi Curly karakteri ile tanıştırmasından önce bu fikri biz buna adlandırdık.

Bu iyi. Sadece Bob Amca'nın sorumluluk dediği şey bir endişe değil. Ona karşı sorumluluk, odaklanacağın bir şey değil. Sizi değişmeye zorlayabilecek bir şey. Tek bir endişeye odaklanabilir ve yine de farklı gündemleri olan farklı insan gruplarından sorumlu bir kod oluşturabilirsiniz.

Belki de umrunda değil. İnce. "Bir şeyi yapmayı" tutmanın tüm tasarım sorunlarınızı çözeceğini düşünmek, "bir şeyin" ne olabileceği konusunda hayal gücü eksikliği olduğunu gösterir. Kapsamı sınırlamanın başka bir nedeni de organizasyondur. Her şeyle dolu bir önemsiz çekmeceye sahip olana kadar birçok "bir şeyi" diğer "bir şeyin" içine yerleştirebilirsiniz. Bunun hakkında daha önce konuştum

Elbette, kapsamı sınırlandırmanın klasik OOP nedeni sınıfın içinde özel alanlara sahip olması ve daha sonra bu verileri paylaşmak için alıcılar kullanması, bu verilere ihtiyaç duyan her yöntemi özel olarak verileri kullanabilecekleri sınıfta koyuyoruz. Birçoğu bunu kapsam sınırlayıcı olarak kullanmak için çok kısıtlayıcı buluyor, çünkü birbirine ait her yöntem aynı alanları kullanmıyor. Verileri bir araya getiren herhangi bir fikrin, yöntemleri bir araya getiren aynı fikir olmasını sağlamayı seviyorum.

Bu bakmak için işlevsel bir şekilde olmasıdır a.f(x)ve a.g(x)f basitçe bir g (x) ve bir (X). İki işlev değil, birlikte değişen işlev çiftleri sürekliliği. aBile içinde verilere sahip olmak zorunda değildir. Kullanacağınız hangi uygulamayı fve guygulamayı nasıl bildiğinizle ilgili olabilir . Birlikte değişen fonksiyonlar birbirine aittir. Bu eski güzel polimorfizm.

SRP, kapsamı sınırlandırmanın birçok nedeninden yalnızca biri. İyi bir tane. Ama tek değil.


25
Bence bu cevap SRP'yi anlamaya çalışan biri için kafa karıştırıcı. Sayın Başkan ve Sayın Direktör arasındaki savaş, teknik yollarla çözülmez ve bir mühendislik kararını haklı çıkarmak için kullanılması saçmadır. Conway yasası iş başında.
whatsisname

8
@whatsisname Aksine. SRP açıkça paydaşlara başvuru amaçlıydı. It has bir şey teknik tasarım ile ilgisi yok. Bu yaklaşıma katılmıyor olabilirsiniz, ancak SRP aslında Bob Amca tarafından tanımlanmıştı ve bunu tekrar tekrar yapmak zorunda kaldı çünkü bazı nedenlerden dolayı insanlar bu basit kavramı anlayamıyor gibi görünüyorlar (zihin, ister Bu gerçekten yararlıdır tamamen ortogonal bir sorudur).
Luaan

Kıvırcık Yasası, Tim Ottinger tarafından tarif edildiği gibi , bir değişkenin sürekli bir şey ifade etmesi gerektiğini vurgulamaktadır . Bana göre, SRP bundan biraz daha güçlüdür; Bir sınıf kavramsal olarak "bir şeyi" temsil edebilir, ancak iki dış değişimin sürücüsü bu "bir şeyin" bir yönünü farklı şekillerde ele alırsa veya iki farklı yönden endişe duyuyorsa SRP'yi ihlal edebilir. Sorun modelleme biridir; bir şeyi tek bir sınıf olarak modellemeyi seçtiniz, ancak etki alanıyla ilgili olarak bu seçimi sorunlu kılan bir şey var (kod temeli geliştikçe işler yolunuza girmeye başlar).
Filip Milovanović

2
@ FilipMilovanović Conway Yasası ile SRP arasında gördüğüm benzerlik, Bob Amca’nın Temiz Mimarlık kitabındaki SRP’yi açıklamasına göre, örgütün temiz bir çevrim dışı org şeması olduğu varsayımından geliyor. Bu eski bir fikir. Burada Mukaddes Kitapta bile bir alıntı var: "Hiçbir kimse iki efendiye hizmet edemez".
candied_orange

1
@TKK, onu Conly yasası ile değil Conways yasasıyla ilişkilendirir. Bob Amca'nın Clean Architecture kitabında kendisi dediği için SRP'nin Curly'nin yasası olduğu fikrini reddediyorum.
candied_orange

48

Buradaki anahtar kapsam veya tercih ederseniz ayrıntı düzeyidir . Bir sınıf tarafından temsil edilen işlevselliğin bir kısmı, her biri bir yöntem olan işlevselliğin parçalarına ayrılabilir.

İşte bir örnek. Bir diziden bir CSV oluşturmanız gerektiğini düşünün. RFC 4180 ile uyumlu olmak istiyorsanız, algoritmayı uygulamak ve tüm son durumları ele almak biraz zaman alabilir.

Tek bir yöntemle yapmak, özellikle okunamayacak bir kodla sonuçlanacak ve özellikle yöntem aynı anda birkaç şey yapacaktır. Bu nedenle, onu birkaç yönteme ayıracaksınız; örneğin, bunlardan biri başlığın, yani CSV'nin ilk satırının oluşturulmasından sorumlu olabilirken, başka bir yöntem, herhangi bir türün değerini, CSV formatı için uygun olan dize gösterimine dönüştürürken, bir diğeri, değerin çift tırnak içine alınması gerekir.

Bu yöntemlerin kendi sorumlulukları vardır. Çift tırnak eklemek gerekip gerekmediğini kontrol eden yöntemin kendine ait bir değeri vardır ve başlığı oluşturan yöntemde bir tane bulunur. Bu yöntemlere uygulanan SRP'dir .

Şimdi, tüm bu yöntemlerin ortak bir amacı var, yani, bir sıralama yapın ve CSV'yi oluşturun. Bu, sınıfın tek sorumluluğudur .


Pablo H yorumladı:

Güzel bir örnek, ancak SRP'nin neden bir sınıfın birden fazla ortak yönteme sahip olmasına izin verdiğini hala yanıtlamadığını hissediyorum.

Aslında. Verdiğim CSV örneği ideal olarak bir ortak yönteme sahip ve diğer tüm yöntemler özel. Daha iyi bir örnek, bir Queuesınıf tarafından uygulanan bir kuyruk olabilir . Bu sınıf, temel olarak iki yöntem içerir: push(ayrıca denir enqueue) ve pop(de denir dequeue).

  • Sorumluluğu Queue.pushsıranın kuyruğuna bir nesne eklemektir.

  • Sorumluluğu Queue.popsıranın başından bir nesneyi kaldırmak ve sıranın boş olduğu durumu ele almaktır.

  • QueueSınıfın sorumluluğu sıra mantığı sağlamaktır.


1
Güzel bir örnek, ancak SRP'nin neden bir sınıfın birden fazla ortak yönteme sahip olmasına izin verdiğini hala yanıtlamadığını hissediyorum .
Pablo H,

1
@PabloH: adil. Bir sınıfın iki yöntemi olduğu başka bir örnek ekledim.
Arseni Mourzenko

30

Bir fonksiyon bir fonksiyondur.

Bir sorumluluk bir sorumluluktur.

Bir tamirci, teşhis, bazı basit bakım işleri, bazı gerçek onarım işleri, bazılarına başkalarının görev devri vb.

Bir konteyner sınıfı (liste, dizi, sözlük, harita, vb.) Nesneleri saklama, yerleştirme, erişim, erişim sağlama, bir çeşit sipariş vb. İçeren nesneleri saklama sorumluluğuna sahiptir.

Tek bir sorumluluk, çok az kod / işlevsellik olmadığı anlamına gelmez, aynı işlev altında hangi işlevlerin bir araya geldiği anlamına gelir.


2
Hemfikir olmak. @Aulis Ronkainen - iki cevabı birbirine bağlamak için. Ve içsel sorumluluklar için, mekanik analojinizi kullanarak, bir garaj, araçların bakımını üstlenir. garajdaki farklı mekanikler arabanın farklı kısımlarının sorumluluğunu taşır, ancak bu mekaniklerin her biri bir arada
çalışırlar

2
@ wolfsshield, kabul etti. Sadece bir şeyi yapan tamirci işe yaramaz, ancak tek bir sorumluluğu olan tamirci değildir (en azından zorunlu olarak). Her ne kadar gerçek hayattaki analojiler soyut OOP kavramlarını tanımlamak için her zaman en iyisi olmasa da, bu farklılıkları ayırt etmek önemlidir. Farkı anlamadığına inanıyorum, ilk başta karışıklığı yaratan şey bu.
Aulis Ronkainen

3
@AulisRonkainen Bir benzetme gibi gözükür, kokar ve benziyor olsa da, mekaniği SRP'de Sorumluluk teriminin özel anlamını vurgulamak için kullanmayı düşündüm . Cevabınıza tamamen katılıyorum.
Peter,

20

Tek sorumluluk, mutlaka sadece bir şey yaptığı anlamına gelmez.

Örneğin, bir Kullanıcı servis sınıfı:

class UserService {
    public User Get(int id) { /* ... */ }
    public User[] List() { /* ... */ }

    public bool Create(User u) { /* ... */ }
    public bool Exists(int id) { /* ... */ }
    public bool Update(User u) { /* ... */ }
}

Bu sınıfın birçok yöntemi var, ancak sorumluluğu açık. Veri deposundaki kullanıcı kayıtlarına erişim sağlar. Tek bağımlılıkları Kullanıcı modeli ve veri deposudur. Gevşek bir şekilde bağlanmış ve son derece uyumludur, bu gerçekten SRP'nin düşünmeye çalıştığı şeydir.

SRP "Arabirim ayırma ilkesi" ile karıştırılmamalıdır (bkz. SOLID ). Arabirim ayrıştırma ilkesi (ISS), daha küçük ve hafif arabirimlerin daha geniş ve genelleştirilmiş arabirimlere tercih edildiğini söylüyor. Go, standart kütüphanesinde ISS'yi yoğun şekilde kullanıyor:

// Interface to read bytes from a stream
type Reader interface {
    Read(p []byte) (n int, err error)
}

// Interface to write bytes to a stream
type Writer interface {
    Write(p []byte) (n int, err error)
}

// Interface to convert an object into JSON
type Marshaler interface {
    MarshalJSON() ([]byte, error)
}

SRP ve ISS kesinlikle ilişkili, ancak biri diğerini ima etmiyor. ISS arayüz düzeyinde ve SRP sınıf düzeyinde. Bir sınıf birkaç basit arayüz uygularsa, artık sadece bir sorumluluğu olmayabilir.

ISP ve SRP arasındaki farkı gösterdiği için Luaan'a teşekkür ederiz.


3
Aslında arayüz ayrıştırma ilkesini (SOLID'deki "I") açıklıyorsunuz. SRP oldukça farklı bir canavardır.
Luaan

Bir kenara, burada hangi kodlama kurallarını kullanıyorsunuz? Ben beklenir nesneleri UserService ve UserUpperCamelCase edilecek, ancak yöntemleri Create , Existsve Updateben lowerCamelCase yapmış olur.
KlaymenDK

1
Haklısın @KlaymenDK, büyük (büyük harf = ihraç / kamusal, küçük harf = özel) Go kullanarak sadece alışkanlıktır
Jesse

@Luaan Buna işaret ettiğin için teşekkürler, cevabımı açıklığa kavuştururum
Jesse

1
@KlaymenDK Birçok dil, sınıfların yanı sıra yöntemler için PascalCase'i kullanır. C # örneğin.
Omegastick

15

Bir restoranda bir şef var. Tek sorumluluğu yemek yapmak. Yine de biftek, patates, brokoli ve yüzlerce başka şey yapabilir. Menüde yemek başına bir şef tutar mısın? Veya her yemeğin her bileşeni için bir şef? Ya da tek sorumluluğunu yerine getirebilecek bir şef: Yemek yapmak?

O şefin bordrosunu da yapmasını isterseniz, o zaman SRP'yi ihlal edersiniz.


4

Karşı örnek: değişken durumu depolamak.

Farz edelim ki, şimdiye kadarki en basit dersin, tek işi depolamak int.

public class State {
    private int i;


    public State(int i) { this.i = i; }
}

Yalnızca 1 yöntemle sınırlı olsaydınız setState(), getState()kapsüllemeyi bozma ve igenel yapmadıkça , a veya a olabilirdi .

  • Bir pasör, alıcı olmadan işe yaramaz (bilgileri asla okuyamazsınız)
  • Alıcı, ayarlayıcı olmadan işe yaramaz (bilgileri asla değiştiremezsiniz).

Açıkçası, bu tek sorumluluk , bu sınıfta en az 2 yönteme sahip olmayı gerektiriyor . QED.


4

Tek sorumluluk ilkesini yanlış yorumluyorsun.

Tek sorumluluk, tek bir yönteme eşit değildir. Farklı şeyler ifade ediyorlar. Yazılım geliştirmede uyum hakkında konuşuruz . Yüksek uyumu olan fonksiyonlar (metotlar) birbirine aittir ve tek bir sorumluluk üstlenirler.

Tek bir sorumluluk ilkesinin yerine getirilmesi için sistemi tasarlamak geliştiriciye kalmıştır. Kişi bunu bir soyutlama tekniği olarak görebilir ve bu nedenle bazen bir fikir meselesidir. Tek sorumluluk ilkesini uygulamak, kodu temel olarak test etmeyi ve mimarisini ve tasarımını anlamayı kolaylaştırır.


2

Bir şeylere bakmak ve bunları işlevler yerine verilerin bakış açısından düzenlemek çoğu zaman (herhangi bir dilde, ancak özellikle OO dillerinde) yararlı olur .

Bu nedenle, bir sınıfın bütünlüğünü korumak ve sahip olduğu verileri doğru kullanmak için yardım sağlamak sorumluluğunu göz önünde bulundurun. Açıkçası, tüm kod birkaç sınıfa yayılmak yerine, bir sınıftaysa bunu yapmak daha kolaydır. İki nokta eklemek daha güvenilir bir şekilde yapılır ve sınıfta başka bir yerde olduğundan çok bir Point add(Point p)yöntemle kod daha kolay korunur Point.

Ve özellikle, sınıf tutarsız veya yanlış verilere yol açabilecek hiçbir şey göstermemelidir. Örneğin, Pointbir (0,0) ila (127,127) arası bir düzlemde yer alması gerekiyorsa, yapıcı ve yenisini değiştiren veya üreten tüm yöntemler, Pointverdikleri değerleri kontrol etme ve bunu ihlal edecek değişiklikleri reddetme sorumluluğuna sahiptir. gereklilik. (Genellikle bir şey gibi bir şey Pointdeğişmez olur ve Pointinşa edildikten sonra değiştirilmesinin hiçbir yolu olmadığından emin olmak aynı zamanda sınıfın sorumluluğudur)

Burada katmanlaşmanın tamamen kabul edilebilir olduğunu unutmayın. Bir olabilir Pointbireysel noktaları ve başa çıkmak için sınıf Polygonkümesi ile başa çıkmak için sınıfın Points; Bu hala ayrı sorumlulukları vardır çünkü Polygondelegeler ile ilgisi sadece bir şeyle başa çıkmak için bütün sorumluluğu Point(bir noktaya temin eden bir hem sahiptir gibi xbir ydeğer) Pointsınıfına.

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.