Önceki Parçalar: Temiz Kod: TypeScript'teki İşlevler ve Yöntemler [Bölüm 1] Temiz Kod: TypeScript'te Adlandırma ve Kod Bileşimi [Bölüm 2] Temiz Kod: TypeScript'te Sınıflar ve Nesneler [Bölüm 3] 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: Tek Sorumluluk Prensibi Açık/Kapalı Prensibi 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 sınıfındadır, yöntemde 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 sınıfını değiştirmeliyiz ve bu güvenli değildir; beklenmedik sonuçlar elde edebiliriz. HttpRequestCost getDeliveryCost HttpRequestCost Bunu önlemek için, sınıfında gerçekleştirmeler olmadan soyut bir yöntem oluşturmalıyız. Özel farkındalık şu sınıfları miras alacaktır: Ananas ve Muz. İsteği kendileri gerçekleştirecekler. Product request sınıfı arayüzünü takip ederek parametresini alacak ve belirli bağımlılıkları aktardığımızda zaten kendisi için yöntemini gerçekleştirecektir. HttpRequestCost Product product HttpRequestCost request // 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 sınıfıyla ilgili bir sorunumuz var. , ve tümü Workers'tır ve ü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. yöntemini geçersiz kıldık ve Liskov Değiştirme İlkesini çiğnedik. Contractor Designer Programmer Seller Worker access Bu prensip bize, üst sınıfını alt sınıfıyla (örneğin sınıfı) değiştirirsek işlevselliğin bozulmaması gerektiğini söyler. Ancak bunu yaparsak, sınıfının işlevselliği bozulacaktır; yöntemi, sınıfından beklenmeyen gerçekleşmelere sahip olacaktır. Worker Designer Programmer access Designer 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 {/../} } ve için yeni soyutlama katmanları oluşturduk ve yöntemini sınıfına taşıdık ve belirli gerçekleştirmeyi tanımladık. sınıfını alt sınıfıyla değiştirirsek işlevselliği bozulmayacaktır. Employee Contractor access Employee Worker Contractor Worker 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.