Что является примером принципа подстановки Лискова? - PullRequest
783 голосов
/ 11 сентября 2008

Я слышал, что принцип замещения Лискова (LSP) является фундаментальным принципом объектно-ориентированного проектирования. Что это такое и какие примеры его использования?

Ответы [ 28 ]

3 голосов
/ 09 октября 2017

Допустим, мы используем прямоугольник в нашем коде

r = new Rectangle();
// ...
r.setDimensions(1,2);
r.fill(colors.red());
canvas.draw(r);

В нашем классе геометрии мы узнали, что квадрат - это особый тип прямоугольника, потому что его ширина равна длине его высоты. Давайте создадим класс Square на основе этой информации:

class Square extends Rectangle {
    setDimensions(width, height){
        assert(width == height);
        super.setDimensions(width, height);
    }
} 

Если мы заменим Rectangle на Square в нашем первом коде, он сломается:

r = new Square();
// ...
r.setDimensions(1,2); // assertion width == height failed
r.fill(colors.red());
canvas.draw(r);

Это потому, что Square имеет новое предварительное условие, которого у нас не было в классе Rectangle: width == height. В соответствии с LSP Rectangle экземпляры должны быть заменяемыми Rectangle экземплярами подкласса. Это связано с тем, что эти экземпляры проходят проверку типа для Rectangle экземпляров, и поэтому они вызовут непредвиденные ошибки в вашем коде.

Это был пример для "предварительных условий, которые нельзя усилить в подтипе" в статье wiki Итак, подведем итог: нарушение LSP в какой-то момент может вызвать ошибки в вашем коде.

2 голосов
/ 09 сентября 2013

Я рекомендую вам прочитать статью: Нарушение принципа подстановки Лискова (LSP) .

Вы можете найти там объяснение принципа подстановки Лискова, общие подсказки, помогающие угадать, если вы уже нарушили его, и пример подхода, который поможет вам сделать вашу иерархию классов более безопасной.

2 голосов
/ 23 сентября 2017

Принцип замещения Ликова гласит, что , если программный модуль использует Базовый класс, тогда ссылку на Базовый класс можно заменить производным классом, не затрагивая функциональность программного модуля.

Намерение - Производные типы должны полностью заменять свои базовые типы.

Пример - ко-вариантные типы возврата в Java.

2 голосов
/ 03 мая 2016

Самым ясным объяснением для LSP, которое я нашел до сих пор, было «Принцип подстановки Лискова говорит, что объект производного класса должен иметь возможность заменить объект базового класса без внесения каких-либо ошибок в систему или изменения поведения Базовый класс "от здесь . В статье приведен пример кода для нарушения LSP и его исправления.

2 голосов
/ 12 августа 2016

ПРИНЦИП ЗАМЕНЫ ЛИСКОВ (Из книги Марка Симанна) гласит, что мы должны иметь возможность заменить одну реализацию интерфейса другой, не нарушая ни клиента, ни реализацию. Это тот принцип, который позволяет учитывать требования, возникающие в будущем, даже если мы не можем предвидеть их сегодня.

Если мы отсоединяем компьютер от стены (Внедрение), ни настенная розетка (Интерфейс), ни компьютер (Клиент) не выходят из строя (фактически, если это портативный компьютер, он может даже работать от батарей в течение некоторого времени времени). Однако с программным обеспечением клиент часто ожидает, что услуга будет доступна. Если служба была удалена, мы получаем исключение NullReferenceException. Чтобы справиться с ситуацией такого типа, мы можем создать реализацию интерфейса, который «ничего не делает». Это шаблон проектирования, известный как Null Object, [4], и он примерно соответствует отключению компьютера от стены. Поскольку мы используем слабую связь, мы можем заменить реальную реализацию чем-то, что ничего не делает, не вызывая проблем.

1 голос
/ 06 апреля 2019

LSP говорит, что «объекты должны быть заменены их подтипами». С другой стороны, этот принцип указывает на

Дочерние классы никогда не должны нарушать определения типов родительского класса.

и следующий пример помогает лучше понять LSP.

Без LSP:

public interface CustomerLayout{

    public void render();
}


public FreeCustomer implements CustomerLayout {
     ...
    @Override
    public void render(){
        //code
    }
}


public PremiumCustomer implements CustomerLayout{
    ...
    @Override
    public void render(){
        if(!hasSeenAd)
            return; //it isn`t rendered in this case
        //code
    }
}

public void renderView(CustomerLayout layout){
    layout.render();
}

Исправление по LSP:

public interface CustomerLayout{
    public void render();
}


public FreeCustomer implements CustomerLayout {
     ...
    @Override
    public void render(){
        //code
    }
}


public PremiumCustomer implements CustomerLayout{
    ...
    @Override
    public void render(){
        if(!hasSeenAd)
            showAd();//it has a specific behavior based on its requirement
        //code
    }
}

public void renderView(CustomerLayout layout){
    layout.render();
}
0 голосов
/ 26 апреля 2019

Позвольте мне попробовать, рассмотрим интерфейс:

interface Planet{
}

Это реализуется классом:

class Earth implements Planet {
    public $radius;
    public function construct($radius) {
        $this->radius = $radius;
    }
}

Вы будете использовать Землю как:

$planet = new Earth(6371);
$calc = new SurfaceAreaCalculator($planet);
$calc->output();

Теперь рассмотрим еще один класс, расширяющий Землю:

class LiveablePlanet extends Earth{
   public function color(){
   }
}

Теперь, согласно LSP, вы должны иметь возможность использовать LiveablePlanet вместо Земли, и это не должно сломать вашу систему. Как:

$planet = new LiveablePlanet(6371);  // Earlier we were using Earth here
$calc = new SurfaceAreaCalculator($planet);
$calc->output();

Примеры взяты из здесь

0 голосов
/ 12 августа 2018

Вот выдержка из этого поста , в которой все проясняется:

[..] Чтобы понять некоторые принципы, важно понимать, когда они были нарушены. Это то, что я буду делать сейчас.

Что означает нарушение этого принципа? Это означает, что объект не выполняет контракт, наложенный абстракцией, выраженной интерфейсом. Другими словами, это означает, что вы неправильно определили свои абстракции.

Рассмотрим следующий пример:

interface Account
{
    /**
     * Withdraw $money amount from this account.
     *
     * @param Money $money
     * @return mixed
     */
    public function withdraw(Money $money);
}
class DefaultAccount implements Account
{
    private $balance;
    public function withdraw(Money $money)
    {
        if (!$this->enoughMoney($money)) {
            return;
        }
        $this->balance->subtract($money);
    }
}

Это нарушение LSP? Да. Это связано с тем, что договор об аккаунте говорит нам, что аккаунт будет отозван, но это не всегда так. Итак, что я должен сделать, чтобы это исправить? Я просто изменяю договор:

interface Account
{
    /**
     * Withdraw $money amount from this account if its balance is enough.
     * Otherwise do nothing.
     *
     * @param Money $money
     * @return mixed
     */
    public function withdraw(Money $money);
}

Вуаля, теперь контракт выполнен.

Это тонкое нарушение часто наделяет клиента способностью различать конкретные используемые объекты. Например, учитывая контракт первого Аккаунта, он может выглядеть следующим образом:

class Client
{
    public function go(Account $account, Money $money)
    {
        if ($account instanceof DefaultAccount && !$account->hasEnoughMoney($money)) {
            return;
        }
        $account->withdraw($money);
    }
}

И это автоматически нарушает принцип открытого-закрытого доступа [то есть требования снятия денег. Потому что вы никогда не знаете, что происходит, если у объекта, нарушающего договор, не хватает денег. Возможно, он просто ничего не возвращает, возможно, будет выдано исключение. Таким образом, вы должны проверить, если hasEnoughMoney() - который не является частью интерфейса. Так что эта принудительная проверка, зависящая от конкретного класса, является нарушением OCP].

Этот пункт также относится к заблуждению, с которым я часто сталкиваюсь по поводу нарушения LSP. В нем говорится «если поведение родителя изменилось у ребенка, значит, оно нарушает LSP». Однако это не так - до тех пор, пока ребенок не нарушает контракт своего родителя.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...