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. Поэтому не нужно слепо руководствоваться внутренними ощущениями или сторонними фактами, продумывая архитектуру. Надо смотреть насколько то или иное решение стыкуется с конкретной задачей.
Отношение подтипизации может быть реализовано разными способами:
Через сужение типа: квадрат является частным случаем прямоугольника
Через расширение типа: прямоугольник является квадратом с дополнительным значением размера
Через пересечение: квадрат и прямоугольник являются подтипами абстрактной фигуры, характеризуемой координатами
Через объединение: квадрат и прямоугольник являются независимыми типами, а вот плита может быть и квадратом, и прямоугольником в зависимости от ракурса.
Наследование описывает отношение "является" между двумя объектами
Нет, это полиморфизм описывает отношение "является". Наследование именно про переиспользование логики и данных, в общем случае объект потомка не обязан "являться" корректным экземпляром родителя.
Наследование в стиле C++ это в первую очередь техника повторного использования кода, чтобы не писать по десять раз одно и тоже, с возможностью подменять отдельные запчасти.
LSP это просто популярный мем, ни какой особой роли он там не играет.
Так а ООП то тут при чем?
От того, что наследование реализаций классов есть в плюсах и нет в расте и интерфейсах - как это ООП мешает?
Отдельный вопрос, что наследование реализаций рекомендуют не использовать, потому что оно порождает хрупкий код и ломает инкапсуляцию
ООП не существует в вакууме. Вокруг него сложилась определенная культура с классами, SOLID, паттернами и т.п. Заявка о поддержке ООП создаёт у разработчиков определенные ожидания, которые превращаются в разочарования и сорванные сроки, если что-то вдруг идёт не так.
JavaScript почти 20 лет прямым текстом всем доказывали, что наследование прототипов это тоже ООП. Но все успокоились только после того, как в язык завезли классы.
В Rust аналогичная картина. Однако они не позиционируют себя как язык с полноценной поддержкой ООП, а честно предупреждают об особенностях, компромиссах и возможностях сделать иначе и лучше https://doc.rust-lang.org/book/ch17-00-oop.html
>объект потомка не обязан «являться» корректным экземпляром родителя
Вот такой случай как раз и есть нарушение 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 если мы вообще не собираемся использовать потомка в качестве родителя? Наследование ведь может являться и просто деталью реализации объекта.
это override
В целом мне кажется, что раст можно назвать вполне поддерживающим ОО подход.
Несмотря на отсутствия наследования реализаций (а это не единственный вариант организации связей) и некоторые особенности, в нем есть все нужное для организации кода в оо стиле - разбиение кода на независимые объекты со своим скрытым сложным внутренним состоянием и методами их обработки.
Если я не ошибаюсь, то динамический полиморфизм в Расте, не без помощи компилятора, превращается в статический.
Компилятор проходится по всему коду и "раскрывает" код для конкретных структур, которые используются разработчиком.
Грубо говоря, компилятор дублирует код, вместо разработчика, для каждой структуры и никакого динамического полиморфизма в готовой программе не остаётся.
Инкапсуляция — сокрытие внутренней реализации объекта
от внешнего пользователя. В Rust эта идея реализуется с помощью
приватных полей и методов структур, используя механизм модулей.
Чем-то сильно напоминает подход объектного паскаля (Delphi/fpc). Там инкапсуляция тоже была больше ориентирована на модули, чем на классы. Потом для тех, кому надо было, как в плюсах, даже сделали разделение между public и published.
public и published как раз пришли в плюсы из Delphi, а не наоборот. Да и к инкапсуляции оно отношения не имеет...
public и published между собой ничем не отличаются, published = public в рамках ООП. published - это информация только для дизайнтам работы. Свойства (именно свойства, потому что ничего иного смысла добавлять туда нет), опубликованные в этой области видимости будут видны в панели свойств при работе с компонентом в дизайнтам.
Rust и ООП