LINUX.ORG.RU

Соблюдаете ли вы LSP?

 , , , ,


2

6

Из комментариев:

«Но следовать LSP или не следовать — дело договоренности внутри команды. Если команда следует, это одно, если не следует — это другое. Команда, в которой работаю, этому принципу не следует.»

Это очень забавный вопрос) Идут годы, а истории те же самые)

Саттер и Александреску говорят про LSP: «подкласс не должен требовать от вызывающего кода больше, чем базовый класс, и не должен предоставлять вызывающему коду меньше, чем базовый класс». Это те чуваки. который C++ и D делали :-) У Гослинга тоже где-то было про это, сейчас не нагуглю.

Посмотрим, как они мучались, когда не было дженериков.

Вот код на Java:

static void update(Object[] objs)
{
    objs[0] = new Object();
}

public static void main(String[] args) {
    String[] strs = new String[] { "hello", "world" };
    update(strs);
}

Или на C#:

static void update(object[] objs)
{
   objs[0] = new object();
}

string[] strs = new string[] { "hello", "world" };
update(strs);

При запуске он бросит исключение ArrayStoreException (Java) или ArrayTypeMismatchException (C#), поскольку Шарп не может записать экземпляр объекта внутрь массива из стрингов. Это — прямое нарушение LSP. string — это подтип object, но когда его попытались использовать в том же месте где object[], всё сломалось. Подстановочность не работает. Заметьте, что это ошибка времени выполнения, а не компиляции.

В чем проблема? Если следовать логике, то пришлось бы писать несколько одинаковых методов: `static void update(string[] objs)`, `static void update(int[] objs)` — и люто копипастить. Вроде бы, в 2018 году так всё ещё делают в некоторых отсталых языках вроде Golang, когда не опускаются до рефлексии и кодогенерации. В нормальных языках для этого есть дженерики. Но когда Java и C# создавались, дженериков в них не было ещё. Поэтому массивы сделали ковариантными по типу элемента. В смысле, теперь можно отправить string[] на вход методу, который принимает object[], и это скомпилируется и заработает вот так:

Java:

static void sort(Object[] objs)
{
    Arrays.sort(objs);
}

public static void main(String[] args) {
    String[] strs = new String[] { "hello", "world" };
    sort(strs);
}

C#:

static void Sort(object[] objs)
{
   // ...
}

string[] strs = new string[] { "hello", "world" };
Sort(strs);

Совершенно очевидно, что это жёсткий хак системы, сделанный от безысходности.

- Кто мы?
- Большие боссы, заказчики языка!
- Что мы хотим?
- Уменьшения копипасты.
- Когда мы это хотим?
- ПРЯМО СЕЙЧАС!!! //и наплевать на ваши задротские дженерики

ООП не предназначено для уменьшения копипасты, оно скорее запрограммировано на её увеличение. Уменьшить именно дублирование буковок можно только на уровне другого над-языка вроде шаблонного генератора.

Есть подозрение, что идти против дизайна языка - это удовольствие не для слабонервных. Когда у тебя в системе будут тысячи классов и типы вроде `Map<Obj,Map<Obj,Map<Obj,Map<Obj,Obj>>>>`, без соблюдения некоторого феншуя всё это быстро скатится в пучину ада.

Теперь, ежедневный опрос. Стоит ли соблюдать LSP? Приведите аргументы.

★★★★☆

Последнее исправление: stevejobs (всего исправлений: 1)
        System.out.println(Object[].class.getSuperclass());
        System.out.println(String[].class.getSuperclass());

Давно String[] является наследником Object[]? Или то что в твоей голове

string[] — это подтип object[]

должно кого-то волновать?

Вот если бы ты писал про то что List<String> и List<Object> incompatible и требует <? extends Object>, это было бы еще пол беды

То ты рассказываешь про Java 11, graal и хаки jvm на крестах, а теперь пришел с детским вопросом уровня жава джуниора [почему жава не такая как я считаю должна быть]. Ты джуниор евангелист?

anonymous
()

Про то что ошибка только в рантайме - это просто дыра в системе типов. Чтобы типы правильно работали в коллекциях, можно было либо усложнить систему типов, либо сделать в ней дыру с проверкой в рантайме, и выбрали второе, примерно как и с NPE. Про это на курсере было :D

anonymous
()
Ответ на: комментарий от stevejobs

но вот отсутствие IDE и программирования с помощью автодополнения

Чем тебе leksah не угодил?

monk ★★★★★
()
Ответ на: комментарий от Lancelot

http://docs.scala-lang.org/tour/variances.html ты про это?

Ага. Это одно из решений. Другое: неизменяемость и классы типов Хаскела. Третье: запретить вообще наследование реализации как в Go и Rust.

Но в целом это попытка проверки установки инвариантов синтаксическими средствами. Если идти по этому пути, то в дальнейшем надо алгоритмически проверять, что для любой реализации operator+, например, a+b=b+a и a+(b+c) = (a+b)+c (а для конкатенации строк придумать другой оператор, а не сложение).

monk ★★★★★
()
Ответ на: комментарий от anonymous

Давно String[] является наследником Object[]? Или то что в твоей голове

С точки зрения компилятора Java, является всегда. Так же как для компилятора Си (int*) является наследником (void*).

monk ★★★★★
()

Вспомни СУБД и денормализацию. Теория хороша в теории, на практике приходится иметь дело с суровой реальностью. Лично я стараюсь ЛСП соблюдать до если его соблюдение не начинает доставлять боль.

Поэтому массивы сделали ковариантными по типу элемента.

Совершенно очевидно, что это жёсткий хак системы, сделанный от безысходности.

Ты видио не писал ничего крупного на языках где это не так. Там копипасты ещё больше.

Map<Obj,Map<Obj,Map<Obj,Map<Obj,Obj>>>>

Помню как запилил трехэтажные дженерики на 6ой жабе кажется, так что они у меня не компилились, пришлось растипизировать немного.

ya-betmen ★★★★★
()
Ответ на: комментарий от monk

что для любой реализации operator+, например, a+b=b+a и a+(b+c) = (a+b)+c (а для конкатенации строк придумать другой оператор, а не сложение)

И для произведения матриц использовать другой оператор, а не умножение?

korvin_ ★★★★★
()
Ответ на: комментарий от Virtuos86

Канеш, в инглише, например, есть лишь два падежа, и один из них — косвенный — как раз с именами и применяется (Moe's, Freddy's).

bodqhrohro_promo
()

Мне кажется, что ты просто выбрал некорректный пример для иллюстрации несоблюдения LSP. Производный класс обязан приводиться к базовому, но базовый совсем не обязан приводиться к производному, что ты пытаешься сделать в методе update. Не говоря уже о том, что статический метод никак не может быть полиморфным. Т. е. проблема не в интерфейсе Object и String, а в твоём интерфейсе. Именно ты должен был бы грамотно реализовать полиморфный update и использовать его. Или не делать этого.

aureliano15 ★★
()
Ответ на: комментарий от korvin_

И для произведения матриц использовать другой оператор, а не умножение?

Разумеется. Тем более, что для матриц их два: a·b и a×b.

monk ★★★★★
()
Ответ на: комментарий от aureliano15

Именно ты должен был бы грамотно реализовать полиморфный update и использовать его. Или не делать этого.

Так именно метод update и нарушает LSP. Пример корректен.

monk ★★★★★
()
Ответ на: комментарий от monk

Именно ты должен был бы грамотно реализовать полиморфный update и использовать его. Или не делать этого.

Так именно метод update и нарушает LSP. Пример корректен.

Но зачем тогда говорить о каких-то проблемах с LSP, если игнорируются механизмы полиморфизма, когда сам принцип строится на них?

aureliano15 ★★
()
Ответ на: комментарий от aureliano15

если игнорируются механизмы полиморфизма

update компилируется без ошибок. Таким образом с точки зрения системы типов Java полиморфизм не нарушается.

базовый совсем не обязан приводиться к производному, что ты пытаешься сделать в методе update

Он пытается не это. Он показывает, что считать String[] производным от Object[] неверно с точки зрения LSP. Так как у Object[] есть операция «добавить элемент типа Object», а у String[] нет. Но Java считает String[] производным от Object[].

когда сам принцип строится на них

Строится. Но LSP гораздо строже, чем просто полиморфизм. По сути он накладывает ограничения на возможные модификации методов в потомках.

monk ★★★★★
()

Каждый юниор нарушает LSP, по 10 раз на дню, когда ему идея генерит equals.

RedPossum ★★★★★
()
Ответ на: комментарий от monk

если игнорируются механизмы полиморфизма

update компилируется без ошибок. Таким образом с точки зрения системы типов Java полиморфизм не нарушается.

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

Он показывает, что считать String[] производным от Object[] неверно с точки зрения LSP. Так как у Object[] есть операция «добавить элемент типа Object», а у String[] нет.

А, тогда действительно косяки в иерархии наследования стандартных классов. Но даже если бы в массив String можно было добавлять элементы, его код не сработал бы, т. к. это должны быть элементы типа String, а не Object, потому что String можно привести к Object, но не наоборот.

aureliano15 ★★
()
Ответ на: комментарий от aureliano15

Для обеспечения полиморфизма в полной мере функция должна быть виртуальной, что для статических методов не выполняется.

Что бы поменялось при

class Test
{
  void update(Object[] objs)
  {
    objs[0] = new Object();
  }
}
?

Но даже если бы в массив String можно было добавлять элементы, его код не сработал бы

Для соблюдения LSP в массив String должно быть можно добавлять элементы String или любого предка String. И тогда код сработал бы. Или массив String не должен быть наследником массива предков String. Но в Java (и C#) имеет место нарушение LSP.

Но даже если бы в массив String можно было добавлять элементы, его код не сработал бы, т. к. это должны быть элементы типа String, а не Object

Он про это и пишет в теме. «При запуске он бросит исключение ArrayStoreException.»

monk ★★★★★
()
Ответ на: комментарий от monk

Для обеспечения полиморфизма в полной мере функция должна быть виртуальной, что для статических методов не выполняется.

Что бы поменялось при

class Test
{
  void update(Object[] objs)
  {
    objs[0] = new Object();
  }
}

?

Поменялось бы, если бы метод был перегружен для всех подклассов.

Для соблюдения LSP в массив String должно быть можно добавлять элементы String или любого предка String.

Или так. Но это в случае, если нужен массив произвольных объектов. В общем, как всегда зависит от задачи.

aureliano15 ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.