Pull to refresh

Comments 30

trait - это ведь интерфейс? То есть в расте есть только наследование от интерфейса?

trait - это ведь интерфейс?

Похож, концепцию реализует ту же самую, но шире по возможностям (если сравнивать например с Java interface). Вот хорошее сравнение:

StackOverflow - Is Rust trait the same as Java interface

То есть в расте есть только наследование от интерфейса?

Вопрос имхо не корректен.:) Есть "наследование от интерфейса" (trait implementation), есть ряд других техник для выполнения концепции наследования. Данные "наследуются" через композицию (Composition over inheritance). Трейты можно наследовать (Rust docs: Supertrait), но это не наследование интерфейсных классов в обычном понимании. Наследования в классическом смысле в Rust нет.

Вообще говоря, вместе с dyn trait, мы получаем настоящий эквивалент С++ классов (... окей, в том объёме как я его знаю, ибо С++##22 неисчерпаем как атом). virtual table есть? Остаётся вопрос с наследованием, и это как раз сущность, которую я стараюсь не использовать даже в языках, где она есть и факультативная, потому что чтение кода между двумя "разно наследованными" классами от одного базового приводит к stack overflow у кожанного мешка.

Не совсем эквивалент... Наверное, если запретить базовым классам в плюсах иметь данные, то будет похоже.
А вообще, согласен. Наследование нужно только тогда, когда оно, действительно, упрощает жизнь именно в контексте конкретной задачи. Хорошая задача на наследование: стоит ли квадрат наследовать от прямоугольника.
С одной стороны, квадрат, действительно является прямоугольником, и, казалось бы, что может пойти не так?) Но, если в нашем контексте мы ожидаем, что для абстракции "прямоугольник", увеличение одной стороны пропорционально увеличивает площадь, то для квадрата это не сработает. В данном контексте нарушится LSP. Поэтому не нужно слепо руководствоваться внутренними ощущениями или сторонними фактами, продумывая архитектуру. Надо смотреть насколько то или иное решение стыкуется с конкретной задачей.

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

Отношение подтипизации может быть реализовано разными способами:

  • Через сужение типа: квадрат является частным случаем прямоугольника

  • Через расширение типа: прямоугольник является квадратом с дополнительным значением размера

  • Через пересечение: квадрат и прямоугольник являются подтипами абстрактной фигуры, характеризуемой координатами

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

Я говорил про наследование. Жесткое отношение «is-a». Какой смысл наследовать квадрат от прямоугольника, если не получается соблюдать инвариант пропорциональности квадрата? Квадрат не ведет себя полностью как прямоугольник. Не получится задать ему разную высоту и ширину как для прямоугольника.
UFO just landed and posted this here
С иммутабельным логично. Но работая с мутабельным интерфейсом прямоугольника совсем не хочется при изменении одной стороны внезапно получить пропорциональное изменение другой.

Если это графический редактор, то очень даже хочется.

Наследование описывает отношение "является" между двумя объектами

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

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

LSP это просто популярный мем, ни какой особой роли он там не играет.

Так а ООП то тут при чем?

От того, что наследование реализаций классов есть в плюсах и нет в расте и интерфейсах - как это ООП мешает?

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

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

JavaScript почти 20 лет прямым текстом всем доказывали, что наследование прототипов это тоже ООП. Но все успокоились только после того, как в язык завезли классы.

В Rust аналогичная картина. Однако они не позиционируют себя как язык с полноценной поддержкой ООП, а честно предупреждают об особенностях, компромиссах и возможностях сделать иначе и лучше https://doc.rust-lang.org/book/ch17-00-oop.html

Справедливости ради, да, в растбуке хорошо написано про эдж кейсы

Это скорее мое мнение, мне кажется, что это инструменты, которые используются вместе с ООП
А для организации объектов все есть.

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

>объект потомка не обязан «являться» корректным экземпляром родителя
Вот такой случай как раз и есть нарушение LSP. И любой клиент, который заложится на корректность поведения, может сломаться.

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

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

Простейший пример на c++:

class Derived : Base {};

Ну-ка, кто скажет почему при таком наследовании нет полиморфизма?

The intuitive idea of a subtype is one whose objects provide all the behavior of objects of another type (the supertype) plus something extra. What is wanted here is something like the following substitution property [S]: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2, then S is a subtype of T.

...We are using the words “subtype” and “supertype” here to emphasize that now we are talking about a semantic distinction. By contrast, “subclass” and “superclass” are simply linguistic concepts in programming languages that allow programs to be built in a particular way.

В своей работе из 1987 Барбара и сотоварищи думали над формализацией отношения тип-подтип и в качестве отправной точки взяли определение из более ранней работы своего коллеги. Позднее они нашли в таком определении изъяны и пытались исправить, усложнив определение этого своего behaviour subtyping. Но в целом данная ветка исследований оказалась не особо практичной (как многое другое, пытающееся опираться на семантику внешнего мира). Сейчас все это представляет собой больше историческую ценность, может быть дающую какие-то инсайты и возможность не наступать на уже кем-то пройденные грабли. То как подаётся LSP в SOLID для адептов ООП это каргокульт в чистом виде.

>возможность не наступать на уже кем-то пройденные грабли
А вы считаете, этого мало? Ну в смысле, опять же — если вы умный, высококвалифицированный и т.п., и не наступаете и так (отчего бы и нет, вполне верю), это же не значит, что своему наследнику (человеку) нужно оставить проект, который он сломает, потому что окажется не таким умным, квалифицированным и т.п.? Разумеется, LSP не серебряная пуля, это в целом не более чем хорошая практика, если так делать — вы скорее всего не сломаете проект добавляя наследников.

вы спорите об эффективности LSP исходя из предположения, что перед нами всегда стоит задача, которую он решает. Спор же был как раз не о таких случаях. Какой смысл в LSP если мы вообще не собираемся использовать потомка в качестве родителя? Наследование ведь может являться и просто деталью реализации объекта.

Я скорее возражал против некоторой расплывчатости вашей формулировки в первом комментарии данной ветки. Да, сейчас вижу что там было и про «в общем случае» — и с таким уточнением скорее согласен.

В целом мне кажется, что раст можно назвать вполне поддерживающим ОО подход.

Несмотря на отсутствия наследования реализаций (а это не единственный вариант организации связей) и некоторые особенности, в нем есть все нужное для организации кода в оо стиле - разбиение кода на независимые объекты со своим скрытым сложным внутренним состоянием и методами их обработки.

Если я не ошибаюсь, то динамический полиморфизм в Расте, не без помощи компилятора, превращается в статический.

Компилятор проходится по всему коду и "раскрывает" код для конкретных структур, которые используются разработчиком.

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

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

Чем-то сильно напоминает подход объектного паскаля (Delphi/fpc). Там инкапсуляция тоже была больше ориентирована на модули, чем на классы. Потом для тех, кому надо было, как в плюсах, даже сделали разделение между public и published.

public и published как раз пришли в плюсы из Delphi, а не наоборот. Да и к инкапсуляции оно отношения не имеет...

public и published между собой ничем не отличаются, published = public в рамках ООП. published - это информация только для дизайнтам работы. Свойства (именно свойства, потому что ничего иного смысла добавлять туда нет), опубликованные в этой области видимости будут видны в панели свойств при работе с компонентом в дизайнтам.

Sign up to leave a comment.