Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.145.58.169] |
|
Страницы: (33) [1] 2 3 ... 32 33 ( Перейти к последнему сообщению ) |
Сообщ.
#1
,
|
|
|
Предлагаю похоливарить на данную тему.
Коротко о trait'ах, impl'ах и данных на примере Rust. Вместо "классического" ОО подхода, Rust предоставляет отдельные ортогональные механизмы. Если классы у нас описывают и методы и данные, то в Rust мы задаем отдельно данные: struct Circle { x: f64, y: f64, radius: f64, } и отдельно наборы методов: impl Circle { fn area(&self) -> f64 { std::f64::consts::PI * (self.radius * self.radius) } } Можно задавать несколько блоков impl для типа. Если классы предоставляют возможность наследования, то в Rust мы описываем интерфейсы посредством trait'а: trait HasArea { fn area(&self) -> f64; } А реализацию trait'а для нашего типа описываем в соответствующем блоке impl. struct Circle { x: f64, y: f64, radius: f64, } impl HasArea for Circle { fn area(&self) -> f64 { std::f64::consts::PI * (self.radius * self.radius) } } struct Square { x: f64, y: f64, side: f64, } impl HasArea for Square { fn area(&self) -> f64 { self.side * self.side } } Можно добавлять реализацию того или иного trait'а для того или иного типа, без внесения каких-либо изменений непосредственно в сам тип. Наследование же поддерживается только на уровне наследование trait'ов: trait Foo { fn foo(&self); } trait FooBar : Foo { fn foobar(&self); } Причем для реализующего trait типа это будет означать, что тип так же должен иметь impl и для "базового" trait'а: struct Baz; impl Foo for Baz { fn foo(&self) { println!("foo"); } } impl FooBar for Baz { fn foobar(&self) { println!("foobar"); } } В trait'ах можно указывать реалиализацию методов по умолчанию: trait Foo { fn bar(&self); fn baz(&self) { println!("We called baz."); } } Так же trait'ы используются для задания ограничений на generic-параметры, но это совсем другая история... Итого, мы имеем три отдельных механизма: для описания интерфейса и иерархии интерфейсов, для задания структур данных и для задания реализаций интерфейсов для тех или иных структур данных. Из плюсов сразу можно указать ортогональность и гибкость, из минусов - сложные описания в случае "классических" ОО-иерархий и полное отсутствие механизма наследования реализаций. Небольшой пример с rustbyexample.com(предыдущие примеры были с doc.rust-lang.org): trait Animal { // Static method signature; `Self` refers to the implementor type fn new(name: &'static str) -> Self; // Instance methods, only signatures fn name(&self) -> &'static str; fn noise(&self) -> &'static str; // A trait can provide default method definitions fn talk(&self) { // These definitions can access other methods declared in the same // trait println!("{} says {}", self.name(), self.noise()); } } struct Dog { name: &'static str } impl Dog { fn wag_tail(&self) { println!("{} wags tail", self.name); } } // Implement the `Animal` trait for `Dog` impl Animal for Dog { // Replace `Self` with the implementor type: `Dog` fn new(name: &'static str) -> Dog { Dog { name: name } } fn name(&self) -> &'static str { self.name } fn noise(&self) -> &'static str { "woof!" } // Default trait methods can be overridden fn talk(&self) { // Traits methods can access the implementor methods self.wag_tail(); println!("{} says {}", self.name, self.noise()); } } struct Sheep { naked: bool, name: &'static str } impl Sheep { fn is_naked(&self) -> bool { self.naked } fn shear(&mut self) { if self.is_naked() { // Implementor methods can use the implementor's trait methods println!("{} is already naked!", self.name()); } else { println!("{} gets a haircut", self.name); self.talk(); self.naked = true; } } } impl Animal for Sheep { fn new(name: &'static str) -> Sheep { Sheep { name: name, naked: false } } fn name(&self) -> &'static str { self.name } fn noise(&self) -> &'static str { if self.is_naked() { "baaah" } else { "baaaaaaaaaaaah" } } } fn main() { // Type annotation is necessary in this case let mut dolly: Sheep = Animal::new("Dolly"); let spike: Dog = Animal::new("Spike"); // TODO ^ Try removing the type annotations dolly.shear(); spike.talk(); dolly.talk(); } |
Сообщ.
#2
,
|
|
|
то есть унаследовать данные в Rust можно только добавив сеттеры/геттеры?
И какое это дает преимущество? Ведь те же яйца, вид сбоку. |
Сообщ.
#3
,
|
|
|
Цитата D_KEY @ из минусов - сложные описания в случае "классических" ОО-иерархий и полное отсутствие механизма наследования реализаций. Насколько я знаю, различные варианты реализации наследования обсуждаются. Ну а пока да, многие привычные вещи делать неудобно. |
Сообщ.
#4
,
|
|
|
DarkEld3r, это не моя цитата
|
Сообщ.
#5
,
|
|
|
Не знаю как так получилось. Вроде, жал на "быстрая цитата". Пофиксил.
|
Сообщ.
#6
,
|
|
|
Цитата DarkEld3r @ Насколько я знаю, различные варианты реализации наследования обсуждаются. Не знаю, насколько это уместно в схеме с этими тремя механизмами... Мне кажется, что вполне достаточно какого-то механизма для делегирования реализации trait полю с возможностью переопределения методов. |
Сообщ.
#7
,
|
|
|
Цитата D_KEY @ Мне кажется, что вполне достаточно какого-то механизма для делегирования реализации trait полю с возможностью переопределения методов. Честно говоря, не вижу принципиальной разницы. Разве что вариант с "делегированием" позволит более естественно реализовывать "множественноe наследование", которое иначе могут и запретить. Ну и ведь за делегированием будет скрываться всё то же наследование только не данных и реализации одновременно, а только реализации. P.S. Что-то не получается холивара. |
Сообщ.
#8
,
|
|
|
Подброшу дровишек: при "реализации ООП через трейты" не возникает проблемы как в соседнем топике (что от чего наследовать квадрат от прямоугольника или прямоугольник от квадрата).
|
Сообщ.
#9
,
|
|
|
Цитата DarkEld3r @ Подброшу дровишек: при "реализации ООП через трейты" не возникает проблемы как в соседнем топике (что от чего наследовать квадрат от прямоугольника или прямоугольник от квадрата). Можно простецкий пример решения этой псевдопроблемы на Rust? |
Сообщ.
#10
,
|
|
|
Цитата applegame @ Можно простецкий пример решения этой псевдопроблемы на Rust? "Проблема" ведь в том, что если мы наследуем квадрат от прямоугольника, то получим лишний бесполезный член данных. В расте нет наследования данных - нет проблемы. Можно и кодом, хотя это, вряд ли, будет интересно: trait Rectangle { fn area(&self) -> i32; } struct RectangleData { a: i32, b: i32, } impl Rectangle for RectangleData { fn area(&self) -> i32 { self.a * self.b } } trait Square : Rectangle { // Методы уникальные для квадрата } struct SquareData { a: i32, } impl Rectangle for SquareData { fn area(&self) -> i32 { self.a * self.a } } impl Square for SquareData { // ... } |
Сообщ.
#11
,
|
|
|
Цитата DarkEld3r @ Никто не мешает НЕ наследовать данные и в C++. Аналог твоего растовского кода на плюсах:"Проблема" ведь в том, что если мы наследуем квадрат от прямоугольника, то получим лишний бесполезный член данных. В расте нет наследования данных - нет проблемы. struct Rectangle { virtual int area() = 0; }; struct RectangleImpl : public Rectangle { int a, b; int area() { return a * b; } }; struct Square : public Rectangle { }; struct SquareImpl : public Square { int a; int area() { return a * a; } }; ... Square* square = new SquareImpl(); Rectangle* rectangle = new RectangleImpl(); P.S. ваш Rust таки уродлив, даже плюсы симпатичней. |
Сообщ.
#12
,
|
|
|
Вообще, деление на наследуемые классы, реализуемые интерфейсы и добавляемые примеси, по-моему, искусственное и надуманное. Вполне достаточно реализуемых классов с перекрываемыми методами. Плюс нужен механизм для разрешения конфликтов имен, например, возможность реализации классов в качестве "компонента" с возможностью доступа к неприватным свойствам и "компонентам" "хозяина". Плюс аналог паскалевского оператора AS. Этого вполне достаточно для реализации самых извращенных схем наследования. Или нет?
|
Сообщ.
#13
,
|
|
|
Цитата applegame @ Никто не мешает НЕ наследовать данные и в C++. Верно, но никто и не запрещает. Хорошо это или нет - спорный вопрос (лично я считаю, что хорошо в рамках С++), тем не менее, если требовать всегда "выделять интерфейс", то проблемы наверняка не будет. Ну а если наследование реализуют так как D_KEY предлагал, то и кода станет меньше и минусов, вроде, не будет. Цитата applegame @ P.S. ваш Rust таки уродлив, даже плюсы симпатичней. Аргументы будут? |
Сообщ.
#14
,
|
|
|
Цитата DarkEld3r @ Нет, это мое субъективное оценочное мнение, которое можно проигнорировать. Не нравится мне синтксис Rust'а и все. Аргументы будут? |
Сообщ.
#15
,
|
|
|
Цитата AVA12 @ Вообще, деление на наследуемые классы, реализуемые интерфейсы и добавляемые примеси, по-моему, искусственное и надуманное. Если я правильно понял мысль, то идея в следующем: "добавляемые примеси" (трейты), в любом случае, должны определяться отдельно так как они не привязаны к типу - можно определять для любого подходящего. Реализация тоже не может содержаться внутри типа - так как она может добавляться к существующим типам. "Просто методы", наверное, могли бы определяться "внутри типа", чтобы было похоже на "обычное ООП", но во первых, методы тоже можно добавлять. Во вторых, так механизм выглядит единообразно с трейтами. Цитата AVA12 @ Вполне достаточно реализуемых классов с перекрываемыми методами. Плюс нужен механизм для разрешения конфликтов имен, например, возможность реализации классов в качестве "компонента" с возможностью доступа к неприватным свойствам и "компонентам" "хозяина". Плюс аналог паскалевского оператора AS. Тут я не понял, можно развернуть мысль? Что именно не нравится и как сделать лучше. |