Software Design Principles (Part 3)

Firuze Gümüş
12 min readApr 29, 2024

--

Herkese merhaba, bu yazı 4 bölümlük bir serinin 3. makalesidir. İlk 2 makale için sizi şöyle alalım:

Bu yazıda SOLID Design Prensiplerini inceliyor olacağız.

SOLID prensipleri, yazılım tasarımlarını daha anlaşılır, esnek ve sürdürülebilir hale getirmek amacıyla oluşturulmuş beş tasarım ilkesinden oluşur. Robert C. Martin (Uncle Bob) tarafından popüler hale getirilen SOLID prensiplerinin orijinal tanımları kısaca aşağıdaki gibidir:

Single Responsibility Principle — SRP (Tek Sorumluluk Prensibi) : A class should have only one reason to change.

Bir sınıfın değişmesi için yalnızca bir nedeni olmalıdır.

Open/Closed Principle — OCP (Açık/Kapalı Prensibi) : Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.

Yazılım bileşenleri (sınıflar, modüller, fonksiyonlar, vb.) genişlemeye açık, ancak değişikliğe kapalı olmalıdır.

Liskov Substitution Principle — LSP (Liskov Yerine Geçme Prensibi) : Subtypes must be substitutable for their base types without altering the correct behavior of the program.

Alt türler, programın doğru davranışını değiştirmeden base türlerin yerine geçebilmelidir.

Interface Segregation Principle — ISP (Arayüz Ayrımı Prensibi) : Clients should not be forced to depend on interfaces they do not use.

Clientlar, kullanmadıkları interfacelere bağımlı olmaya zorlanmamalıdır.

Dependency Inversion Principle — DIP (Bağımlılık Tersine Çevirme Prensibi) : High-level modules should not depend on low-level modules; both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions.

Üst düzey modüller alt düzey modüllere bağlı olmamalıdır; her ikisi de soyutlamalara bağımlı olmalıdır. Soyutlamalar detaylara bağlı olmamalı; detaylar soyutlamalara bağlı olmalıdır.

O halde ilk prensibimiz olan Single Responsibility ile başlayalım.

1. Single Responsibility Principle

Bu prensip, bir sınıfın sadece bir tür işlevsellikten veya programın tek bir yönünden sorumlu olması gerektiğini belirtir. Tek tür sorumluluğa odaklanarak, kodda yapılan değişikliklerin beklenmedik sonuçlara yol açma olasılığı azalır ve kodun anlaşılması ile bakımı daha kolay hale gelir.

Daha net anlaşılması adına örneklerle anlatalım.

class Game {
fun login(){}
fun signup(){}
fun move(){}
fun fire(){}
fun rest(){}
fun getHighScore(){}
fun getName(){}
}

Yukarıdaki gibi bir Game classımız var. Öncelikle içerisindeki functionların işlevleri nelermiş bir bakalım:

  • login ve signup fonksiyonları, kullanıcı kimlik doğrulama ve yetkilendirme ile ilgili.
  • move, fire ve rest fonksiyonları, oyun içi hareketler ve eylemler ile ilgili.
  • getHighScore ve getName ise oyuncuya ait bazı bilgilerden oluşuyor.

Bu farklı sorumluluklar, sınıfın karmaşık ve yönetilemez hale gelmesine yol açabilir. Ayrıca, herhangi bir değişiklik yapıldığında, sınıfın farklı kısımlarını etkileyebilir ve böylece hata olasılığını artırır. Dolayısıyla bu sınıfı Tek Sorumluluk İlkesi’ne uygun hale getirmek için sorumlulukları ayırmamız gerekiyor.


class GameSession{
fun login(){}
fun signup(){}
}

class Player{
fun getHighScore(){}
fun getName(){}
}

class PlayerActions{
fun move(){}
fun fire(){}
fun rest(){}
}
  • GameSession: login ve signup gibi kimlik doğrulama ve yetkilendirme işlemleri için.
  • PlayerActions: move, fire ve rest oyun içi eylemler için.
  • Player: getHighScore, getName oyuncuya ait bilgileri tutmak için.

Bir başka örnek vermek gerekirse:

class PersonClass {
fun setName(){}
fun setAddress(){}
fun setPhoneNumber(){}
fun save(){}
fun load(){}
}
  • Person: Bu sınıf sadece bir kişinin bilgilerini temsil eder ve setName(), setAddress(), setPhoneNumber sadece veri ile ilgili metotları içerir.
  • PersonRepository: Bu sınıf save() ve load() veri kaydetme ve yükleme işlemlerini gerçekleştirir. Veri tabanı, dosya sistemi veya başka bir saklama mekanizması ile etkileşim bu sınıfın sorumluluğundadır.

class Person {
fun setName(){}
fun setAddress(){}
fun setPhoneNumber(){}
}

class PersonRepository{
fun save(){}
fun load(){}
}

Bir başka örnek:

class ShoppingCartClass{
fun add(){}
fun remove(){}
fun checkOut(){}
fun saveForLater(){}
}
  • add() ve remove(): Alışveriş sepetine ürün eklemek veya çıkarmak gibi sepet yönetimiyle ilgili.
  • checkOut(): Satın alma işlemi ve ödeme süreciyle ilgili.
  • saveForLater(): Ürünleri daha sonra kullanmak üzere kaydetmek gibi farklı bir işlevi içeriyor.

Aynı şekilde bu sınıfı Tek Sorumluluk İlkesi’ne uygun hale getirmek için, farklı sorumlulukları aşağıda gibi ayırabiliriz:

class ShoppingCart{
fun add(){}
fun remove(){}
}

class CheckoutProcess{
fun checkOut(){}
}

class Wishlist{
fun saveForLater(){}
}
  • ShoppingCart: add() ve remove() temel alışveriş sepeti yönetimi işlevlerini içerir.
  • CheckoutProcess: checkout() ödeme ve satın alma sürecini yönetir.
  • Wishlist: saveForLater() ürünleri daha sonra kullanmak için kaydetme işlevini içerir

Burada dikkat etmemiz gereken konu Single Responsibility’nin bir classta sadece bir işi yapmaktan bahsetmediğidir. Bir classın yaptığı birden fazla işin birbiriyle sıkı ilişki içinde olması yani cohesion’ın yüksek olması gerektiğidir. Cohesion’dan Part 2'de detaylı bir şekilde bahsetmiştik:

2. Open Closed Principle

Bu prensip, bir modülün veya sınıfın davranışının, kaynak kodu değiştirmeden genişletilebilir olması gerektiğini vurgular. Bu genellikle polymorphism, abstraction veya interface tabanlı tasarım ile sağlanabilir, böylece yeni işlevsellik eklenirken mevcut kod değiştirilmez.

OCP için çok güzel bir örnek olacağını düşündüğüm Decorator Pattern’i kullanarak bir senaryo oluşturalım.

Sade kahve satışı gerçekleştiren bir Kafe düşünelim.

interface Coffee {
fun cost(): Double
fun description(): String
}

class BasicCoffee : Coffee {
override fun cost() = 5.0
override fun description() = "Sade Kahve"
}


fun main() {
val coffeeOrderManager = CoffeeOrderManager()
coffeeOrderManager.addDecorator(BasicCoffee())
println("Fiyat: ${coffeeOrderManager.getTotalCost()}, Açıklama: ${coffeeOrderManager.getDescription()}")
}

Diyelim ki müşterilerimize kahvelerine süt, çikolata, krema, şeker ekleme seçenekleri sunmaya karar veriyoruz. Bunların da ekstra ücretleri olsun.

interface Coffee {
fun cost(): Double
fun description(): String
}

class BasicCoffee : Coffee {
override fun cost() = 5.0
override fun description() = "Sade Kahve"
}

abstract class CoffeeDecorator(private val coffee: Coffee) : Coffee {
override fun cost() = coffee.cost()
override fun description() = coffee.description()
}

class MilkDecorator(coffee: Coffee) : CoffeeDecorator(coffee) {
override fun cost() = super.cost() + 3.0
override fun description() = super.description() + ", Süt"
}

class SugarDecorator(coffee: Coffee) : CoffeeDecorator(coffee) {
override fun cost() = super.cost() + 0.5
override fun description() = super.description() + ", Şeker"
}

class ChocolateDecorator(coffee: Coffee) : CoffeeDecorator(coffee) {
override fun cost() = super.cost() + 2.5
override fun description() = super.description() + ", Çikolata"
}

class CoffeeOrderManager {
private var coffee: Coffee = BasicCoffee()

fun addDecorator(decorator: CoffeeDecorator) {
coffee = decorator
}

fun getTotalCost(): Double {
return coffee.cost()
}

fun getDescription(): String {
return coffee.description()
}

}

fun main() {
val coffeeOrderManager = CoffeeOrderManager()
val milkCoffee = MilkDecorator(BasicCoffee())
coffeeOrderManager.addDecorator(milkCoffee)
println("Fiyat: ${coffeeOrderManager.getTotalCost()}, Açıklama: ${coffeeOrderManager.getDescription()}")
val sugarCoffee = SugarDecorator(BasicCoffee())
coffeeOrderManager.addDecorator(sugarCoffee)
println("Fiyat: ${coffeeOrderManager.getTotalCost()}, Açıklama: ${coffeeOrderManager.getDescription()}")

val sugarMilkCoffee = SugarDecorator(milkCoffee)
coffeeOrderManager.addDecorator(sugarMilkCoffee)
println("Fiyat: ${coffeeOrderManager.getTotalCost()}, Açıklama: ${coffeeOrderManager.getDescription()}")

val chocolateDecorator = ChocolateDecorator(BasicCoffee())
coffeeOrderManager.addDecorator(chocolateDecorator)
println("Fiyat: ${coffeeOrderManager.getTotalCost()}, Açıklama: ${coffeeOrderManager.getDescription()}")

val chocolateMilkDecorator = ChocolateDecorator(sugarMilkCoffee)
coffeeOrderManager.addDecorator(chocolateMilkDecorator)
println("Fiyat: ${coffeeOrderManager.getTotalCost()}, Açıklama: ${coffeeOrderManager.getDescription()}")

}

Kodumuzun çıktısı şu şekilde olacaktır:

Fiyat: 8.0, Açıklama: Sade Kahve, Süt
Fiyat: 5.5, Açıklama: Sade Kahve, Şeker
Fiyat: 8.5, Açıklama: Sade Kahve, Süt, Şeker
Fiyat: 7.5, Açıklama: Sade Kahve, Çikolata
Fiyat: 11.0, Açıklama: Sade Kahve, Süt, Şeker, Çikolata

Gördüğümüz gibi istediğimiz ek ürünü kahvemize dahil edebiliyoruz. Yeni bir ek ürün eklemek istediğimizde tek yapmamız gereken ilgili ürün için bir decorator eklemek. Hepsi bu!

Hadi Crema ekleyelim o zaman. Tek yapmamız gereken CreamDecorator eklemek..

interface Coffee {
fun cost(): Double
fun description(): String
}

class BasicCoffee : Coffee {
override fun cost() = 5.0
override fun description() = "Sade Kahve"
}

abstract class CoffeeDecorator(private val coffee: Coffee) : Coffee {
override fun cost() = coffee.cost()
override fun description() = coffee.description()
}

class MilkDecorator(coffee: Coffee) : CoffeeDecorator(coffee) {
override fun cost() = super.cost() + 3.0
override fun description() = super.description() + ", Süt"
}

class SugarDecorator(coffee: Coffee) : CoffeeDecorator(coffee) {
override fun cost() = super.cost() + 0.5
override fun description() = super.description() + ", Şeker"
}

class ChocolateDecorator(coffee: Coffee) : CoffeeDecorator(coffee) {
override fun cost() = super.cost() + 2.5
override fun description() = super.description() + ", Çikolata"
}

class CreamDecorator(coffee: Coffee) : CoffeeDecorator(coffee) {
override fun cost() = super.cost() + 1.5
override fun description() = super.description() + ", Krema"
}

class CoffeeOrderManager {
private var coffee: Coffee = BasicCoffee()

fun addDecorator(decorator: CoffeeDecorator) {
coffee = decorator
}

fun getTotalCost(): Double {
return coffee.cost()
}

fun getDescription(): String {
return coffee.description()
}

}

fun main() {
val coffeeOrderManager = CoffeeOrderManager()
val milkCoffee = MilkDecorator(BasicCoffee())
coffeeOrderManager.addDecorator(milkCoffee)
println("Fiyat: ${coffeeOrderManager.getTotalCost()}, Açıklama: ${coffeeOrderManager.getDescription()}")
val sugarCoffee = SugarDecorator(BasicCoffee())
coffeeOrderManager.addDecorator(sugarCoffee)
println("Fiyat: ${coffeeOrderManager.getTotalCost()}, Açıklama: ${coffeeOrderManager.getDescription()}")

val sugarMilkCoffee = SugarDecorator(milkCoffee)
coffeeOrderManager.addDecorator(sugarMilkCoffee)
println("Fiyat: ${coffeeOrderManager.getTotalCost()}, Açıklama: ${coffeeOrderManager.getDescription()}")

val chocolateDecorator = ChocolateDecorator(BasicCoffee())
coffeeOrderManager.addDecorator(chocolateDecorator)
println("Fiyat: ${coffeeOrderManager.getTotalCost()}, Açıklama: ${coffeeOrderManager.getDescription()}")

val chocolateMilkDecorator = ChocolateDecorator(sugarMilkCoffee)
coffeeOrderManager.addDecorator(chocolateMilkDecorator)
println("Fiyat: ${coffeeOrderManager.getTotalCost()}, Açıklama: ${coffeeOrderManager.getDescription()}")

val creamDecorator = CreamDecorator(chocolateDecorator)
coffeeOrderManager.addDecorator(creamDecorator)
println("Fiyat: ${coffeeOrderManager.getTotalCost()}, Açıklama: ${coffeeOrderManager.getDescription()}")

}

Yeni çıktımız aşağıdaki şekilde olacak:

Fiyat: 8.0, Açıklama: Sade Kahve, Süt
Fiyat: 5.5, Açıklama: Sade Kahve, Şeker
Fiyat: 8.5, Açıklama: Sade Kahve, Süt, Şeker
Fiyat: 7.5, Açıklama: Sade Kahve, Çikolata
Fiyat: 11.0, Açıklama: Sade Kahve, Süt, Şeker, Çikolata
Fiyat: 9.0, Açıklama: Sade Kahve, Çikolata, Krema

OCP’ye bir diğer örnek olarak Abstract Factory Design Pattern kullanımını verebiliriz.

Diyelim bir araç galerisi elindeki araçların çeşitli özelliklerine göre kullanıcıların özel sipariş oluşturabileceği bir uygulama geliştirilmesini istiyor.

Öncelikle galerimiz Tesla satışı yapıyor olsun. Kodumuzu şu şekilde yazalım:

abstract class Car {
abstract fun assemble()
abstract fun paint()
abstract fun test()
}

class TeslaModelS : Car() {
override fun assemble() = println("Assembling Tesla Model S")
override fun paint() = println("Painting Tesla Model S")
override fun test() = println("Testing Tesla Model S")
}

class TeslaModel3 : Car() {
override fun assemble() = println("Assembling Tesla Model 3")
override fun paint() = println("Painting Tesla Model 3")
override fun test() = println("Testing Tesla Model 3")
}

class TeslaModelX : Car() {
override fun assemble() = println("Assembling Tesla Model X")
override fun paint() = println("Painting Tesla Model X")
override fun test() = println("Testing Tesla Model X")
}

class TeslaModelY : Car() {
override fun assemble() = println("Assembling Tesla Model Y")
override fun paint() = println("Painting Tesla Model Y")
override fun test() = println("Testing Tesla Model Y")
}

class DependentCarFactory {
fun createCar(model: String): Car? {
return when (model) {
"Model S" -> TeslaModelS()
"Model 3" -> TeslaModel3()
"Model X" -> TeslaModelX()
"Model Y" -> TeslaModelY()
else -> {
println("Error: Invalid Tesla model")
null
}
}
}
}

fun main() {
val factory = DependentCarFactory()

val car1 = factory.createCar("Model S")
car1?.apply {
assemble()
paint()
test()
}

val car2 = factory.createCar("Model 3")
car2?.apply {
assemble()
paint()
test()
}
}

Daha sonra galerimiz TOGG satışına da başlayacağını bildirdi ve bizden uygulamamızı güncellememizi istedi. Kodumuzun yeni hali şu şekilde oldu:

abstract class Car {
abstract fun assemble()
abstract fun paint()
abstract fun test()
}

class ToggT10X : Car() {
override fun assemble() = println("Assembling Togg T10X")
override fun paint() = println("Painting Togg T10X")
override fun test() = println("Testing Togg T10X")
}

class ToggT10F : Car() {
override fun assemble() = println("Assembling Togg T10F")
override fun paint() = println("Painting Togg T10F")
override fun test() = println("Testing Togg T10F")
}

class TeslaModelS : Car() {
override fun assemble() = println("Assembling Tesla Model S")
override fun paint() = println("Painting Tesla Model S")
override fun test() = println("Testing Tesla Model S")
}

class TeslaModel3 : Car() {
override fun assemble() = println("Assembling Tesla Model 3")
override fun paint() = println("Painting Tesla Model 3")
override fun test() = println("Testing Tesla Model 3")
}

class DependentCarFactory {
fun createCar(brand: String, model: String): Car? {
val car = when (brand) {
"Togg" -> {
when (model) {
"T10X" -> ToggT10X()
"T10F" -> ToggT10F()
else -> {
println("Error: Invalid Togg model")
null
}
}
}
"Tesla" -> {
when (model) {
"Model S" -> TeslaModelS()
"Model 3" -> TeslaModel3()
else -> {
println("Error: Invalid Tesla model")
null
}
}
}
else -> {
println("Error: Invalid brand")
return null
}
}

car?.apply {
assemble()
paint()
test()
}

return car
}
}

fun main() {
val factory = DependentCarFactory()

val toggCar = factory.createCar("Togg", "T10X")
toggCar?.apply {
assemble()
paint()
test()
}

val teslaCar = factory.createCar("Tesla", "Model S")
teslaCar?.apply {
assemble()
paint()
test()
}
}

Ancak galerimiz her yeni markayı dahil ettiğinde kodun sürekli değiştirildiğini, daha da karmaşık bir hal aldığını ve bunun bazı hataları da beraberinde getirdiğini farkettik. Aynı zamanda kodumuz OCP’yi de ihlal ediyor olduğundan Abstract Factory Design Pattern kullanarak kodumuzu aşağıdaki gibi yeniden tasarlamaya karar verdik. Öncelikle farklı olanları bir ayıralım. Tesla kendi modellerini tutsun, TOGG kendi modellerini..

abstract class Car {
abstract fun assemble()
abstract fun paint()
abstract fun test()

abstract fun chooseWheels(wheelType: String)
abstract fun chooseSeats(seatType: String)
abstract fun choosePaintColor(color: String)
}

class ToggT10X : Car() {
override fun assemble() = println("Assembling Togg T10X")
override fun paint() = println("Painting Togg T10X")
override fun test() = println("Testing Togg T10X")

override fun chooseWheels(wheelType: String) = println("Choosing wheels for Togg T10X: $wheelType")
override fun chooseSeats(seatType: String) = println("Choosing seats for Togg T10X: $seatType")
override fun choosePaintColor(color: String) = println("Choosing paint color for Togg T10X: $color")
}

class ToggT10F : Car() {
override fun assemble() = println("Assembling Togg T10F")
override fun paint() = println("Painting Togg T10F")
override fun test() = println("Testing Togg T10F")

override fun chooseWheels(wheelType: String) = println("Choosing wheels for Togg T10F: $wheelType")
override fun chooseSeats(seatType: String) = println("Choosing seats for Togg T10F: $seatType")
override fun choosePaintColor(color: String) = println("Choosing paint color for Togg T10F: $color")
}

class TeslaModelS : Car {
override fun assemble() = println("Assembling Tesla Model S")
override fun paint() = println("Painting Tesla Model S")
override fun test() = println("Testing Tesla Model S")

override fun chooseWheels(wheelType: String) = println("Choosing wheels for Tesla Model S: $wheelType")
override fun chooseSeats(seatType: String) = println("Choosing seats for Tesla Model S: $seatType")
override fun choosePaintColor(color: String) = println("Choosing paint color for Tesla Model S: $color")
}

class TeslaModel3 : Car {
override fun assemble() = println("Assembling Tesla Model 3")
override fun paint() = println("Painting Tesla Model 3")
override fun test() = println("Testing Tesla Model 3")

override fun chooseWheels(wheelType: String) = println("Choosing wheels for Tesla Model 3: $wheelType")
override fun chooseSeats(seatType: String) = println("Choosing seats for Tesla Model 3: $seatType")
override fun choosePaintColor(color: String) = println("Choosing paint color for Tesla Model 3: $color")
}

interface CarFactory {
fun createCar(model: String): Car?
}

class ToggFactory : CarFactory {
override fun createCar(model: String): Car? {
return when (model) {
"T10X" -> ToggT10X()
"T10F" -> ToggT10F()
else -> {
println("Error: Invalid Togg model")
null
}
}
}
}

class TeslaFactory : CarFactory {
override fun createCar(model: String): Car? {
return when (model) {
"Model S" -> TeslaModelS()
"Model 3" -> TeslaModel3()
else -> {
println("Error: Invalid Tesla model")
null
}
}
}
}

class CarProductionManager(val factory: CarFactory) {
fun buildCar(model: String) {
val car = factory.createCar(model)
car?.apply {
assemble()
paint()
test()

chooseWheels("Sport")
chooseSeats("Leather")
choosePaintColor("Red")
} ?: println("Unable to build the car: Model '$model' is invalid.")
}
}

fun main() {
val toggFactory = ToggFactory()
val teslaFactory = TeslaFactory()

val toggManager = CarProductionManager(toggFactory)
toggManager.buildCar("T10X")

val teslaManager = CarProductionManager(teslaFactory)
teslaManager.buildCar("Model S")
}

Evet, kodumuz bu haldeyken uygulamamıza Audi modellerini dahil etmeye çalıştığımızda aşağıdaki gibi mevcut kodu değiştirmediğimizi ve sadece genişlettiğimizi göreceğiz.

abstract class Car {
abstract fun assemble()
abstract fun paint()
abstract fun test()

abstract fun chooseWheels(wheelType: String)
abstract fun chooseSeats(seatType: String)
abstract fun choosePaintColor(color: String)
}

class ToggT10X : Car() {
override fun assemble() = println("Assembling Togg T10X")
override fun paint() = println("Painting Togg T10X")
override fun test() = println("Testing Togg T10X")

override fun chooseWheels(wheelType: String) = println("Choosing wheels for Togg T10X: $wheelType")
override fun chooseSeats(seatType: String) = println("Choosing seats for Togg T10X: $seatType")
override fun choosePaintColor(color: String) = println("Choosing paint color for Togg T10X: $color")
}

class ToggT10F : Car() {
override fun assemble() = println("Assembling Togg T10F")
override fun paint() = println("Painting Togg T10F")
override fun test() = println("Testing Togg T10F")

override fun chooseWheels(wheelType: String) = println("Choosing wheels for Togg T10F: $wheelType")
override fun chooseSeats(seatType: String) = println("Choosing seats for Togg T10F: $seatType")
override fun choosePaintColor(color: String) = println("Choosing paint color for Togg T10F: $color")
}

class TeslaModelS : Car() {
override fun assemble() = println("Assembling Tesla Model S")
override fun paint() = println("Painting Tesla Model S")
override fun test() = println("Testing Tesla Model S")

override fun chooseWheels(wheelType: String) = println("Choosing wheels for Tesla Model S: $wheelType")
override fun chooseSeats(seatType: String) = println("Choosing seats for Tesla Model S: $seatType")
override fun choosePaintColor(color: String) = println("Choosing paint color for Tesla Model S: $color")
}

class TeslaModel3 : Car() {
override fun assemble() = println("Assembling Tesla Model 3")
override fun paint() = println("Painting Tesla Model 3")
override fun test() = println("Testing Tesla Model 3")

override fun chooseWheels(wheelType: String) = println("Choosing wheels for Tesla Model 3: $wheelType")
override fun chooseSeats(seatType: String) = println("Choosing seats for Tesla Model 3: $seatType")
override fun choosePaintColor(color: String) = println("Choosing paint color for Tesla Model 3: $color")
}

class AudiA4 : Car() {
override fun assemble() = println("Assembling Audi A4")
override fun paint() = println("Painting Audi A4")
override fun test() = println("Testing Audi A4")

override fun chooseWheels(wheelType: String) = println("Choosing wheels for Audi A4: $wheelType")
override fun chooseSeats(seatType: String) = println("Choosing seats for Audi A4: $seatType")
override fun choosePaintColor(color: String) = println("Choosing paint color for Audi A4: $color")
}

class AudiQ5 : Car() {
override fun assemble() = println("Assembling Audi Q5")
override fun paint() = println("Painting Audi Q5")
override fun test() = println("Testing Audi Q5")

override fun chooseWheels(wheelType: String) = println("Choosing wheels for Audi Q5: $wheelType")
override fun chooseSeats(seatType: String) = println("Choosing seats for Audi Q5: $seatType")
override fun choosePaintColor(color: String) = println("Choosing paint color for Audi Q5: $color")
}

interface CarFactory {
fun createCar(model: String): Car?
}

class ToggFactory : CarFactory {
override fun createCar(model: String): Car? {
return when (model) {
"T10X" -> ToggT10X()
"T10F" -> ToggT10F()
else -> null
}
}
}

class TeslaFactory : CarFactory {
override fun createCar(model: String): Car? {
return when (model) {
"Model S" -> TeslaModelS()
"Model 3" -> TeslaModel3()
else -> null
}
}
}

class AudiFactory : CarFactory {
override fun createCar(model: String): Car? {
return when (model) {
"A4" -> AudiA4()
"Q5" -> AudiQ5()
else -> null
}
}
}

class CarProductionManager(val factory: CarFactory) {
fun buildCar(model: String) {
val car = factory.createCar(model)
car?.apply {
assemble()
paint()
test()

chooseWheels("Sport")
chooseSeats("Leather")
choosePaintColor("Red")
} ?: println("Unable to build the car: Model '$model' is invalid.")
}
}

fun main() {
val toggFactory = ToggFactory()
val teslaFactory = TeslaFactory()
val audiFactory = AudiFactory()

val toggManager = CarProductionManager(toggFactory)
toggManager.buildCar("T10X")

val teslaManager = CarProductionManager(teslaFactory)
teslaManager.buildCar("Model S")

val audiManager = CarProductionManager(audiFactory)
audiManager.buildCar("A4")
}

Güzel mevcut kodu değiştirmedik, yalnızca eklememiz gereken ister için genişlettik.

Elimden geldiğince basit bir şekilde ve somut örneklerle bu iki prensibi açıklamaya çalıştım. Umarım faydalı olmuştur.

SOLID’in L, I ve D harfleri de bir sonraki yazının konusu olsun.

Bir sonraki yazıda görüşmek üzere. Kodla kalın!

--

--