Наследоваться от нативных классов (с соблюдением LSP) - yea or nay?

Фанат

oncle terrible
Команда форума
Никак не могу определиться.
В прошлый раз, несколько лет назад, меня заслуженно запинали за то что я "спрятал, но не прекратил" - наследовался с переписыванием конструктора.
А если без?
С одной стороны - композишен овер инхеританс.
С другой - принцип открытого клозета всё-таки говорит что "открыт для расширения" (сиречь наследования же?). Ну а если открыт - то можно же?

Но меня тут запинал какой-то чувак на стековерфлое прям очень эмо ционально, за то что я посмел добавить в пдошечку нашу всего лишь один простой метод, ничего больше нитрогал. Мотивировал тем, что у него может быть своя пдошечка, и если я буду использовать, скажем, в своей либе расширенную, то он не сможет моей либой ползоваться.
Ну и самому стрёмно как-то.
Или постойте. Получается, что ЛСП относится не только к конструктору, как я до сих пор думал - а ко всему публичному контрахту целиком?
 
Последнее редактирование:

флоппик

promotor fidei
Команда форума
Партнер клуба
Да. Открыт для расширения, это не про наследование. Наследование - это изменение поведения. Расширение - это дополнение поведения. Ну и не существует никаких особенной магичности именно в конструкторе, лсп это только про публичный контракт, конечно же. Конструктор может быть, а может не быть частью контракта. Ну и то, что технически является в языке пхп конструктором, не обязательно конструктор для обьекта этого контракта. Например, у PdoStatement нет конструктора в контракте, тк его всегда порождает PDO.
 

WMix

герр M:)ller
Партнер клуба
мне странно слышать что конструктор имеет какую либо роль в LSP, как бы это "до" обьекта, и не врублюсь как это может быть частью контракта, можно пример когда это необходимо
 

fixxxer

К.О.
Партнер клуба
LSP как раз про контракт. Вот приходит мне в метод PDOStatement $stmt, и мне должно быть абсолютно пофигу, PDOStatement там или наследник. Вот это и есть оно.

Конструктор же, классически, к LSP вообще не имеет никакого отношения - ведь когда я создаю инстанс, я явно указываю имя класса. Во всяком случае, в более строгих языках, в php с его динамикой и late static binding всякое может быть.
 

fixxxer

К.О.
Партнер клуба
По наследованию. От нативных или нет - не вижу разницы. По сути же - если получаются ситуации, когда пишешь instanceof, значит, наследование - плохая идея. Если нет - все ок. В целом, единственный стопроцентный вариант, когда наследование норм - это изменение внутреннего поведения с полным сохранением публичного контракта. Добавление метода - уже практически гарантия, что instanceof где-нибудь вылезет.
 

Фанат

oncle terrible
Команда форума
это изменение внутреннего поведения
Я правильно понимаю что ты здесь имеешь в виду "изменение того, как внутри себя класс выполняет свою работу, но результат должен оставаться тот же самый"? (ну просто изменение внутреннего поведения может быть истолковано двояко)
По логике это так, но я тогда вообще перестал понимать принцип открытого клозета. Что значит это "расширение"?
 

Фанат

oncle terrible
Команда форума
мне странно слышать что конструктор имеет какую либо роль в LSP
Я думаю это у меня из того тупого факта, что пхп бьёт тебя по рукам, когда ты меняешь конструктор.
А когда ты добиваешь методы - ему пофигу.
Ну вот и.
 

S.Chushkin

Пофигист
а ко всему публичному контрахту целиком?
Если под "публичным" считается в т.ч. protected, то да.
А с наследованием и методами всё просто:
PHP:
// есть нарушение
class A {
    public function Draw2D() {}
}
class B extends A {
    public function Draw3D() {}
}
// нет нарушения
class A {
    public function Draw() {}
}
class B extends A {
    public function Draw() {}
}
//
class A {
    public function Draw2D() {}
}
class B extends A {
    public function Draw2D() {}
    public function Draw3D() {}
}
Некоторые ещё считают нарушением, если не выполняется условие: предусловия могут только ослабляться, постусловия - только усиливаться.
Но ... это спорный вопрос, на любителя.
Я бы считал базовым правилом для принципа: Поведение класса не должно меняться в наследнике. Поведение метода не должно менять действие.
Грубо, - если написано Draw, значит должно быть "нарисовано" тчк . А что и как нарисовано, это внутреннее дело объекта.

По поводу конструктора. В ПХП это просто метод, который вызывается при создании класса. И если конструктор-наследник не изменяет действие конструктора-предка, то принцип соблюдается.

Как-то так...
 
Последнее редактирование:

флоппик

promotor fidei
Команда форума
Партнер клуба
я посмел добавить в пдошечку нашу всего лишь один простой метод, ничего больше нитрогал.
С точки зрения контракта, твоего добавленного метода не существует: ведь его нельзя вызвать, потому что нет способа узнать у какой реализации контракта есть этот метод, а у какой - нет!
И тут два варианта: либо твоя реализация прекрасно выполняет заявленный контракт, и добавленный метод не нужен для контракта, а значит, метод можно просто сделать либо приватным, или вообще удалить, либо твоя реализация просто не может выполнить контракт, который она хочет реализовать!
Ага, становится яснее.
А можешь чуть развернутее пояснить? с примером?
Я понял что вообще не представляю, что здесь имеется в в виду.
Очень суррогатный пример, как обычно:
Предположим, у тебя есть контракт:
PHP:
interface DatabaseCredentials {
   public getDsn(): string;
   public getUsername(): string;
   public getPassword(): string;
}
ты берешь, и делаешь реализацию для докера:
PHP:
class EnvDatabaseCredentials implements DatabaseCredentials {
 /// ... методы с геттерами и тд
   public readFromEnv()
  {
      $this->dsn = env('DATABASE_DSN');
      $this->username = env('DATABASE_USERNAME');
      $this->password = env('DATABASE_PASSWORD');
  }
}
Можно ли сказать, что твоя реализация выполняет контракт? Конечно нет, ведь придется в коде писать какое-то гонево, типа:
PHP:
class Database {
public connect(DatabaseCredentials $creds) : Connection
   // и тут кто-то вынужден написать говношляпу:
   if ($creds instanceof EnvDatabaseCredentials) {
    $creds->readFromEnv();
  }
   return $pdo->connect($creds->getDsn(), $creds->getUsername(), $creds->getPassword());
}
 
Последнее редактирование:

флоппик

promotor fidei
Команда форума
Партнер клуба
Я думаю это у меня из того тупого факта, что пхп бьёт тебя по рукам, когда ты меняешь конструктор.
А когда ты добиваешь методы - ему пофигу.
Ну вот и.
пф, так ты просто сделай конструктор приватным (т.е. изъяв его из публичного контракта) и пхп не будет ругаться!
 

fixxxer

К.О.
Партнер клуба
Мне вообще не нравится формулировка OCP.
Если задуматься, смысл в том, чтобы поддерживать разумный уровень расширяемости методом разделения на методы, а не пихать все в один метод run(). Причем эта расширяемость не обязательно реализуется механизмом наследования, может, например, быть общий интерфейс и частичное делегирование (это даже лучше).
Я правильно понимаю что ты здесь имеешь в виду "изменение того, как внутри себя класс выполняет свою работу, но результат должен оставаться тот же самый"? (ну просто изменение внутреннего поведения может быть истолковано двояко)
По логике это так, но я тогда вообще перестал понимать принцип открытого клозета. Что значит это "расширение"?
Ну, например, я захотел сделать "кэширующий" pdo, который считает хэш от запроса и биндингов и кэширует по нему в мемкеше.
Добавляю, скажем, в конструктор опцию, все методы Pdo и PdoStatement остаются ровно такими же, но внутре у ней неонка, ну то есть мемкеш-клиент.
Тут и OCP (open for extension, вот взял и расширил), и LSP (публичный контракт остался as is). Кэширование тут "автоматическое", для пользователя этой моей caching-PDO ничего не меняется, он ничего не знает про кэширование, это внутренняя деталь реализации. Может быть, я даже внутри сделаю парсинг SQL-запросов и умную инвалидацию кэша на основании результатов парсинга. Но если я добавлю методы или аргументы для явного сброса кэша - тут уже нарушение LSP, да и из общих соображений, если я управляю кэшированием, то это уже другая "штука" совсем.
 
Последнее редактирование:

Фанат

oncle terrible
Команда форума
Ну в общем под изменением мы имеем в виду "улучшение". Типа оптимизировать код.
Хотя пойду щас мартина почитаю
 

fixxxer

К.О.
Партнер клуба
Не, почему? Частичное переиспользование.
Например, я могу захотеть добавить в PDO поддержку какого-нибудь clickhouse. Парсить DSN, если там clickhouse, делегировать своей реализации, если нет, то нативной.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Ну, например, я захотел сделать "кэширующий" pdo, который считает хэш от запроса и биндингов и кэширует по нему в мемкеше.
Тут и OCP (open for extension, вот взял и расширил), и LSP (публичный контракт остался as is).
и srp 😁
неудачный пример
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
@Фанат давай более жизненный пример возьмем.
Вот представь - пилим мы магазин на Yii. Нет, за него прибьют - на Laravel. Разницы нет, конечно, но там аффтор про маркетинг думал.
База у нас через Eloquent-классы, конечно. Не мы такие - жизнь такая. 100500 классов вручили - "хорошего вам настроения". Классы уже есть.
Хотим некоторые select-ы на отдельный slave перенести. В конфиг соединение добавить можно, но надо вынести только одну категорию под 14 февраля, а не все запросы размазывать.
Надо логику выбора соединения пропихнуть теперь в Eloquent. Писали бы Yii Laravel нормально - вопроса нет, прописал в контейнере свой query builder, и в нем выбираешь соединение. Но эти братья по разуму сделали в фреймвоках два разных класса для генерации запросов без общего интерфейса. Это закрытые для расширения интерфейсы.

А спросили бы они нас про open-closed principle, принимали бы тип по интерфейсу вместо имен классов - и не было бы этого примера )))
 

Adelf

Administrator
Команда форума
Надо логику выбора соединения пропихнуть теперь в Eloquent. Писали бы Yii Laravel нормально - вопроса нет, прописал в контейнере свой query builder, и в нем выбираешь соединение.
Никаких проблем такое сделать. И ты слишком многое полагаешь на query builder. Выбор соединения все равно должен быть на более высоком уровне.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Никаких проблем такое сделать. И ты слишком многое полагаешь на query builder. Выбор соединения все равно должен быть на более высоком уровне.
чукчанечитатель? :) 100500 классов уже написаны до нас, вчера продажники придумали акцию к 14 февраля "купи пачку презервативов - получи омыватель для стекла", и заказали рекламу, перепиливать логику и некогда и нафиг не надо - через 2 недели этот код надо выкинуть,
надо просто перекинуть запросы к акции на временный слейв
ты как первый раз в ecommerce )))
 
Сверху