DonanımHaber

SOLID etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
SOLID etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

Software Architecture (SOLID) & Design Patterns in Java - Udemy - İnglizce - Holczer Balazs - Udemy

 Holczer Balzacs'ın SOLID ve Design Patternlerı kısa ve yeterince açıklayıcı şekilde, Java kodlarıyla anlattığı kursa buradan ulaşabilirsiniz.




Software Architecture (SOLID) & Design Patterns in Java





understand SOLID principles
understand the core of design patterns
undertand object oriented design
understand the single responsibility principle
understand the open / closed principle
understand the Liskov substitution principle
understand the interface segregation principle
understand the dependency inversion principle
understand creational design patterns (singleton pattern, factory pattern, builder pattern and prototype pattern)
understand behavioral design patterns (strategy pattern, command pattern, visitor pattern and template pattern)
understand structural design patterns (adapter pattern, facade pattern and decorator pattern)

SOLID Prensipleri -The Liskov Substitution Principle - Uncle Bob Çevirisi

Merhaba, bu seride sizlere Uncele Bob'un (Robert C. Martin) SOILD presnipleri için yazdığı makaleleri Türkçe'ye çevireceğim. Umarım yararlı bir yazı dizisi olur.

Seri'nin diğer yazıları :

The Single Responsibility Principle

Open Closed Principle

3- The Liskov Substitution Principle

GİRİŞ

Bu ilke, sürdürülebilir ve tekrar kullanılabilir kod oluşturma temelidir. İyi tasarlanmış kodun değiştirilmeden genişletilebileceğini belirtir; iyi tasarlanmış bir programda eski, halihazırda çalışan kodu değiştirmek yerine yeni kod eklenerek yeni özellikler eklenmesini amaç edinir.

Açık-Kapalı prensibinin ardındaki birincil mekanizmalar soyutlama ve polimorfizmdir. C ++ gibi statik olarak yazılmış dillerde, soyutlama ve polimorfizmi destekleyen temel mekanizmalardan biri kalıtımdır. Soyut temel sınıflarda saf sanal fonksiyonlar tarafından tanımlanan soyut polimorfik arayüzlere uyan türetilmiş sınıflar oluşturabiliriz.

Bu özel miras kullanımını yöneten tasarım kuralları nelerdir? En iyi miras hiyerarşilerinin özellikleri nelerdir? Açık-Kapalı prensibine uymayan hiyerarşiler yaratmamıza neden olacak tuzaklar nelerdir? Bunlar bu makalenin ele alacağı sorular olacak.

PRENSİP

POINTERLARI VEYA TEMEL SINIFLARIN REFERANSLARINI  KULLANAN FONKSİYONLAR, TÜREDİKLERİ SINIFLARIN NESNELERİNİ TİPLERİNİ BİLMEDEN KULLANABİLMELİDİRLER.

Yukarıdaki, Liskov İkame Prensibi'nin (LSP) bir açıklamasıdır. Barbara Liskov ilk olarak yaklaşık 8 yıl önce şunu yazmıştır:

Burada istenen şu ikame özelliğine benzer bir şeydir: S tipindeki her bir o1 nesnesi için T tipinde bir O2 nesnesi varsa, T olarak tanımlanan tüm P programları için, o1 o2 yerine ikame edildiğinde P'nin davranışı değişmiyorsa, o zaman S, T'nin bir alt tipidir.

Bu ilkenin önemi, ihlal etmenin sonuçlarını düşündüğünüzde belirginleşir. LSP'ye uymayan bir metod varsa, bu metod bir temel sınıfa bir işaretçi veya başvuru kullanır, ancak bu temel sınıfın tüm alt sınıflarını bilmelidir. Böyle bir işlev Açık-Kapalı ilkesini ihlal eder, çünkü temel sınıfın yeni bir alt sınıfı oluşturulduğunda değiştirilmesi gerekir.

LSP İhlaline Basit Bir Örnek

Bu ilkenin en göze çarpan ihlallerinden biri, bir nesnenin tipine dayalı bir metod seçmek için C ++ Çalışma Zamanı Türü Bilgilerinin (RTTI) kullanılmasıdır. yani .:

void DrawShape(const Shape& s)
{
if (typeid(s) == typeid(Square))
  DrawSquare(static_cast < square > (s)); 
else if (typeid(s) == typeid(Circle))
  DrawCircle(static_cast < circle > (s));
}

[Not: static_cast yeni cast operatörlerinden biridir. Bu örnekte normal bir cast gibi çalışır. yani DrawSquare ((Square &) s); Bununla birlikte, yeni sözdiziminin kullanımı daha güvenli ve grep gibi araçlarla bulunması daha katı kurallara sahiptir. Bu nedenle tercih edilir.]

Açıkçası DrawShape metodu kötü bir şekilde oluşturulmuştur. Shape sınıfının her olası alt sınıfı bilmeli ve yeni Shape alt sınıfları her oluşturulduğunda değiştirilmelidir. Gerçekten de birçoğu bu metodun yapısını Nesneye Dayalı Tasarıma lanetli olarak görmektedir.

Kare ve Dikdörtgen, Daha İnce Bir İhlal.

Ancak, LSP'yi ihlal etmenin çok daha incelikli başka yolları da vardır. Aşağıda açıklandığı gibi Rectangle sınıfını kullanan bir uygulama düşünün:



class Rectangle
{
	public:
		void SetWidth(double w) {itsWidth=w;}
		void SetHeight(double h) {itsHeight=w;}
		double GetHeight() const {return itsHeight;}
		double GetWidth() const {return itsWidth;}
	private:
		double itsWidth;
		double itsHeight;
};

Bu uygulamanın iyi çalıştığını ve birçok sitede kurulu olduğunu hayal edin. Tüm başarılı yazılımlarda olduğu gibi, kullanıcılarının ihtiyaçları değiştikçe yeni fonksiyonlara ihtiyaç duyulmaktadır. Bir gün kullanıcıların dikdörtgenlere ek olarak kareleri de işleme yeteneği talep ettiğini hayal edin.

C++'da kalıtımın ISA ilişkisi olduğu sıklıkla söylenir. Başka bir deyişle, yeni bir tür nesnenin eski tür bir nesneyle ISA ilişkisini yerine getirdiği söylenebilirse, o zaman yeni nesnenin sınıfı eski nesnenin sınıfından türetilmelidir.

Açıkça, bir kare, tüm normal niyet ve amaçlar için bir dikdörtgendir. ISA ilişkisi geçerli olduğundan, Square sınıfını Rectangle'dan türetilmiş olarak modellemek mantıklıdır.


ISA ilişkisinin bu kullanımı birçok kişi tarafından Nesne Yönelimli Analizin temel tekniklerinden biri olarak kabul edilir. Bir kare bir dikdörtgendir ve bu nedenle Square sınıfı, Rectangle sınıfından türetilmelidir. Bununla birlikte, bu tür bir düşünce, bazı ince, ancak önemli sorunlara yol açabilir. Genellikle bu sorun, biz uygulamayı gerçekten kodlamaya çalışana kadar öngörülemez. İlk ipucumuz, bir Square'in hem ItsHeight hem de itsWidth üye değişkenlerine ihtiyaç duymadığı gerçeği olabilir. Yine de onları yine de miras alacak. Açıkçası bu israf. Ayrıca, yüz binlerce Square nesnesi oluşturacaksak (örneğin, karmaşık bir devrenin her bileşeninin her pininin bir kare olarak çizildiği bir CAD/CAE programı), bu israf son derece önemli olabilir.

Ancak, bellek verimliliğiyle pek ilgilenmediğimizi varsayalım. Başka sorunlar var mı? Aslında! Square, SetWidth ve SetHeight işlevlerini devralır. Bir karenin genişliği ve yüksekliği aynı olduğundan, bu işlevler bir Kare için kesinlikle uygun değildir. Bu, tasarımda bir sorun olduğuna dair önemli bir ipucu olmalıdır. Ancak, sorunu ortadan kaldırmanın bir yolu var. Set-Width ve SetHeight'ı aşağıdaki gibi geçersiz kılabiliriz:

void Square::SetWidth(double w)
{
	Rectangle::SetWidth(w);
	Rectangle::SetHeight(w);
}
void Square::SetHeight(double h)
{
	Rectangle::SetHeight(h);
	Rectangle::SetWidth(h);
}
Şimdi, birisi bir Square nesnesinin genişliğini ayarladığında, yüksekliği de buna bağlı olarak değişecektir. Ve birisi yüksekliğini ayarladığında genişlik de onunla birlikte değişecektir. Böylece, Karenin değişmezleri bozulmadan kalır. Square nesnesi matematiksel olarak uygun bir kare olarak kalacaktır.
 
  Square s;
s.SetWidth(1); // Fortunately sets the height to 1 too.
s,SetHeight(2); // sets width and heigt to 2, good thing.

Ama aşağıdaki fonksiyonu bir düşünün

void f(Rectangle r)
{
r.SetWidth(32); // calls Rectangle::SetWidth
}

Bu fonksiyona bir Square nesnesine bir referans iletirsek, yükseklik değişmeyeceği için Square nesnesi bozulacaktır. Bu, LSP'nin açık bir ihlalidir. f işlevi, bağımsız değişkenlerinin türevleri için çalışmaz. Başarısızlığın nedeni, SetWidth ve SetHeight'ın Rectangle'da virtual olarak bildirilmemesidir.

Bunu kolayca düzeltebiliriz. Ancak, türetilmiş bir sınıfın oluşturulması, temel sınıfta değişiklik yapmamıza neden olduğunda, genellikle tasarımın hatalı olduğu anlamına gelir. Gerçekten de Açık-Kapalı ilkesini ihlal ediyor. Buna, SetWidth ve SetHeight'ı sanal yapmayı unutmanın gerçek tasarım hatası olduğu ve şu anda onu düzelttiğimiz iddiasıyla karşı çıkabiliriz. Ancak, bir dikdörtgenin yüksekliğini ve genişliğini ayarlamak son derece ilkel işlemler olduğundan, bunu haklı çıkarmak zordur. Square'in varlığını tahmin etmeseydik, hangi akıl yürütmeyle onları sanal hale getirirdik.

Yine de, argümanı kabul ettiğimizi ve sınıfları düzelttiğimizi varsayalım. Aşağıdaki kodla tamamlıyoruz:
 
   class Rectangle
	{
	public:
		virtual void SetWidth(double w) {itsWidth=w;}
		virtual void SetHeight(double h) {itsHeight=h;}
		double GetHeight() const {return itsHeight;}
		double GetWidth() const {return itsWidth;}
    private:
		double itsHeight;
	double itsWidth;
	};
	class Square : public Rectangle
	{
	public:
		virtual void SetWidth(double w);
		virtual void SetHeight(double h);
	};
	void Square::SetWidth(double w)
	{
		Rectangle::SetWidth(w);
		Rectangle::SetHeight(w);
	}
	void Square::SetHeight(double h)
	{
		Rectangle::SetHeight(h);
		Rectangle::SetWidth(h);
	}
   
   
Gerçek Sorun

Bu noktada, işe yarıyor gibi görünen Kare ve Dikdörtgen olmak üzere iki sınıfımız var. Bir Square nesnesine ne yaparsanız yapın, matematiksel bir kare ile tutarlı kalacaktır. Ve bir Rectangle nesnesine ne yaparsanız yapın, o matematiksel bir dikdörtgen olarak kalacaktır. Ayrıca, bir Dikdörtgen'e bir işaretçi veya referans kabul eden bir fonksiyona bir Kare iletebilirsiniz ve Kare yine bir kare gibi davranacak ve tutarlı kalacaktır.

Böylece, modelin artık kendi içinde tutarlı ve doğru olduğu sonucuna varabiliriz. Ancak bu sonuç yanlış olacaktır. Kendinden tutarlı bir model, tüm kullanıcıları ile mutlaka tutarlı olmak zorunda değildir! Aşağıdaki g fonksiyonunu düşünün.

void g(Rectangle& r)
{
	r.SetWidth(5);
	r.SetHeight(4);
	assert(r.GetWidth() * r.GetHeight()) == 20);
}

Bu işlev, bir Dikdörtgen olduğuna inandığı şeyin SetWidth ve SetHeight üyelerini çağırır. İşlev, bir Dikdörtgen için gayet iyi çalışır, ancak bir Kare iletilirse bir onaylama hatası bildirir. İşte asıl sorun şu: Bu işlevi yazan programcı, bir Dikdörtgenin genişliğini değiştirmenin yüksekliğini değiştirmediğini varsaymakta haklı mıydı?


Açıkçası, g programcısı bu çok makul varsayımı yaptı. Programcıları bu varsayımı yapan fonksiyonlara bir Kare iletmek problemlere yol açacaktır. Bu nedenle, Rectangle nesnelerine işaretçiler veya referanslar alan, ancak Square nesneleri üzerinde düzgün çalışamayan işlevler vardır. Bu işlevler, LSP'nin ihlal edildiğini ortaya çıkarır. Dikdörtgen'in Kare türevinin eklenmesi bu işlevi bozmuştur; ve böylece Açık-Kapalı ilkesi ihlal edilmiştir.


Geçerlilik İçsel Değildir

Bu bizi çok önemli bir sonuca götürüyor. Tek başına bakıldığında bir model anlamlı bir şekilde doğrulanamaz. Bir modelin geçerliliği ancak müşterileri açısından ifade edilebilir. Örneğin Square ve Rectangle sınıflarının son halini ayrı ayrı incelediğimizde kendi içinde tutarlı ve geçerli olduklarını gördük. Ancak onlara temel sınıf hakkında makul varsayımlarda bulunan bir programcının bakış açısından baktığımızda model bozuldu.

Bu nedenle, belirli bir tasarımın uygun olup olmadığı değerlendirilirken, çözüme tek başına bakmamak gerekir. Bu tasarımın kullanıcıları tarafından yapılacak makul varsayımlar açısından değerlendirilmelidir.

Ne yanlış gitti?

Peki ne oldu? Görünüşe göre makul olan Kare ve Dikdörtgen modeli neden kötü gitti? Sonuçta, Kare Dikdörtgen değil mi? ISA ilişkisi devam etmiyor mu?

Hayır! Bir kare bir dikdörtgen olabilir, ancak bir Square nesnesi kesinlikle bir Rectangle nesnesi değildir. Niye? Çünkü bir Square nesnesinin davranışı, bir Rectangle nesnesinin davranışıyla tutarlı değildir. Davranışsal olarak, Kare Dikdörtgen değildir! Ve yazılımın gerçekten ilgili olduğu şey davranıştır.

LSP, OOD'de ISA ilişkisinin davranışla ilgili olduğunu açıkça ortaya koymaktadır. İçsel özel davranış değil, dışsal kamusal davranış; clientlerın bağlı olduğu davranış. Örneğin, yukarıdaki g fonksiyonunun yazarı, Dikdörtgenlerin yükseklikleri ve genişlikleri birbirinden bağımsız olarak değişecek şekilde davranmasına bağlıydı. İki değişkenin bu bağımsızlığı, diğer programcıların muhtemelen bağımlı olduğu dışsal bir genel davranıştır.

LSP'nin ve onunla birlikte Açık-Kapalı ilkesini tutması için, tüm türevlerin, müşterilerin kullandıkları temel sınıflardan beklediği davranışa uyması gerekir.

Sözleşmeye Göre Tasarım (Design By Contract)


LSP ile Bertrand Meyer2 tarafından açıklandığı üzere Sözleşmeye Göre Tasarım kavramı arasında güçlü bir ilişki vardır. Bu şemayı kullanarak, sınıf metotları önkoşulları ve sonkoşulları bildirir. Yöntemin uygulanabilmesi için ön koşulların doğru olması gerekir. Tamamlandığında, yöntem, son koşulun doğru olacağını garanti eder.

Rectangle::SetWidth(double w) öğesinin son durumunu şu şekilde görebiliriz:

assert((itsWidth == w) && (itsHeight == old.itsHeight)); Şimdi, Meyer3 tarafından belirtildiği gibi, türevler için ön koşullar ve son koşullar için kural şudur:

...bir rutini [türevde] yeniden tanımlarken, yalnızca ön koşulunu daha zayıf olanla ve son koşulunu daha güçlü olanla değiştirebilirsiniz.

Başka bir deyişle, bir nesneyi temel sınıf arabirimi aracılığıyla kullanırken, kullanıcı yalnızca temel sınıfın ön koşullarını ve son koşullarını bilir. Bu nedenle, türetilmiş nesneler, bu tür kullanıcıların, temel sınıfın gerektirdiğinden daha güçlü olan ön koşullara uymasını beklememelidir. Yani, temel sınıfın kabul edebileceği her şeyi kabul etmeleri gerekir. Ayrıca, türetilmiş sınıflar, tabanın tüm son koşullarına uygun olmalıdır. Yani, davranışları ve çıktıları, temel sınıf için belirlenen kısıtlamaların hiçbirini ihlal etmemelidir. Temel sınıfın kullanıcıları, türetilmiş sınıfın çıktısıyla karıştırılmamalıdır.

Açıkça, Square::SetWidth(double w) öğesinin son koşulu, “(itsHeight == old.itsHeight)” temel sınıf yan tümcesine uymadığından, yukarıdaki Rectangle::SetWidth(double w) öğesinin son koşulundan daha zayıftır. Bu nedenle, Square::SetWidth(double w) temel sınıfın sözleşmesini ihlal eder.

Eiffel gibi bazı diller, ön koşullar ve son koşullar için doğrudan desteğe sahiptir. Bunları gerçekten bildirebilir ve çalışma zamanı sisteminin bunları sizin için doğrulamasını sağlayabilirsiniz. C++'ın böyle bir özelliği yoktur. Yine de C++'da bile her yöntemin ön koşullarını ve son koşullarını manuel olarak değerlendirebilir ve Meyer kuralının ihlal edilmediğinden emin olabiliriz. Ayrıca, bu ön koşulları ve son koşulları her bir yöntem için yorumlarda belgelemek çok yardımcı olabilir.

A real Example Kısmını çevirmedim. Orjinal makaleden gözatabilirsiniz.


ÖZET SONUÇ

Açık-Kapalı prensibi, OOD için yapılan iddiaların çoğunun kalbinde yer alır. Bu ilke yürürlükte olduğunda, uygulamalar daha sürdürülebilir, tekrar kullanılabilir ve sağlamdır. Liskov İkame İlkesi (Diğer adıyla Sözleşmeli Tasarım (Design by Contract)), Açık-Kapalı prensibine uyan tüm programların önemli bir özelliğidir. Sadece alt sınıflar temel tipleri için tamamen ikame edildiğinde, bu temel sınıfları kullanan metodlar cezasızlıkla yeniden kullanılabilir ve alt tipler cezasızlıkla değiştirilebilir.

Metnin Orjinali :


SOLID Prensipleri -The Open-Closed Principle- Uncle Bob Çevirisi

Merhaba, bu seride sizlere Uncele Bob'un (Robert C. Martin) SOILD presnipleri için yazdığı makaleleri Türkçe'ye çevireceğim. Umarım yararlı bir yazı dizisi olur.

Seri'nin diğer yazıları :

The Single Responsibility Principle




2- The Open-Closed Principle


Ivar Jacobson'un dediği gibi: “Tüm sistemler yaşam döngüleri boyunca değişir. İlk sürümden daha uzun sürmesi beklenen sistemler geliştirilirken bu akılda tutulmalıdır. ” Değişim karşısında, kararlı ve ilk sürümden daha uzun sürecek tasarımlar nasıl oluşturabiliriz? Bertrand Meyer, ünlü open-closed prensibini icat ettiği 1988'den beri bize rehberlik etti. Onu yeniden ifade etmek için:

SOFTWARE ENTITIES (CLASSES, MODULES, FUNCTIONS, ETC.) SHOULD BE OPEN FOR EXTENSION, BUT CLOSED FOR MODIFICATION.
(Yazılım varlıkları (SINIFLAR, MODÜLLER, FONKSİYONLAR, VB.) gelişime açık, ancak değişikliğe kapalı olmalıdır.) 

Bir programda yapılan tek bir değişiklik, bağımlı modüllerde kademeli bir değişiklikle sonuçlandığında, bu program “kötü” tasarımla ilişkilendirdiğimiz istenmeyen nitelikleri sergiler. Program kırılgan, katı, öngörülemez ve kullanılamaz hale gelir. Açık kapalı prensibi bunu çok basit bir şekilde engeller. Asla değişmeyen modüller tasarlamanız gerektiğini söylüyen bu prensip, gereksinimler değiştiğinde, bu tür modüllerin davranışlarını, zaten çalışan eski kodu değiştirerek değil, yeni kod ekleyerek genişletmeyi önerir.

TANIM

Açık-kapalı prensibine uyan modüllerin iki temel özelliği vardır.

1.  “Genişlemeye Açık” dır.
Bu, modülün davranışının genişletilebileceği anlamına gelir. Uygulamanın gereksinimleri değiştikçe veya yeni uygulamaların gereksinimlerini karşılamak için modülün yeni ve farklı şekillerde davranmasını sağlayabiliriz.

2. “Değişikliğe Kapalıdır”.
Böyle bir modülün kaynak kodu dokunulmazdır. Hiç kimsenin kaynak kodda değişiklik yapmasına izin verilmez.

Bu iki özelliğin birbiriyle çeliştiği anlaşılıyor. Bir modülün davranışını genişletmenin normal yolu, o modülde değişiklik yapmaktır. Değiştirilemeyen bir modülün normalde sabit bir davranışı olduğu düşünülmektedir. Bu iki karşıt özellik nasıl çözülebilir?


Abstraction is the Key
(Soyutlama burada kilit anahtardır)

C ++ 'da, nesne yönelimli tasarım ilkelerini kullanarak, sabit ve sınırsız olası davranış grubunu temsil eden soyutlamalar oluşturmak mümkündür. Soyutlamalar soyut temel sınıflardır ve sınırsız olası davranış grubu tüm olası alt sınıfları tarafından temsil edilir. Bir modülün bir soyutlamayı manipüle etmesi mümkündür. Böyle bir modül, düzeltilmiş bir soyutlamaya bağlı olduğu için modifikasyon için kapatılabilir. Yine de bu modülün davranışı, soyutlamanın yeni alt sınıfları yaratılarak genişletilebilir.

Şekil 1, açık-kapalı prensibine uymayan basit bir tasarımı göstermektedir. Hem Client hem de Server sınıfları somuttur. Server sınıfının üye metodlarının abstract olduğuna dair bir garanti yoktur. Client sınıfı Server sınıfını kullanır. Bir Client nesnesinin farklı bir sunucu nesnesi kullanmasını istiyorsak, yeni sunucu sınıfını adlandırmak için Client sınıfının değiştirilmesi gerekir.


Şekil 2, açık-kapalı prensibine uyan ilgili tasarımı göstermektedir. Bu durumda, AbstractServer sınıfı saf abstract üye metodlarına sahip soyut bir sınıftır. Client sınıfı bu soyutlamayı kullanır. Ancak Client sınıfının nesneleri, alt sınıf Server sınıfının nesnelerini kullanacaktır. Client nesnelerinin farklı bir sunucu sınıfı kullanmasını istiyorsak, AbstractServer sınıfının yeni bir alt sınıfı oluşturulabilir. Client sınıfı değişmeden kalabilir.



The Shape Abstraction
(Şekil Soyutlaması)


Aşağıdaki örneği ele alalım. Standart bir GUI üzerine daireler ve kareler çizebilmemiz gereken bir uygulamamız var. Daireler ve kareler belirli bir sırada çizilmelidir. Dairelerin ve karelerin bir listesi uygun sırayla oluşturulacaktır ve program listeyi bu sırayla yürütmeli ve her daireyi veya kareyi çizmelidir.

C'de, açık-kapalı prensibine uymayan prosedürel teknikler kullanarak, bu problemi aşağıdai kodda gösterildiği gibi çözebiliriz. Burada aynı ilk öğeye sahip olan ancak bunun ötesinde farklı bir veri yapıları kümesi görüyoruz. Her birinin ilk öğesi, veri yapısını daire veya kare olarak tanımlayan bir tür kodudur. DrawAllShapes metodu, bu veri yapılarına bir dizi işaretçi yürütür, tür kodunu inceler ve sonra uygun fonksiyonu çağırır (DrawCircle veya DrawSquare).
Procedural Solution to the Square/Circle Problem
enum ShapeType {circle, square};
struct Shape
{
 ShapeType itsType;
};
struct Circle
{
 ShapeType itsType;
 double itsRadius;
 Point itsCenter;
};
struct Square
{
 ShapeType itsType;
 double itsSide;
 Point itsTopLeft;
};
//
// These functions are implemented elsewhere
//
void DrawSquare(struct Square*)
void DrawCircle(struct Circle*);
typedef struct Shape *ShapePointer;
void DrawAllShapes(ShapePointer list[], int n)
{
 int i;
 for (i=0; iitsType)
 {
 case square:
 DrawSquare((struct Square*)s);
 break;
 case circle:
 DrawCircle((struct Circle*)s);
 break;
 }
 }
}

DrawAllShapes metodu, yeni şekil türlerine karşı kapatılamadığından açık-kapalı prensibine uymaz. Üçgenleri içeren şekiller listesi çizebilmek için bu fonksiyonu genişletmek isteseydim, fonksiyonu değiştirmek zorunda kalırdım. Aslında, çizmem gereken herhangi bir yeni şekil için fonksiyonu değiştirmem gerekirdi.

Elbette bu program sadece basit bir örnektir. Gerçek hayatta DrawAllShapes fonksiyonundaki switch deyimi, uygulamanın her yerindeki çeşitli fonksiyonlarda tekrar tekrar tekrarlanır; her biri biraz farklı bir şey yapar. Böyle bir uygulamaya yeni bir şekil eklemek, bu tür anahtar ifadelerin (veya if / else zincirlerinin) bulunduğu her yer için her birine yeni şekil ekleme anlamına gelir. Ayrıca, tüm switch deyimlerinin ve if / else zincirlerinin DrawAllShapes'deki kadar güzel yapılandırılmış olması pek olası değildir.

İf ifadelerinin tahminlerinin mantıksal işleçlerle birleştirilecek ya da
switch statementlarının case'leri yerel karar almayı “basitleştirmek” için birleştirilecektir. Bu nedenle, yeni şeklin eklenmesi gereken tüm yerleri bulma ve anlama problemi önemsiz olabilir.

Aşağıdaki kod bloğu, açık-kapalı prensibine uyan kare / daire problemine bir çözüm kodunu gösterir. Bu durumda soyut bir Shape sınıfı oluşturulur. Bu soyut sınıf, Draw adında tek bir saf sanal metoda sahiptir. Daire ve Kare, Shape sınıfının alt sınıflarıdır.


Square/Circle problemine OOD Çözümü.
class Shape
{
 public:
 virtual void Draw() const = 0;
};
class Square : public Shape
{
 public:
 virtual void Draw() const;
};
class Circle : public Shape
{
 public:
 virtual void Draw() const;
};
void DrawAllShapes(Set& list)
{
 for (Iteratori(list); i; i++)
 (*i)->Draw();
}

Yeni bir şekil çizmek için yukarıdaki kodda DrawAllShapes metodunun davranışını genişletmek istiyorsak, tek yapmamız gereken Shape sınıfının yeni bir alt sınıfı eklemektir. DrawAllShapes metodunun değiştirilmesi gerekmez. Böylece DrawAllShapes açık kapalı prensibine uygundur. Davranışı değiştirilmeden genişletilebilir.

Gerçek dünyada Shape sınıfının çok daha fazla methodu olacaktır. Yine de uygulamaya yeni bir şekil eklemek hala oldukça basittir, çünkü gerekli olan tek şey yeni alt sınıfı oluşturmak ve tüm metodlarını uygulamaktır. Değişiklik gerektiren yerleri arayan tüm uygulamada arayarak avlanmaya gerek yoktur.

Açık-kapalı prensibine uyan programlar, mevcut kodu değiştirmek yerine yeni kod ekleyerek değiştirildiğinden, bu presibe uymayan programlar tarafından sergilenen değişikliklerin sıkıntısını yaşamazlar.

STRATEJİK KAPATMA

Hiçbir programın% 100 kapatılamayacağı açık olmalıdır. Örneğin, tüm Circle'ların herhangi bir Square'den önce çizilmesi gerektiğine karar verirsek, yukarıdaki koddaki DrawAllShapes metoduna ne olacağını düşünün. DrawAllShapes metodu böyle bir değişikliğe karşı kapalı değildir. Genel olarak, bir modül ne kadar “kapalı” olursa olsun, her zaman kapalı olmadığı bir tür değişiklik olacaktır.

Kapanış tamamlanamayacağı için stratejik olmalı. Yani, tasarımcı tasarımını kapatmak için hangi tür değişiklikleri yapması gerektiğini seçmelidir. Bu, deneyimden elde edilen belli bir miktar önseziyi gerektirir. Deneyimli tasarımcı, kullanıcıları ve sektörü farklı türdeki değişikliklerin olasılığını değerlendirecek kadar iyi tanır. Daha sonra açık-kapalı prensibinin en olası değişiklikler için çağrıldığından emin olur.

Using Abstraction to Gain Explicit Closure.
(Açık Kapatma Kazanmak için Soyutlama Kullanma.)

Çizim sırasındaki değişikliklere karşı DrawAllShapes metodunu nasıl kapatabiliriz? Kapamanın soyutlamaya dayandığını unutmayın. Bu nedenle, DrawAllShapes'i sıralamaya karşı kapatmak için bir çeşit “sıralama soyutlaması” na ihtiyacımız var. Yukarıdaki özel sıralama durumu, diğer şekil türlerinden önce belirli şekil türlerinin çizilmesiyle ilgilidir.

Bir sıralama politikası, herhangi iki nesne göz önüne alındığında, hangisinin önce çizilmesi gerektiğini keşfetmenin mümkün olduğunu gösterir. Böylece, başka bir Şekli bağımsız değişken olarak alan ve bir bool sonucu döndüren Precedes adlı bir Shape yöntemi tanımlayabiliriz. İletiyi alan Shape nesnesinin, Shape nesnesi bağımsız değişken olarak geçmeden önce sıralanması gerekmesi sonucunu doğrudur.

C ++ 'da bu metod aşırı yüklenmiş bir operatör <fonksiyonu ile temsil edilebilir.
Liste 3, yerinde sıralama yöntemleriyle Shape sınıfının nasıl görünebileceğini göstermektedir. Artık iki Shape nesnesinin göreceli sırasını belirlemenin bir yoluna sahip olduğumuza göre, bunları sıralayabilir ve sonra sırayla çizebiliriz. Liste 4, bunu yapan C ++ kodunu gösterir.

Bu bize Shape nesnelerini sıralamak ve bunları uygun sırada çizmek için bir araç sağlar. Ama hala iyi bir sıralama soyutlamamız yok. Haliyle, her bir Shape nesnesinin sıralamasını belirtmek için Precedes yöntemini geçersiz kılması gerekecektir. Bu nasıl olurdu? Karelerin Liste 5'ten önce çizildiğinden emin olmak için, Circle :: Preedes'de ne tür bir kod yazardık?


Listing 3
Shape with ordering methods.
class Shape
{
 public:
 virtual void Draw() const = 0;
 virtual bool Precedes(const Shape&) const = 0;
 bool operator<(const Shape& s) {return Precedes(s);}
};
Listing 4
DrawAllShapes with Ordering
void DrawAllShapes(Set& list)
{
 // copy elements into OrderedSet and then sort.
 OrderedSet orderedList = list;
 orderedList.Sort();
 for (Iterator i(orderedList); i; i++)
 (*i)->Draw();
}

}
Listing 5
Ordering a Circle
bool Circle::Precedes(const Shape& s) const
{
 if (dynamic_cast(s))
 return true;
 else
 return false;
}
<
Bu metodun açık-kapalı prensibine uymadığı çok açık olmalıdır. Yeni Shape alt sınıflarına karşı kapatmanın bir yolu yoktur. Her yeni bir Shape alt sınıfı oluşturulduğunda, bu metodun değiştirilmesi gerekir.


Using a “Data Driven” Approach to Achieve Closure.
(Kapanışı Gerçekleştirmek için “Veriye Dayalı” Bir Yaklaşım Kullanmak.)

Shape alt sınıflarının kapatılması, türetilmiş her sınıftaki değişiklikleri zorlamayan tablo güdümlü bir yaklaşım kullanılarak elde edilebilir. Liste 6'da bir olasılık gösterilmektedir.
Bu yaklaşımı kullanarak, genel olarak sıralama sorunlarına karşı DrawAllShapes metodunu ve yeni Shape alt sınıflarının oluşturulmasına veya Shape nesnelerini türlerine göre yeniden sıralayan politikadaki bir değişikliğe karşı Shape alt sınıflarının her birini başarıyla kapattık. (örneğin, önce Kareler çizilecek şekilde sıralamayı değiştirme.)

Listing 6
Tabloya dayalı sıralama mekanizması
#include 
#include 
enum {false, true};
typedef int bool;
class Shape
{
 public:
 virtual void Draw() const = 0;
 virtual bool Precedes(const Shape&) const;
 bool operator<(const Shape& s) const
 {return Precedes(s);}
 private:
 static char* typeOrderTable[];
};
char* Shape::typeOrderTable[] =
{
 “Circle”,
 “Square”,
 0
};
// Bu fonksiyon sınıf isimlerine göre tabloda arama yapar
// Tablo hangi şeklin çizileceğikonusunda sıralama bilgisi verir
// Bulunamayan şekiller
// her zaman bulunan şekillerden önce gelir.

bool Shape::Precedes(const Shape& s) const
{
 const char* thisType = typeid(*this).name();
 const char* argType = typeid(s).name();
 bool done = false;
 int thisOrd = -1;
 int argOrd = -1;
 for (int i=0; !done; i++)
 {
 const char* tableEntry = typeOrderTable[i];
 if (tableEntry != 0)
 {
 if (strcmp(tableEntry, thisType) == 0)
 thisOrd = i;
 if (strcmp(tableEntry, argType) == 0)
 argOrd = i;
 if ((argOrd > 0) && (thisOrd > 0))
 done = true;
 }
 else // table entry == 0
 done = true;
 }
 return thisOrd < argOrd;
}

Çeşitli Şekillerin sırasına karşı kapalı olmayan tek öğe tablonun kendisidir. Ve bu tablo, diğer modüllerden ayrı olarak kendi modülüne yerleştirilebilir, böylece tabloda yapılan değişiklikler diğer modüllerin hiçbirini etkilemez.


Heuristics and Conventions
(Buluşsal Yöntemler ve Sözleşmeler)

Bu makalenin başında belirtildiği gibi, açık-kapalı prensibi, yıllar boyunca OOD ile ilgili olarak yayınlanan birçok buluşsal yöntem ve sözleşmenin arkasındaki temel motivasyondur. İşte bunların en önemlilerinden bazıları.

Make all Member Variables Private.
(Tüm Üye Değişkenlerini Private Yap.)

Bu, OOD'nin tüm sözleşmelerinde en yaygın olarak tutulanlardan biridir. Sınıfların üye değişkenleri, yalnızca sınıfın tanımlayan yöntemleri tarafından bilinmelidir. Üye değişkenler, türetilmiş sınıflar da dahil olmak üzere hiçbir zaman başka bir sınıf tarafından bilinmemelidir. Bu nedenle, public ya da protected yerine private ilan edilmelidir. Açık kapalı prensibi ışığında, bu sözleşmenin nedeni açık olmalıdır. Bir sınıfın üye değişkenleri değiştiğinde, bu değişkenlere bağlı her metod değiştirilmelidir. Böylece, bir değişkene bağlı hiçbir fonksiyon, bu değişkene göre kapatılamaz.

OOD'de, bir sınıfın metodlarının, o sınıfın üye değişkenlerindeki değişikliklere kapalı olmamasını bekliyoruz. Ancak, alt sınıflar da dahil olmak üzere diğer sınıfların bu değişkenlerdeki değişikliklere karşı kapalı olmasını bekliyoruz. Bu beklenti için bir ismimiz var, buna encapsulation (kapsülleme) diyoruz. Peki ya asla değişmeyeceğini bildiğiniz bir üye değişkeniniz olsaydı? Özel yapmak için herhangi bir neden var mı? Örneğin, Liste 7'de bool durum değişkeni olan bir sınıf Device gösterilmektedir. Bu değişken son işlemin durumunu içerir. Bu işlem başarılı olursa, durum true, aksi halde false olur.

Listing 7
non-const public variable
class Device
{
 public:
 bool status;
};

Bu değişkenin türü veya anlamının asla değişmeyeceğini biliyoruz. Öyleyse neden herkese açık hale getirmiyor ve istemci kodunun içeriğini incelemesine izin vermiyoruz? Bu değişken gerçekten hiç değişmezse ve diğer tüm istemciler kurallara uyarsa ve yalnızca durumun içeriğini sorgularsa, değişkenin herkese açık olması hiçbir zarar vermez. Bununla birlikte, bir istemci bile status değişkenin yazılabilir doğasından yararlanırsa ve değerini değiştirirse ne olacağını düşünün. Aniden, bu bir istemci diğer tüm istemcileri etkileyebilir. Bu, herhangi bir Device istemcisini bu hatalı davranış modülündeki değişikliklere karşı kapatmanın imkansız olduğu anlamına gelir. Bu muhtemelen alınamayacak kadar büyük bir risktir.


Öte yandan, Liste 8'de gösterildiği gibi Time sınıfına sahip olduğumuzu varsayalım. Bu sınıftaki public üye değişkenlerin verdiği zarar nedir? Elbette değişmeleri pek olası değildir. Ayrıca, istemci modüllerinden herhangi birinin değişkenlerde değişiklik yapması önemli değildir, değişkenlerin istemciler tarafından değiştirilmesi gerekir. Ayrıca, türetilmiş bir sınıfın belirli bir üye değişkenin ayarını değiştirmek istemesi pek olası değildir. Peki bu durumun herhangi bir zarar var mı?


Listing 8
class Time
{
 public:
 int hours, minutes, seconds;
 Time& operator-=(int seconds);
 Time& operator+=(int seconds);
 bool operator< (const Time&);
 bool operator> (const Time&);
 bool operator==(const Time&);
 bool operator!=(const Time&);
};

Liste 8 ile ilgili yapabileceğim bir şikayet, zamanın değiştirilmesinin atomik olmamasıdır. Yani, istemci hours değişkenini hours değişkenini değiştirmeden değiştirebilir. Bu bir Time nesnesi için tutarsız değerlere neden olabilir. Üç argüman alıp zamanı atomik olarak ayarlayan tek bir fonksiyonu tercih ederdim. Fakat bu çok zayıf bir argüman.


Bu değişkenlerin public doğasının bazı sorunlara neden olduğu diğer koşulları düşünmek zor olmayacaktır. Bununla birlikte, uzun vadede, bu değişkenleri private yapmak için baskın bir neden yoktur. Hala public hale getirmenin kötü bir tarzı olduğunu düşünüyorum, ancak muhtemelen kötü bir tasarım değil. Kötü tarz olarak görüyorum çünkü uygun satır içi üye moetdları oluşturmak çok ucuz ve ucuz maliyet kesinlikle kapama sorunlarının ortaya çıkması riskine karşı korumaya değer.

Bu nedenle, açık-kapalı prensibinin ihlal edilmediği nadir durumlarda, public ve protected değişkenlerin yasaklanması, sağlamlılıktan ziyade stile bağlıdır.

No Global Variables -- Ever.
(Hiçbir zaman için Global Değişken kullanılmamalı)

Global değişkenlere karşı argüman, public değişkenlere karşı olan argümana benzer. Global değişkene bağlı hiçbir modül, bu değişkene yazabilecek diğer modüllere karşı kapatılamaz. Değişkeni diğer modüllerin beklemediği şekilde kullanan herhangi bir modül, diğer modülleri kıracaktır. Birçok modülün kötü davranmış bir modülün kaprisine maruz kalması çok risklidir.

Diğer taraftan, global bir değişkenin çok az bağımlısı olduğu veya tutarsız bir şekilde kullanılamadığı durumlarda, diğer moduller çok az zarar verirler. Tasarımcı, bir global için ne kadar kapatmanın feda edildiğini değerlendirmeli ve global değişken tarafından sunulan kolaylığın maliyete değip değmediğini belirlemelidir.

Yine, ortaya çıkan stil sorunları var. Globalleri kullanmanın alternatifleri genellikle çok ucuzdur. Bu gibi durumlarda, böyle bir risk taşımayan yöntemlere karşı çok az miktarda kapanma riski taşıyan bir teknik kullanmak kötü bir stildir. Bununla birlikte, global bir rahatlığın önemli olduğu durumlar vardır. Global değişkenler için cout ve cin yaygın örneklerdir. Bu gibi durumlarda, açık-kapalı prensibi ihlal edilmezse, kolaylık stil ihlaline değebilir.


RTTI is Dangerous.
(RTTI tehlikelidir)

Bir diğer çok yaygın yasak, dynamic_cast'e karşıdır. Genellikle dynamic_cast veya herhangi bir run time türü tanımlamasının (RTTI) kendiliğinden tehlikeli olduğu ve bundan kaçınılması gerektiği iddia edilir. Genellikle atıfta bulunulan durum, açık-kapalı prensibini açıkça ihlal eden Liste 9'a benzer. Ancak Liste 10, dynamic_cast kullanan ancak açık-kapalı ilkesini ihlal etmeyen benzer bir programı gösterir.

Bu ikisi arasındaki fark, yeni bir Shape türü elde edildiğinde  Liste 9'un değiştirilmesi gerektiğidir. (Sadece düpedüz saçma olduğunu söylememe gerek yok). Ancak, yeni bir Shape alt sınıfı oluşturulduğunda Liste 10'da hiçbir şey değişmez. Bu nedenle, Liste 10 açık-kapalı prensibini ihlal etmez. Genel bir kural olarak, RTTI kullanımı açık-kapalı prensibini ihlal etmiyorsa, güvenlidir.

Listing 9
RTTI violating the open-closed principle.
class Shape {};
class Square : public Shape
{
 private:
 Point itsTopLeft;
 double itsSide;
 friend DrawSquare(Square*);
};
class Circle : public Shape
{
 private:
 Point itsCenter;
 double itsRadius;
 friend DrawCircle(Circle*);
};
void DrawAllShapes(Set& ss)
{
 for (Iteratori(ss); i; i++)
 {
 Circle* c = dynamic_cast(*i);
 Square* s = dynamic_cast(*i);
 if (c)
 DrawCircle(c);
 else if (s)
 DrawSquare(s);
 }
}
Listing 10
RTTI that does not violate the open-closed Principle.
class Shape
{
 public:
 virtual void Draw() cont = 0;
};
class Square : public Shape
{
 // as expected.
};
void DrawSquaresOnly(Set& ss)
{
 for (Iteratori(ss); i; i++)
 {
 Square* s = dynamic_cast(*i);
 if (s)
 s->Draw();
 }
}

SONUÇ

Açık-kapalı prensibi hakkında söylenebilecek çok şey var. Birçok yönden bu ilke nesne yönelimli tasarımın merkezindedir. Bu prensibe uygunluk, nesne yönelimli teknoloji için talep edilen en büyük faydaları mevcuttur; ör: tekrar kullanılabilirlik ve sürdürülebilirlik. Ancak bu ilkeye uyum basitçe nesne yönelimli bir programlama dili kullanılarak elde edilemez. Aksine, tasarımcının, değişikliğe tabi olacağını düşündüğü programlara soyutlama uygulaması için bir özveri gerekmektedir.








Bonus :




SOLID Prensipleri -The Single Responsibility Principle- Uncle Bob Çevirisi

Merhaba, bu seride sizlere Uncele Bob'un (Robert C. Martin) SOILD presnipleri için yazdığı makaleleri Türkçe'ye çevireceğim. Umarım yararlı bir yazı dizisi olur.

Seri'nin diğer yazıları :

Open Closed Principle

1 - SRP: The Single Responsibility Principle




"None but Buddha himself must take the responsibility of giving out occult secrets..." — E. Cobham Brewer 1810–1897. Dictionary of Phrase and Fable. 1898.

"Buda'nın kendisi dışında hiçbiri gizli sırlar verme sorumluluğunu almamalıdır ..."

Bu ilke Tom DeMarco ve Meilir Page-Jones çalışmalarında tanımlanmıştır. Bu çalışmalarada bu prensibe cohession denmiştir. Cohession ise bir modülün ilişkili elemanları arasında fonksiyonel ilişkinliği ifade etmektedir.

Bu bölümde, bu anlamı biraz değiştireceğiz ve uyumu bir modülün veya bir sınıfın değişmesine neden olan sebeplerle ilişkilendireceğiz.

A CLASS SHOULD HAVE ONLY ONE REASON TO CHANGE.
(Bir sınıf sadece bir sebepten dolayı değişmelidir.)

Bir Game sınıfının olduğunu ve bu sınıfın mevcut kareyi takip etmek ve skoru hesaplamak gibi iki ayrı sorumluluğu olduğunu düşünelim. Dikkat ederseniz sınıfın birbirinden farklı iki sorumluluğu mevcut. Bu durumada, bu iki sorumluluk iki sınıfa ayrılmalıdır. Game, kareleri takip etme sorumluluğunu sürdürürken, yeni oluşturulan Scorer sınıfı skoru hesaplama sorumluluğunu alır.

Bu iki sorumluluğu iki ayrı sınıfa ayırma nedenimiz nedir ? Çünkü her sorumluluk bir değişim eksenidir. Gereksinimler değiştiğinde, bu değişiklik sınıflar arasında sorumluluk değişikliği ile ortaya çıkacaktır. Bir sınıf birden fazla sorumluluk üstlenirse, değişmesi için birden fazla sebep olacaktır.

Bir sınıfın birden fazla sorumluluğu varsa, sorumluluklar coupled(çift hale gelmiş) olmuş demektir.
Bir sorumlulukta yapılan değişiklikler, sınıfın diğer sorumluklarını kullanma yeteneğini bozabilir veya engelleyebilir. Sorumluluklar arasında bu tür bağlantı, değişiklikte beklenmedik şekillerde kırılgan tasarımlara yol açar.

Örneğin, aşağıdaki tasarımı düşünün. Rectangle sınıfının gösterilen iki yöntemi vardır. Biri dikdörtgeni ekrana çizer, diğeri dikdörtgenin alanını hesaplar.


İki farklı uygulama Rectangle sınıfını kullanır. Bir uygulama hesaplamalı geometri yapar. Geometrik şekillerin matematiğinde yardımcı olması için Rectangle kullanır.Asla dikdörtgeni ekrana çizmez.Diğer uygulama doğası gereği grafikseldir. Ayrıca bazı hesaplama geometrisi yapabilir, ancak kesinlikle dikdörtgeni ekrana çizer.

Bu tasarım SRP'yi ihlal etemektedir. Rectangle sınıfının iki sorumluluğu vardır. İlk sorumluluk, bir dikdörtgenin geometrisinin matematiksel bir modelini sağlamaktır. İkinci sorumluluk, dikdörtgeni grafik kullanıcı arayüzünde oluşturmaktır.

SRP'nin ihlali birkaç kötü soruna neden olur. İlk olarak, GUI'yi hesaplamalı geometri uygulamasına dahil etmeliyiz. .NET'te GUI bağımlılığının derlemesinin hesaplamalı geometri uygulamasıyla oluşturulması ve dağıtılması gerekir.

İkinci olarak, GraphicalApplication üzerinde yapılan bir değişiklik Rectangle'ın bir nedenle değişmesine neden olursa, bu değişiklik bizi ComputationalGeometryApplication uygulamasını yeniden oluşturmaya, yeniden test etmeye ve yeniden konuşlandırmaya zorlayabilir. Bunu yapmayı unutursak, bu uygulama öngörülemeyen şekillerde bozulabilir.

Daha iyi bir tasarım, iki sorumluluğu aşağıda gösterildiği gibi tamamen farklı iki sınıfa ayırmaktır. Bu tasarım, Dikdörtgenin hesaplama bölümlerini GeometricRectangle sınıfına taşır. Şimdi dikdörtgenlerin oluşturulma biçiminde yapılan değişiklikler ComputationalGeometryApplication'ı etkileyemez.



What is a Responsibility?
(Sorumluluk nedir?)

Tek Sorumluluk İlkesi (SRP) bağlamında bir sorumluluğu “değişim nedeni” olarak tanımlıyoruz. Bir sınıfı değiştirmek için birden fazla nedeni düşünebiliyorsanız, o sınıfın birden fazla sorumluluğu vardır. Bunu görmek bazen zor olabilir. Sorumlulukları gruplar halinde düşünmeye alışkınız. Örneğin, aşağıdaki Modem arayüzünü düşünün. Çoğumuz bu arayüzün son derece makul göründüğünü kabul edeceğiz. Bildirdiği dört işlev kesinlikle modeme ait işlevlerdir.



Modem.cs -- SRP Violation

public interface Modem
{
   public void Dial(string pno);
   public void Hangup();
   public void Send(char c);
   public char Recv();
}


Ancak, burada gösterilen iki sorumluluk vardır. İlk sorumluluk bağlantı yönetimidir. İkincisi veri iletişimidir. Dial ve Hangup işlevleri modem bağlantısını yönetirken, Send ve Recv işlevleri veri iletir.

Bu iki sorumluluk birbirinden ayrılmalı mı? Bu uygulamanın nasıl değiştiğine bağlıdır. Uygulama, bağlantı işlevlerinin imzasını etkileyecek şekilde değişirse, tasarım Rigidity(Sertlik) kokusu alacaktır, çünkü gönderme ve okuma çağrısı yapan sınıfların bizim istediğimizden daha sık derlenmesi ve yeniden konuşlandırılması gerekecektir. Bu durumda iki sorumluluk aşağıda gösterildiği gibi ayrılmalıdır. Bu, istemci uygulamalarının iki sorumluluğu birleştirmesini önler.



Öte yandan, uygulama iki sorumluluğun farklı zamanlarda değişmesine neden olacak şekilde değişmiyorsa, bunları ayırmaya gerek yoktur. Gerçekten de onları ayırmak Needless Complexity(Gereksiz Karmaşıklık) kokusu alacaktı.

Burada bir korozyon var. Bir değişim ekseni, yalnızca değişiklikler gerçekten meydana gelirse bir değişim eksenidir. Herhangi bir belirti yoksa, SRP'yi veya bu konuda başka bir ilkeyi uygulamak akıllıca değildir.

Separating coupled responsibilities.
(Birleştirilmiş sorumlulukların ayrılması.)

Şekil 8-3'te ModemImplementation sınıfında her iki sorumluluğu da yerine getirdiğime dikkat edin. Bu arzu edilmez, ancak gerekli olabilir. Donanım veya işletim sisteminin ayrıntılarıyla ilgili olmak zorunda olduğumuz ve bizi çift olmayı tercih etmeyeceğimiz şeyleri birleştirmeye zorlayan çoğu zaman sebepler vardır. Bununla birlikte, arayüzlerini ayırarak, uygulamanın geri kalanıyla ilgili olarak kavramları birbirinden ayırdık.

ModemImplementation sınıfını bir çamur veya siğil olarak görebiliriz; ancak, tüm bağımlılıkların bundan uzaklaştığına dikkat edin. Kimsenin bu sınıfa ihtiyacı yoktur. Ana ihtiyaçlar dışında hiç kimse bunun var olduğunu bilmeye ihtiyaç duymaz. Böylece, çirkin parçayı bir çitin arkasına koyduk. Çirkinliğin dışarı sızmasına ve uygulamanın geri kalanını kirletmesine gerek yoktur. (Sınıf yerine Interface bağımlılığından bahsediliyor)

SONUÇ
SRP, ilkelerin en basitlerinden ve doğru yapılması en zorlarından biridir. Birbirine karşı sorumluluklar, doğal olarak yaptığımız bir şeydir. Bu sorumlulukları bulmak ve birbirinden ayırmak, yazılım tasarımının gerçekte ne olduğudur. Gerçekten de tartışacağımız ilkelerin geri kalanı bu ilkeye şu ya da bu şekilde geri dönüyor.


Bibliography [DeMarco79]: Structured Analysis and System Specification, Tom DeMarco, Yourdon Press Computing Series, 1979

[PageJones88]: The Practical Guide to Structured Systems Design, 2d. ed., Meilir PageJones, Yourdon Press Computing Series, 1988

Yazının Orjinali

Bonus :




Bonus :







Rastgele İçerik

© tüm hakları saklıdır
made with by templateszoo