paint-brush
Temiz Kod: Tek Sorumluluk, Açık/Kapalı, TS'de Liskov Değiştirme SOLID İlkeleri [Bölüm 4]ile@alenaananich
4,777 okumalar
4,777 okumalar

Temiz Kod: Tek Sorumluluk, Açık/Kapalı, TS'de Liskov Değiştirme SOLID İlkeleri [Bölüm 4]

ile Alena Ananich7m2023/11/09
Read on Terminal Reader

Çok uzun; Okumak

Bu makalede ilk üç SOLID ilkesine göz atacağız: Tek Sorumluluk İlkesi, Açık/Kapalı İlkesi ve pratik örneklerle Liskov İkame İlkesi.
featured image - Temiz Kod: Tek Sorumluluk, Açık/Kapalı, TS'de Liskov Değiştirme SOLID İlkeleri [Bölüm 4]
Alena Ananich HackerNoon profile picture


Önceki Parçalar:


Kodumuzu temiz, esnek ve sürdürülebilir hale getirmeye yönelik yaklaşımları değerlendirmeye devam ediyoruz. Şimdi temiz mimari çözümleri araştırmaya başlayalım.


SOLID ilkeleri Robert C. Martin tarafından ortaya atılmıştır ve nesne yönelimli programlama ve yazılım tasarımında en iyi uygulamalar olarak kabul edilmektedir.


Bu makalede, ilk üç SOLID ilkesine bir göz atacağız: Tek Sorumluluk İlkesi, Açık/Kapalı İlkesi ve Liskov İkame İlkesi'ni pratik örneklerle.

İçindekiler:

  1. Tek Sorumluluk Prensibi
  2. Açık/Kapalı Prensibi
  3. Liskov Değiştirme Prensibi

1. Tek Sorumluluk İlkesi (SRP)

Bir sınıfın değişmek için tek bir nedeni olmalıdır


Başka bir deyişle, bir sınıfın tek bir sorumluluğu veya işi olmalıdır. Bu önemlidir çünkü tek sorumluluğa sahip bir sınıfın anlaşılması, değiştirilmesi ve bakımı daha kolaydır. Kodun bir alanındaki değişiklikler tüm sisteme yayılmayacak ve hata oluşma riskini azaltacaktır.


Pratik bir örneğe ve tek sorumluluk ilkesini takip etmenin yollarına bir göz atalım:

 // Bad class UserSettingsService { constructor(user: IUser) { this.user = user; } changeSettings(settings: IUserSettings): void { if (this.isUserValidated()) { // ... } } getUserInfo(): Promise<IUserSettings> { // ... } async isUserValidated(): Promise<boolean> { const userInfo = await this.getUserInfo(); // ... } }

Bu örnekte sınıfımız farklı yönlerde eylemler gerçekleştiriyor: bağlamı kuruyor, değiştiriyor ve doğruluyor.


SRP'yi takip etmek için bu farklı sorumlulukları bölmemiz gerekir.

 // Better class UserAuth { constructor(user: IUser) { this.user = user; } getUserInfo(): Promise<IUserSettings> { // ... } async isUserValidated(): boolean { const userInfo = await this.getUserInfo(); // ... } } class UserSettings { constructor(user: IUser) { this.user = user; this.auth = new UserAuth(user); } changeSettings(settings: IUserSettings): void { if (this.auth.isUserValidated()) { // ... } } }


Neden Önemlidir?

SRP, kod modülerliğini geliştirmeyi ve karşılıklı bağımlılıklardan kaynaklanan zorlukları en aza indirmeyi amaçlamaktadır. Kodu işlevler, ayrılmış sınıflar halinde düzenleyerek ve modülerliği teşvik ederek, daha yeniden kullanılabilir hale gelir ve aksi takdirde mevcut işlevselliği yeniden kodlamak için harcanabilecek zamandan tasarruf sağlar.

2. Açık/Kapalı Prensibi (OCP)

Yazılım varlıkları (sınıflar, modüller, işlevler) genişletmeye açık, değişiklik yapmaya kapalı olmalıdır


Mevcut kodu değiştirmeden yeni işlevler ekleyebilmelisiniz. Bu önemlidir çünkü bu prensibe bağlı kalarak, mevcut kodun kararlılığını riske atmadan sisteminize yeni özellikler veya bileşenler katabilirsiniz.


Kodun yeniden kullanılabilirliğini artırır ve işlevselliği genişletirken kapsamlı değişiklik ihtiyacını azaltır.

 // Bad class Product { id: number; name: string[]; price: number; protected constructor(id: number, name: string[], price: number) { this.id = id; this.name = name; this.price = price; } } class Ananas extends Product { constructor(id: number, name: string[], price: number) { super(id, name, price); } } class Banana extends Product { constructor(id: number, name: string[], price: string) { super(id, name, price); } } class HttpRequestCost { constructor(product: Product) { this.product = product; } getDeliveryCost(): number { if (product instanceOf Ananas) { return requestAnanas(url).then(...); } if (product instanceOf Banana) { return requestBanana(url).then(...); } } } function requestAnanas(url: string): Promise<ICost> { // logic for ananas } function requestBanana(url: string): Promise<ICost> { // logic for bananas }

Bu örnekte sorun HttpRequestCost sınıfındadır, yöntemde getDeliveryCost farklı ürün türlerinin hesaplanmasına yönelik koşullar içerir ve her ürün türü için ayrı yöntemler kullanırız. Dolayısıyla, yeni bir ürün türü eklememiz gerekiyorsa HttpRequestCost sınıfını değiştirmeliyiz ve bu güvenli değildir; beklenmedik sonuçlar elde edebiliriz.


Bunu önlemek için, Product sınıfında gerçekleştirmeler olmadan soyut bir yöntem request oluşturmalıyız. Özel farkındalık şu sınıfları miras alacaktır: Ananas ve Muz. İsteği kendileri gerçekleştirecekler.


HttpRequestCost Product sınıfı arayüzünü takip ederek product parametresini alacak ve HttpRequestCost belirli bağımlılıkları aktardığımızda zaten kendisi için request yöntemini gerçekleştirecektir.

 // Better abstract class Product { id: number; name: string[]; price: string; constructor(id: number, name: string[], price: string) { this.id = id; this.name = name; this.price = price; } abstract request(url: string): void; } class Ananas extends Product { constructor(id: number, name: string[], price: string) { super(id, name, price); } request(url: string): void { // logic for ananas } } class Banana extends Product { constructor(id: number, name: string[], price: string) { super(id, name, price); } request(url: string): void { // logic for bananas } } class HttpRequestCost { constructor(product: Product) { this.product = product; } request(): Promise<void> { return this.product.request(url).then(...); } }


Neden Önemlidir?

Bu prensibi takip ederek kod eşleşmesini azaltacak ve sistemi öngörülemeyen davranışlardan kurtaracaksınız.

3. Liskov İkame Prensibi (LSP)

Bir üst sınıfın nesneleri, uygulamayı bozmadan alt sınıflarının nesneleri ile değiştirilebilir olmalıdır.


Bu prensibi anlamak için şu örneğe bir göz atalım:

 // Bad class Worker { work(): void {/../} access(): void { console.log('Have an access to closed perimeter'); } } class Programmer extends Worker { createDatabase(): void {/../} } class Seller extends Worker { sale(): void {/../} } class Designer extends Worker { access(): void { throwError('No access'); } }

Bu örnekte Contractor sınıfıyla ilgili bir sorunumuz var. Designer , Programmer ve Seller tümü Workers'tır ve Worker üst sınıfından miras alırlar. Ancak aynı zamanda Tasarımcılar Çalışan değil Yüklenici oldukları için kapalı çevreye erişimleri yoktur. access yöntemini geçersiz kıldık ve Liskov Değiştirme İlkesini çiğnedik.


Bu prensip bize, Worker üst sınıfını alt sınıfıyla (örneğin Designer sınıfı) değiştirirsek işlevselliğin bozulmaması gerektiğini söyler. Ancak bunu yaparsak, Programmer sınıfının işlevselliği bozulacaktır; access yöntemi, Designer sınıfından beklenmeyen gerçekleşmelere sahip olacaktır.


Liskov İkame Prensibini takip ederek, alt sınıflardaki gerçekleşmeleri yeniden yazmamalıyız, ancak her bir soyutlama türü için belirli gerçekleşmeleri tanımladığımız yeni soyutlama katmanları yaratmamız gerekir.


Düzeltelim:

 // Better class Worker { work(): void {/../} } class Employee extends Worker { access(): void { console.log('Have an access to closed perimeter'); } } class Contractor extends Worker { addNewContract(): void {/../} } class Programmer extends Employee { createDatabase(): void {/../} } class Saler extends Employee { sale(): void {/../} } class Designer extends Contractor { makeDesign(): void {/../} }

Employee ve Contractor için yeni soyutlama katmanları oluşturduk ve access yöntemini Employee sınıfına taşıdık ve belirli gerçekleştirmeyi tanımladık. Worker sınıfını Contractor alt sınıfıyla değiştirirsek Worker işlevselliği bozulmayacaktır.

Neden Önemlidir?

LSP'ye bağlı kalırsanız, temel sınıf örneğinin beklendiği yerde herhangi bir alt sınıf örneğini kullanabilirsiniz; program yine de amaçlandığı gibi çalışmaya devam etmelidir. Bu, kodun yeniden kullanımını ve modülerliğini destekler ve kodunuzu değişikliklere karşı daha dayanıklı hale getirir.


Bir sonraki yazımızda Arayüz Ayrımı ve Bağımlılığı Ters Çevirme SOLID prensiplerine göz atacağız.