hkucuk

SOLID - Liskov Substitution Principle

15 Kasım 2021 • ☕️ 4 dk okuma • 🏷 bilgisayar, yazılım

Yazar tarafından şu dillere çevrildi: English


Liskov Substitution Prensibi (LSP), Barbara Liskov tarafından geliştirilen bir kalıtım prensibi olup, bu prensip, bir sınıfın kalıtım ağacındaki bir alt sınıfın yerine, yüksek seviye bir sınıfın kullanılmasının mümkün olmasını sağlar. Bu prensibe göre, alt sınıfların, yüksek seviye sınıfların yerine kullanılabilmeleri için alt sınıfların aynı özellik ve davranışlara sahip olabilmeleri gerekir.

Alt sınıflardan oluşan nesnelerin, üst sınıfın nesneleri ile yer değiştirdikleri zaman, aynı davranışı sergilemesi gerekmektedir.

PHP’de, Liskov Substitution prensibini uygulamak için aşağıdaki gibi bir örnek verilebilir:

<?php
// Parent sınıfı
class Vehicle {
  protected $model;

  public function setModel($model) {
    $this->model = $model;
  }
}

// Child sınıfı
class Car extends Vehicle {
  public function getModel() {
    return "The model of this car is: " . $this->model;
  }
}

// Child sınıfı
class Bicycle extends Vehicle {
  public function getModel() {
    return "The model of this bicycle is: " . $this->model;
  }
}

// Parent sınıfının yerine kullanılabilecek bir child sınıfı
class ElectricCar extends Car {
  public function getModel() {
    return "This car is electric. " . parent::getModel();
  }
}

// Örnek oluşturma
$electricCar = new ElectricCar();
$electricCar->setModel("Tesla Model 3");

echo $electricCar->getModel(); // "This car is electric. The model of this car is: Tesla Model 3"

Bu örnekte, Vehicle sınıfı bir parent sınıf olup, Car ve Bicycle sınıfları ise bu sınıfın child sınıflarıdır. ElectricCar sınıfı ise Car sınıfının bir alt sınıfıdır. ElectricCar sınıfı, Vehicle sınıfının yerine geçebilir ve bu nedenle Vehicle sınıfına ait herhangi bir kodu ElectricCar sınıfına uygulayabiliriz. Bu Liskov Substitution prensibinin temelini oluşturur.

Aşağıdaki PHP kodu ise Liskov Substitution prensibini ihlal eden bir sınıf örneğidir.

<?php
// Parent sınıfı
class Vehicle {
  protected $model;

  public function setModel($model) {
    $this->model = $model;
  }
}

// Child sınıfı
class Car extends Vehicle {
  public function getModel() {
    return "The model of this car is: " . $this->model;
  }
}

// Child sınıfı
class Bicycle extends Vehicle {
  public function getModel() {
    return "The model of this bicycle is: " . $this->model;
  }
}

// Parent sınıfının yerine kullanılamayacak bir child sınıfı
class ElectricCar extends Car {
  private $batteryType;

  public function __construct($batteryType) {
    $this->batteryType = $batteryType;
  }

  public function getModel() {
    return "This electric car uses a " . $this->batteryType . " battery. " . parent::getModel();
  }
}

// Örnek oluşturma
$electricCar = new ElectricCar("lithium-ion");
$electricCar->setModel("Tesla Model 3");

echo $electricCar->getModel(); // "This electric car uses a lithium-ion battery. The model of this car is: Tesla Model 3"

// Hata: Vehicle sınıfının yerine ElectricCar sınıfı kullanılamaz
$vehicle = new Vehicle();
$vehicle = $electricCar; // Hata: "Cannot assign an ElectricCar instance to a Vehicle variable"

Bu örnekte, ElectricCar sınıfı Car sınıfının bir alt sınıfıdır ve Vehicle sınıfının yerine geçebilir. Ancak, ElectricCar sınıfının bir batteryType özelliği vardır ve bu özellik, Vehicle sınıfında bulunmamaktadır. Bu nedenle Vehicle sınıfının yerine ElectricCar sınıfını kullanılamaz. Bu Liskov Substitution prensibinin ihlal edildiğini gösterir.


GoLang dilinde, Liskov Substitution prensibini uygulamak için aşağıdaki gibi bir örnek verilebilir:

package main

import "fmt"

// Parent interface
type Vehicle interface {
  SetModel(model string)
}

// Child struct
type Car struct {
  model string
}

func (c *Car) SetModel(model string) {
  c.model = model
}

func (c *Car) GetModel() string {
  return fmt.Sprintf("The model of this car is: %s", c.model)
}

// Child struct
type Bicycle struct {
  model string
}

func (b *Bicycle) SetModel(model string) {
  b.model = model
}

func (b *Bicycle) GetModel() string {
  return fmt.Sprintf("The model of this bicycle is: %s", b.model)
}

// Parent interface'in yerine kullanılabilecek child struct
type ElectricCar struct {
  Car
}

func (e *ElectricCar) GetModel() string {
  return fmt.Sprintf("This car is electric. %s", e.Car.GetModel())
}

func main() {
  // Örnek oluşturma
  electricCar := ElectricCar{}
  electricCar.SetModel("Tesla Model 3")

  fmt.Println(electricCar.GetModel()) // "This car is electric. The model of this car is: Tesla Model 3"

  // Parent interface'in yerine ElectricCar struct'ı kullanılabilir
  var vehicle Vehicle = &electricCar
  fmt.Println(vehicle.GetModel()) // "This car is electric. The model of this car is: Tesla Model 3"
}

Bu örnekte Vehicle interface’i bir parent interface olup, Car ve Bicycle struct’ları bu interface’i implement eden child struct’lardır. ElectricCar struct’ı ise Car struct’ının bir alt struct’ıdır. ElectricCar struct’ı, Vehicle interface’inin yerine geçebilir ve bu nedenle Vehicle interface’ine ait herhangi bir kodu,ElectricCar struct’ına uygulayabiliriz.

PHP örneğine benzer şekilde, aşağıdaki GoLang kodu da Liskov Substitution prensibini ihlal eden bir örnektir.

package main

import "fmt"

// Parent interface
type Vehicle interface {
  SetModel(model string)
  GetModel() string
}

// Child struct
type Car struct {
  model string
}

func (c *Car) SetModel(model string) {
  c.model = model
}

func (c *Car) GetModel() string {
  return fmt.Sprintf("The model of this car is: %s", c.model)
}

// Child struct
type Bicycle struct {
  model string
}

func (b *Bicycle) SetModel(model string) {
  b.model = model
}

func (b *Bicycle) GetModel() string {
  return fmt.Sprintf("The model of this bicycle is: %s", b.model)
}

// Parent interface'in yerine kullanılamayacak child struct
type ElectricCar struct {
  Car
}

func (e *ElectricCar) GetModel() string {
  return fmt.Sprintf("This car is electric. %s", e.Car.GetModel())
}

// ElectricCar'ın yerine geçemeyecek bir metod
func (e *ElectricCar) Charge() {
  fmt.Println("Charging the electric car...")
}

func main() {
  // Örnek oluşturma
  electricCar := ElectricCar{}
  electricCar.SetModel("Tesla Model 3")

  fmt.Println(electricCar.GetModel()) // "This car is electric. The model of this car is: Tesla Model 3"

  // Hata: Vehicle interface'inin yerine ElectricCar struct'ı kullanılamaz
  var vehicle Vehicle = &electricCar // Hata: "ElectricCar does not implement Vehicle (missing Charge method)"
}

Bu örnekte de ElectricCar struct’ı Car struct’ının bir alt struct’ıdır ve Vehicle interface’inin yerine geçebilir. Ancak ElectricCar struct’ının bir Charge() metodu vardır ve bu metod Vehicle interface’inde bulunmamaktadır. Bu nedenle Vehicle interface’inin yerine ElectricCar struct’ı kullanılamayacaktır.


Kaynaklar