На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! Правила раздела:
1. Название темы - краткое описание кто/что против кого/чего
2. В первом сообщении - список параметров, по которым идет сравнение.
3. Старайтесь аргументировать свои высказывания. Фразы типа "Венда/Слюникс - ацтой" считаются флудом.
4. Давайте жить дружно и не доводить обсуждение до маразма и личных оскорблений.
Модераторы: Модераторы, Комодераторы
Страницы: (33) [1] 2 3 ...  32 33  ( Перейти к последнему сообщению )  
> trait + impl vs class , навеяно Rust'ом
    Предлагаю похоливарить на данную тему.

    Коротко о trait'ах, impl'ах и данных на примере Rust.
    Вместо "классического" ОО подхода, Rust предоставляет отдельные ортогональные механизмы.
    Если классы у нас описывают и методы и данные, то в Rust мы задаем отдельно данные:
    ExpandedWrap disabled
      struct Circle {
          x: f64,
          y: f64,
          radius: f64,
      }


    и отдельно наборы методов:

    ExpandedWrap disabled
      impl Circle {
          fn area(&self) -> f64 {
              std::f64::consts::PI * (self.radius * self.radius)
          }
      }

    Можно задавать несколько блоков impl для типа.

    Если классы предоставляют возможность наследования, то в Rust мы описываем интерфейсы посредством trait'а:

    ExpandedWrap disabled
      trait HasArea {
          fn area(&self) -> f64;
      }


    А реализацию trait'а для нашего типа описываем в соответствующем блоке impl.
    ExpandedWrap disabled
      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'ов:
    ExpandedWrap disabled
      trait Foo {
          fn foo(&self);
      }
       
      trait FooBar : Foo {
          fn foobar(&self);
      }


    Причем для реализующего trait типа это будет означать, что тип так же должен иметь impl и для "базового" trait'а:
    ExpandedWrap disabled
      struct Baz;
       
      impl Foo for Baz {
          fn foo(&self) { println!("foo"); }
      }
       
      impl FooBar for Baz {
          fn foobar(&self) { println!("foobar"); }
      }


    В trait'ах можно указывать реалиализацию методов по умолчанию:
    ExpandedWrap disabled
      trait Foo {
          fn bar(&self);
       
          fn baz(&self) { println!("We called baz."); }
      }


    Так же trait'ы используются для задания ограничений на generic-параметры, но это совсем другая история...

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

    Небольшой пример с rustbyexample.com(предыдущие примеры были с doc.rust-lang.org):
    ExpandedWrap disabled
      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();
      }
      то есть унаследовать данные в Rust можно только добавив сеттеры/геттеры?
      И какое это дает преимущество? Ведь те же яйца, вид сбоку.
        Цитата D_KEY @
        из минусов - сложные описания в случае "классических" ОО-иерархий и полное отсутствие механизма наследования реализаций.

        Насколько я знаю, различные варианты реализации наследования обсуждаются. Ну а пока да, многие привычные вещи делать неудобно.
        Сообщение отредактировано: DarkEld3r -
          DarkEld3r, это не моя цитата :)
            Не знаю как так получилось. Вроде, жал на "быстрая цитата". :) Пофиксил.
              Цитата DarkEld3r @
              Насколько я знаю, различные варианты реализации наследования обсуждаются.

              Не знаю, насколько это уместно в схеме с этими тремя механизмами... Мне кажется, что вполне достаточно какого-то механизма для делегирования реализации trait полю с возможностью переопределения методов.
                Цитата D_KEY @
                Мне кажется, что вполне достаточно какого-то механизма для делегирования реализации trait полю с возможностью переопределения методов.

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

                P.S. Что-то не получается холивара. :D
                Сообщение отредактировано: DarkEld3r -
                  Подброшу дровишек: при "реализации ООП через трейты" не возникает проблемы как в соседнем топике (что от чего наследовать квадрат от прямоугольника или прямоугольник от квадрата). :D
                    Цитата DarkEld3r @
                    Подброшу дровишек: при "реализации ООП через трейты" не возникает проблемы как в соседнем топике (что от чего наследовать квадрат от прямоугольника или прямоугольник от квадрата). :D

                    Можно простецкий пример решения этой псевдопроблемы на Rust?
                      Цитата applegame @
                      Можно простецкий пример решения этой псевдопроблемы на Rust?

                      "Проблема" ведь в том, что если мы наследуем квадрат от прямоугольника, то получим лишний бесполезный член данных. В расте нет наследования данных - нет проблемы. :D

                      Можно и кодом, хотя это, вряд ли, будет интересно:
                      ExpandedWrap disabled
                        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 {
                            // ...
                        }
                        Цитата DarkEld3r @
                        "Проблема" ведь в том, что если мы наследуем квадрат от прямоугольника, то получим лишний бесполезный член данных. В расте нет наследования данных - нет проблемы. :D
                        Никто не мешает НЕ наследовать данные и в C++. Аналог твоего растовского кода на плюсах:
                        ExpandedWrap disabled
                          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 таки уродлив, даже плюсы симпатичней.
                        Сообщение отредактировано: applegame -
                          Вообще, деление на наследуемые классы, реализуемые интерфейсы и добавляемые примеси, по-моему, искусственное и надуманное. Вполне достаточно реализуемых классов с перекрываемыми методами. Плюс нужен механизм для разрешения конфликтов имен, например, возможность реализации классов в качестве "компонента" с возможностью доступа к неприватным свойствам и "компонентам" "хозяина". Плюс аналог паскалевского оператора AS. Этого вполне достаточно для реализации самых извращенных схем наследования. Или нет?
                            Цитата applegame @
                            Никто не мешает НЕ наследовать данные и в C++.

                            Верно, но никто и не запрещает. Хорошо это или нет - спорный вопрос (лично я считаю, что хорошо в рамках С++), тем не менее, если требовать всегда "выделять интерфейс", то проблемы наверняка не будет. Ну а если наследование реализуют так как D_KEY предлагал, то и кода станет меньше и минусов, вроде, не будет.

                            Цитата applegame @
                            P.S. ваш Rust таки уродлив, даже плюсы симпатичней.

                            Аргументы будут?
                              Цитата DarkEld3r @
                              Аргументы будут?
                              Нет, это мое субъективное оценочное мнение, которое можно проигнорировать. Не нравится мне синтксис Rust'а и все.
                                Цитата AVA12 @
                                Вообще, деление на наследуемые классы, реализуемые интерфейсы и добавляемые примеси, по-моему, искусственное и надуманное.

                                Если я правильно понял мысль, то идея в следующем: "добавляемые примеси" (трейты), в любом случае, должны определяться отдельно так как они не привязаны к типу - можно определять для любого подходящего. Реализация тоже не может содержаться внутри типа - так как она может добавляться к существующим типам.

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

                                Цитата AVA12 @
                                Вполне достаточно реализуемых классов с перекрываемыми методами. Плюс нужен механизм для разрешения конфликтов имен, например, возможность реализации классов в качестве "компонента" с возможностью доступа к неприватным свойствам и "компонентам" "хозяина". Плюс аналог паскалевского оператора AS.

                                Тут я не понял, можно развернуть мысль? Что именно не нравится и как сделать лучше.
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:
                                Страницы: (33) [1] 2 3 ...  32 33


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0519 ]   [ 16 queries used ]   [ Generated: 28.03.24, 17:05 GMT ]