LINUX.ORG.RU

Вопрос по принципу Лисков.

 ,


0

3

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

Основной вопрос тут заключается в том, может ли класс-клиент использовать интерфейс квадрата и прямоугольника единообразно.

Контракт прямоугольника (инвариант): ширина и высота положительны.

Контракт квадрата (инвариант): ширина и высота положительны; ширина и высота равны.

допустим, мы переопределяем методы квадрата setWidth и setHeight таким образом, что квадрат не реагирует на них, просто игнорирует.

Нарушили ли мы контракты? Нет. Нарушили ли мы принцип Лисков? Да, вроде, тоже нет. Тогда в чем проблема?

Ответ на: комментарий от loz

все таки, чтобы прояснить суть дела, полезно абстрагироваться от субъективного, чем является, в частности, и представление о пользе.

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

Тот факт, что вызов setWidth/setHeight не всегда приводит к изменениям, не должен смущать, это обычный паттерн в программировании, если объект может реагировать — он реагирует, если нет — молчит. Ничего тут сверхъестественного нет.

linearisation
() автор топика
Ответ на: комментарий от linearisation

у нас есть цель обеспечить единство интерфейса для класса-клиента

Мелковато целишь

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

полезно абстрагироваться от субъективного

И понять, что в первую очередь Rectangle и Square не реальные геометрические фигуры а куски кода
Они представляют эти фигуры, а не разделяют отношение между этими фигурами в реальном мире
Вот пример от Роберта Мартина

import java.util.ArrayList;
import java.util.List;

public class Main {

    private class Rectangle { }
    private class Square extends Rectangle { }

    void g() {
        List<Square> squares = new ArrayList<Square>();
        f(squares);
    }

    void f(List<Rectangle> l) {
        l.add(new Rectangle());
    }

    public static void main(String[] args) {
        new Main().g();
    }
}
Error:(11, 11) java: incompatible types: java.util.List<Main.Square> cannot be converted to java.util.List<Main.Rectangle>

system-root ★★★★★
()
Последнее исправление: system-root (всего исправлений: 1)

ООП головного мозга. Долго медитировать над отношениями «А является Б» объектов предметной области, соблюдением принципа Лисков, построить красивую иерархию классов. Обнаружить, что внутренний цикл упирается в размер кэша, и можно ускорить программу в 10 раз, храня ширину и высоту в разных массивах. Начать лепить костыли.

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

В общем. Сделать модуль с функциями, принимающими ширину и высоту, и выполняющими необходимые действия. И уже используя эти функции лепить хоть иерархию классов, хоть cache-conscious алгоритмы.

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

Обнаружить, что внутренний цикл упирается в размер кэша, и можно ускорить программу в 10 раз, храня ширину и высоту в разных массивах

Есть же гении на ЛОРе, которые не задумываются о такой ерунде как кэш, у них же принципы...

I-Love-Microsoft ★★★★★
()

ТС, а как ты, в контексте ООП, прокомментируешь тот факт, что квадрат на самом деле является четырёхугольником, прямоугольником, параллелограммом и ромбом? Возможно ли средствами ООП выразить иерархию отношений квадрата с другими фигурами?

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

В реальном мире легко представить ситуацию, когда в группе прямоугольников находятся квадраты, ведь прямоугольник и есть набор квадратов.
Но это не реальный мир, а представление его.
«представляют» != «разделяют отношение»
Еще раз — Rectangle это не прямоугольник из геометрии, в которой нет Refused Bequest

system-root ★★★★★
()
Последнее исправление: system-root (всего исправлений: 1)
Ответ на: комментарий от system-root

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

Лол :-) Это ты из какой методички взял? :-)

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

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

То, что ты продемонстрировал - это пример контравариантности, к принципу Лисков отношения не имеет (будет принцип соблюден или нет - ArrayList останется контравариантным).

Еще раз — Rectangle это не прямоугольник из геометрии, в которой нет Refused Bequest

То, что предложение анонiмуса нарушает контракт класса и, следовательно, принцип Лисков - это понятно.

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

к принципу Лисков отношения не имеет

абсолютно согласен, не в одном из постов про принцип ни слова, только про «отношение» из за которого возможно возникла проблема

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

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

То есть, если в жабе что-то не компилируется, значит этого не существует? Это метод доказательства такой? Как называется?

anonymous
()

https://habrahabr.ru/post/83269/ со ссылкой на википедию говорит: «Пусть q(x) является свойством верным относительно объектов x некоторого типа T. Тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T.»

Вспоминается: «Минимальное целое число, которое нельзя определить восемью словами».

den73 ★★★★★
()
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

Вспоминается: «Минимальное целое число, которое нельзя определить восемью словами».

Какая связь?

anonymous
()

Логичнее было бы сделать реализацию setWidth и setHeight идентичной - они должны записывать свой аргумент в оба поля класса - и width, и height.

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

Не вижу связи. Может ты ошибся, как обычно? Ляпнул не подумавши?

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

Я, как программист, был бы изрядно удивлён, если бы метод, работающий корректно для родителя, игнорировался бы потомком.

Допустим, мы делаем некоторый графический редактор, где пользователь может создавать прямоугольники и квадраты. Гуй этого редактора вызывает методы у объектов getX, getY, getHeight и getWidth, чтобы нарисовать квадратики, за которые нужно тянуть, чтобы изменить размер объекта. А когда пользователь тянет, то вызывает методы setWidth и setHeight, чтобы изменить размеры.

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

Но в этом случае по сути дела разрушается полиморфизм (выполнение внешнего воздействия требует влезание в потроха объекта, чтобы определить его тип, вместо вызов виртуального метода). Во-вторых, не используются преимущества наследования (одинаковый по функциональности код приходится повторять дважды - у квадрата появляются свой собственный setSize, который по факту выполняет то же). Таким образом ООП лишается двух из трёх основных принципов (инкапсуляция осталась вроде как, ибо мы напрямую в поля width и height не лезем) и возникает разумный вопрос «а зачем он вообще тогда нужен?».

Кстати, насчёт контрактов. Они для методов тоже есть, не? Ибо если метод можно делать, что угодно, то смысла в таком методе не очень много. Разумный контракт для того же setWidth - «меняет ширину объекта», а setHeight - «меняет высоту объекта». При этом, конечно же, гарантий, что у этих методов нет побочных эффектов нет (в данном случае побочный эффект - изменение второго размера) - это нормально. А если «setWidth может сделать что угодно, в том числе ничего», то нафиг такой метод нужен.

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

KivApple ★★★★★
()

надо всего лишь определить setWidth и setHeight так, чтобы прямоугольник становился квадратом, если у него длина и ширина равны, а квадрат становился прямоугольником, если длина и ширина не равны. как это в реальности и происходит.

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

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

В прямоугольнике этих методов быть не должно, как впрочем и setWidth/Height, они должны быть в отдельном классе - Transform. А если ещё чуть-чуть подумать, то и классы прямоугольников-квадратов не нужны - нужен всего один класс Shape содержащий массив Point + фабрика которая клепает объекты квадратов, прямоугольников, звёздочек и т.д. Если тебе нужно определять что какой-то объект квадрат, запили ещё предикаты is_square и т.п.

no-such-file ★★★★★
()
Ответ на: комментарий от KivApple

Ты всего лишь описал частный случай, специально подогнанный под обоснование твоей версии. В общем случае может быть по-разному, это в контексте рассматриваемого вопроса не важно. Уж тем более не важно, что там ожидается физическим юзером, данный слой абстракции вообще не для него, а для класса-клиента

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

Хорошо. Тогда я хочу заявить, что рассмотрение данного вопроса не имеет смысла без указания, какие контракты определены для setWidth и setHeight. Если же у метода нет чёткого контракта, то он явно не соответствует этому вашему принципу Лисков, ибо вызывающему метод объекту в этом случае не получится абстрагироваться от конкретной реализации и придётся проверять реальный класс объекта (а то мало ли, вдруг setWidth одного из потомков прямоугольника делает rm -rf /).

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от KivApple

если бы метод, работающий корректно для родителя, игнорировался бы потомком.

Тут слово «корректно», вообще не уместно, для каждого корректность своя, важна совместимость интерфейсов. Эти методы могли игнорироваться в каких-то случаях и предком, например, при превышении лимита или отрицательных значениях. К делу вообще отношения не имеет. Каждый объект сам решает что для него корректно, а что нет.

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

как уж тут исключить, когда всякий квадрат является прямоугольником, а прямоугольник с равными сторонами является квадратом.

anonymous
()

Хороший всё-таки пример того, что ООП сосёт даже в представлении таких простых вещей. С одной стороны квадрат Is-A прямоугольник, с другой стороны набор операций над квадратом, оставляющий его квадратом, меньше чем набор операций над прямоугольником, оставляющий его прямоугольником.

Основываясь на этом, принцип подстановки Лисков требует чтобы прямоугольник был наследником квадрата (наследник расширяет, но не модифицирует поведение предка). С другой стороны, квадрат is-a прямоугольник, то есть имеет смысл определять операции, не модифицирующие квадрат, как операции над прямоугольником с одинаковыми сторонами. Значит делаем квадрат абстрактным классом, делегирующим реализацию части методов наследнику (виртуальные методы setSideLength, area, perimeterLength). Полезной функциональности в классе квадрат не остаётся, да и проблемы с открытой рекурсией никто не отменял, поэтому делегируем реализацию всех методов классу прямоугольник. Сконструировать квадрат теперь не получится (абстрактный класс) поэтому создаём фабрику для конструирования квадратов и прямоугольников.

Потом выкидываем всё это на помойку, и делаем набор функций или класс для работы с прямоугольниками (с методом isSquare) или тип сумму enum Shape {Square(sideLen), Rectangle(width, height)}

red75prim ★★★
()

Лис или лиспов?

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

Никто не обратил внимание. А ведь принцип-то Лисков никуда не годится. Придётся развить тему.

«свойство, верное относительно объектов» - некорректное высказывание, которое ведёт к логическим парадоксам. Получается из серии «если Бог всемогущ, может ли он сделать камень, который он сам не сможет поднять» или «минимальное целое число, которое нельзя определить восемью словами».

Например, рассмотрим базовый класс Родитель и производный Ребёнок. Пусть q(x) формулируется как x.класс.Имя == «Родитель»

Оно автоматически не может выполняться для класса Ребёнок. Прощай, рефлексия.

Далее, рассмотрим любой функционально чистый виртуальный метод М. Пусть q(x) формулируется как «результат М(x) совпадает с <а здесь мы подставляем тело от M>». Очевидно, если мы при наследовании перекроем этот метод, то q(x) для потомка не будет соблюдаться. Как выпутаться? Видимо, M(x) должен быть в родителе абстрактным, тогда поведение как бы не определено и можно уклониться от соблюдения логики. Ничего себе: любой виртуальный метод должен быть в родителе абстрактным и перекрыть его можно не более одного раза.

Прощайте, виртуальные методы.

Но даже это не сработает: пусть свойство q(x) формулируется как «метод M(x) не выдаёт ошибку».

Прощайте, абстрактные методы.

Далее, пусть q(x) формулируется как sizeof(x) == sizeof(Родитель). Прощайте, новые поля.

Т.е. принцип Лисков вообще ничего не может означать на практике, если не сузить возможный диапазон элементов, из которых можно строить q(x). Но от виртуальных методов в любом случае ничего существенного не останется.

Потому что подтип - это лишь подмножество объектов без всяких добавлений. Фильтр. А потомок в ООП - это другой тип. Отношение наследования не является отношением подтипа, это лишь «совместимость интерфейса» или «совместимость реализации», или «возьмём родителя, поменяем в нём что-то и получим новый тип».

Вывод из этого - ООП в стиле С++ не имеет осмысленной теории, является парадоксальным творением человеческого ума и поэтому вредоносно. При многозначности святой книги неизбежны Варфоломеевская ночь, ереси и пр. Я всегда это чувствовал, а теперь, похоже, есть доказательство.

Кто разъяснит мне мою неправоту?

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

Например, рассмотрим базовый класс Родитель и производный Ребёнок. Пусть q(x) формулируется как x.класс.Имя == «Родитель» Оно автоматически не может выполняться для класса Ребёнок. Прощай, рефлексия.

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

linearisation
() автор топика
Ответ на: комментарий от den73

Пусть q(x) формулируется как «результат М(x) совпадает с <а здесь мы подставляем тело от M>». Очевидно, если мы при наследовании перекроем этот метод, то q(x) для потомка не будет соблюдаться. Как выпутаться?

А напомни, зачем ты в это впутался? Ну, в смысле - зачем тебе такая странная формулировка q(x)?

Кто разъяснит мне мою неправоту?

Анонiмус. Вижу, он уже начал.

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

Зачем мне такая формулировка q(x). Ты вообще математику в школе учил? В курсе, что такое «доказательство от противного»? Я постулирую, что принцип Лисков не соотносится с реальным ООП (с наследованием, абстрактными классами, виртуальными методами), и я это доказал. Ты можешь указать на ошибки в моём доказательстве, но такого вопроса как «зачем» в математике нет.

den73 ★★★★★
()
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

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

А тема связи с парадоксами по-прежнему не раскрыта, совершенно непонятно, какая тут связь.

Кстати, не вижу ничего парадоксального в высказывании «Если Бог всемогущ, может ли он сделать камень, который он сам не сможет поднять» Корректный ответ: может. Нет никакого парадокса. Затем он может сделать так, что вновь сможет его поднять. Время имеет значение, этот факт для математического, слабенького склада ума, видимо, является выносом мозга. Аналогичная ситуация и с парадоксом про слова. Нет в этом никаких парадоксов

linearisation
() автор топика
Ответ на: комментарий от den73

что принцип Лисков безполезен

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

и я это доказал

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

linearisation
() автор топика
Ответ на: комментарий от tailgunner

А впутался я в это затем, что пытаюсь придумать ООП для Яра. И вот я упёрся в вопрос, разрешать ли наследование. С одной стороны, очевидно, что надо, а с другой, очевидно, что тут что-то нечисто. В итоге я пока воздержался от включения наследования. Но вот думаю, если в дополнение к «корректному» абстрактному понятию «подтип» (например, массив целых является подтипом массива) включить именно понятие «наследования», т.е. «берём существующий тип, что-то в нём меняем, а что-то добавляем», то противоречия разрешаются. Хотя если вдуматься, то и массив целых не обязательно является подтипом массива произвольных значений - его можно реализовать по-другому и он физически будет другим типом. Далее, попытка записать в массив обычных значений строку «абв» прходоит, а в массив целых - нет. Значит, даже тут принцип Лисков не годится.

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

Ты вообще математику в школе учил?

Да. А ты?

В курсе, что такое «доказательство от противного»?

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

Я постулирую, что принцип Лисков безполезен, и я это доказал.

Этого ты не доказал. Ты доказал, что твое определение q(x) бесполезно.

А впутался я в это затем, что пытаюсь придумать ООП для Яр

А... ну тогда ладно, главное - чтобы тебе нравилось.

tailgunner ★★★★★
()
Последнее исправление: tailgunner (всего исправлений: 3)
Ответ на: комментарий от den73

придумать ООП для Яра. И вот я упёрся в вопрос, разрешать ли наследование

эта формулировка как раз парадоксальна, если исходить из определения ООП, по-первоисточнику. Ты, конечно, можешь и кусок говна «ООП» назвать, дело хозяйское. Современные дезигнеры примерно так и поступают

linearisation
() автор топика
Ответ на: комментарий от den73

у тебя, в данном случае, примерно такая логика. Пусть есть чистая ф-ция sum

sum = function(x){x + x}
a = 1
sum(a) --> 2
a = 2
sum(a) --> 4

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

linearisation
() автор топика
Ответ на: комментарий от tailgunner

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

Я могу. А вот теперь вопрос «зачем» уже возникает вовсю. Неужели приведённые мной примеры недостаточно вопиющи?

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

А вот теперь вопрос «зачем» уже возникает вовсю

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

linearisation
() автор топика
Ответ на: комментарий от den73

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

Я могу

Сделай это.

Неужели приведённые мной примеры недостаточно вопиющи?

Определение q(x) тупо бесполезно, поэтому пример не то, чтобы не вопиющ - вообще нерелевантен.

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

Ты доказал, что твое определение q(x) бесполезно.

У меня нет никакого своего определения. Я взял его из Википедии:

Subtype Requirement: Let ϕ ( x ) be a property provable about objects x of type T. Then ϕ ( y ) should be true for objects y of type S where S is a subtype of T.

Сейчас я смотрю первоисточник, чтобы понять, что они понимают под этим. Но в статьях, например, на Хабре, это не расшифровывается и о необходимости сузить ϕ речи не идёт. Партия сказала «надо» - комсомол ответил «есть». Победа ученика - победа школы. Поражение ученика - поражение ученика. Поражает, что программирование всё же находится рядом с математикой, а мысли всё равно спускают людям сверху.

den73 ★★★★★
()
Последнее исправление: den73 (всего исправлений: 2)
Ответ на: комментарий от den73

should be true for objects y

Ни на что не намекает? Имя твоего ребенка == *отличное от имени родителя* верно для ребенка? Где тут противоречие?

linearisation
() автор топика
Ответ на: комментарий от den73

Ты доказал, что твое определение q(x) бесполезно.

У меня нет никакого своего определения

Как нет?

den73> q(x) формулируется как «результат М(x) совпадает с <а здесь мы подставляем тело от M>»

Вот именно «подстановка тела» и делает определение q(x) бесполезным. q(x) нужно формулировать в терминах возвращаемого значения и/или побочных эффектов. И да, выбор хороших q(x) - это проектирование программы. Принцип Лисков можно применять только для отсеивания плохих.

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

What is wanted here is something like the following substitution property [6] If for each object o 1 of type S there is an object o 2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o 1 is substituted for o 2, then S is a subtype of T.

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

Более того, виртуальные методы по этому определению вовсе запрещены (потомок _не_может быть подтипом в этом смысле, если виртуальный метод используется в программе по существу и как-то переопределяет предка. Даже переопределять абстрактный метод запрещено, поскольку поведение программы меняется).

И практический вывод отсюда - нет смысла вообще говорить о принципе Лисковой, он не нужен.

den73 ★★★★★
()
Последнее исправление: den73 (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.