Sobes.tech
Junior

За какво отговаря буквата 'L' в принципите SOLID?

sobes.tech AI

Отговор от AI

Принцип на заместване на Лисков (Liskov Substitution Principle).

Според този принцип, обектите в програма трябва да могат да бъдат заменени с техните подкласове без да се нарушава коректността на програмата.

Други думи, ако класът B е подклас на клас A, тогава всеки обект от тип A в програмата може да бъде заменен с обект от тип B, и програмата ще продължи да работи правилно.

Този принцип помага за създаване на по-гъвкава и устойчива на промени архитектура, минимизирайки страничните ефекти при работа с наследяване.

Нарушение на LSP:

class Rectangle {
    var width: Double
    var height: Double

    init(width: Double, height: Double) {
        self.width = width
        self.height = height
    }

    var area: Double {
        return width * height
    }
}

class Square: Rectangle { // Квадрат е подклас на правоъгълника
    override var width: Double {
        didSet {
            height = width // Промяната на ширината също променя височината
        }
    }

    override var height: Double {
        didSet {
            width = height // Промяната на височината също променя ширината
        }
    }

    init(side: Double) {
        super.init(width: side, height: side)
    }
}

func calculateArea(of rectangle: Rectangle) -> Double {
    return rectangle.area
}

let myRectangle = Rectangle(width: 5, height: 10)
print(calculateArea(of: myRectangle)) // 50

let mySquare = Square(side: 5)
// Предполагаме, че това е правоъгълник 5x5 с площ 25.
// Но при използване във функция, която очаква Rectangle,
// поведението му може да бъде непредвидимо, ако променим ширината или височината
// преди извикването на calculateArea, тъй като Square променя и двете страни едновременно.
// В този пример, ако не променяме свойствата след инициализация,
// резултатът ще бъде 25, но ако например зададем mySquare.width = 10
// преди извикването на calculateArea, площта ще стане 100, което може
// да бъде неочаквано за кода, работещ с Rectangle.
print(calculateArea(of: mySquare)) // 25

В примера, Square нарушава LSP, тъй като промяната на една мярка (width или height) също променя другата. Ако използваме обект Square там, където се очаква Rectangle, това може да доведе до неочаквано поведение, тъй като кодът, работещ с Rectangle, може да предполага възможността за независимо изменение на страните.

Спазване на LSP:

За да спазим LSP в този случай, можем да разделим йерархията или да използваме композиция вместо наследяване за Square. Например, да въведем общ интерфейс или протокол за фигури, които имат площ, и да го реализираме в Rectangle и Square.

protocol Shape {
    var area: Double { get }
}

class Rectangle: Shape {
    var width: Double
    var height: Double

    init(width: Double, height: Double) {
        self.width = width
        self.height = height
    }

    var area: Double {
        return width * height
    }
}

class Square: Shape {
    var side: Double

    init(side: Double) {
        self.side = side
    }

    var area: Double {
        return side * side
    }
}

func calculateShapeArea(of shape: Shape) -> Double {
    return shape.area
}

let myShapeRectangle: Shape = Rectangle(width: 5, height: 10)
print(calculateShapeArea(of: myShapeRectangle)) // 50

let myShapeSquare: Shape = Square(side: 5)
print(calculateShapeArea(of: myShapeSquare)) // 25

Сега Square и Rectangle реализират общ протокол Shape, и можем да използваме обектите им там, където се очаква Shape, без да нарушаваме очакваното поведение.