Kiedy stosować cechy, a nie dziedziczenie i kompozycję?

9

Istnieją trzy powszechne sposoby AFAIK na wdrożenie wielokrotnego użytku, jeśli chodzi o OOP

  1. Dziedziczenie: zwykle w celu przedstawienia związku jest (kaczka jest ptakiem)
  2. Skład: zwykle reprezentuje relację „ma związek” (samochód ma silnik)
  3. Cechy (np. Słowo kluczowe cecha w PHP): ... naprawdę nie jestem tego pewien

Chociaż wydaje mi się, że cechy mogą implementować zarówno relacje typu „a”, jak i „to”, nie jestem do końca pewien, do jakiego rodzaju modelowania był przeznaczony. Do jakich sytuacji zaprojektowano cechy?

Extrakun
źródło
Możesz wyjaśnić nieco więcej na temat cech, ale czy cechy mogą być po prostu innym słowem określającym kompozycję?
Trilarion
1
Naprawdę też chciałbym wiedzieć, dlaczego. Nie użyłem ich raz.
Andy

Odpowiedzi:

6

Cechy to kolejny sposób na komponowanie. Pomyśl o nich jako o sposobie komponowania wszystkich części klasy w czasie kompilacji (lub w czasie kompilacji JIT), składając konkretne implementacje potrzebnych części.

Zasadniczo chcesz korzystać z cech, gdy tworzysz zajęcia z różnymi kombinacjami funkcji. Sytuacja ta pojawia się najczęściej u osób piszących elastyczne biblioteki, z których mogą korzystać inni. Na przykład, oto deklaracja klasy testu jednostkowego, którą ostatnio napisałem za pomocą ScalaTest :

class TestMyClass
  extends WordSpecLike
  with Matchers
  with MyCustomTrait
  with BeforeAndAfterAll
  with BeforeAndAfterEach
  with ScalaFutures

Struktury testów jednostkowych mają mnóstwo różnych opcji konfiguracji, a każdy zespół ma inne preferencje co do tego, jak chcą to robić. Umieszczając opcje w cechach (które są mieszane przy użyciu withw Scali), ScalaTest może zaoferować wszystkie te opcje bez konieczności tworzenia nazw klas, takich jak WordSpecLikeWithMatchersAndFutures, lub ton flagi logicznej środowiska wykonawczego, takich jak WordSpecLike(enableFutures, enableMatchers, ...). Ułatwia to przestrzeganie zasady otwartej / zamkniętej . Możesz dodać nowe funkcje i nowe kombinacje funkcji, po prostu dodając nową cechę. Ułatwia także przestrzeganie zasady segregacji interfejsów , ponieważ łatwo można umieścić w funkcji cechy, które nie są powszechnie potrzebne.

Cechy są również dobrym sposobem na umieszczenie wspólnego kodu w kilku klasach, które nie mają sensu współdzielić hierarchii dziedziczenia. Dziedziczenie jest związkiem bardzo ściśle powiązanym i nie powinieneś ponosić tych kosztów, jeśli możesz mu pomóc. Cechy są relacją znacznie bardziej luźno powiązaną. W powyższym przykładzie MyCustomTraitz łatwością korzystałem z udawanej implementacji bazy danych między kilkoma niezwiązanymi ze sobą klasami testów.

Wstrzykiwanie zależności osiąga wiele takich samych celów, ale w czasie wykonywania na podstawie danych wejściowych użytkownika zamiast w czasie kompilacji na podstawie danych programisty. Cechy są również przeznaczone bardziej dla zależności, które są semantycznie częścią tej samej klasy. Gromadzisz części jednej klasy, a nie dzwonisz do innych klas z innymi obowiązkami.

Ramy wstrzykiwania zależności osiągają wiele takich samych celów w czasie kompilacji w oparciu o dane programistyczne, ale w dużej mierze są obejściem dla języków programowania bez odpowiedniego wsparcia cech. Cechy wprowadzają te zależności do dziedziny sprawdzania typu kompilatora, z czystszą składnią, z prostszym procesem kompilacji, który czyni wyraźniejsze rozróżnienie między zależnościami czasu kompilacji i środowiska wykonawczego.

Karl Bielefeldt
źródło
Cześć, to świetna odpowiedź. Czy mogę zapytać, jak zdecydować, kiedy użyć cech, a kiedy zastrzyk zależności. który wydaje się osiągać to samo.
Extrakun
Sprytna obserwacja. Zobacz moją edycję.
Karl Bielefeldt,
Dziękuję za napisanie tego. Jestem ciekawy twojej odpowiedzi na temat cech takich jak kompozycja @KarlBielefeldt. Twoja klasa może być używana wszędzie tam, gdzie ta cecha jest potrzebna, a ty nadal cierpiałbyś z powodu takiej samej kruchości jak w przypadku zwykłego interfejsu. Wydaje się więc, że jest to bliższe poddziałaniu i dziedziczeniu, prawda? Nie jestem również pewien, jak jest podobny do DI ... jeśli masz zdefiniowane różne klasy, które rozszerzają cechy, to czy te konkretne zależności nie są zdefiniowane gdziekolwiek w hierarchii, w której klasa jest zdefiniowana, a nie w punkcie początkowym / początkowym kod? Dzięki!
allstar
Cechy mogą być używane tak jak interfejsy, ze względu na ich właściwości dziedziczenia, ale to nie czyni ich wyjątkowymi. withOperator jest to, co je odróżnia, a withto działanie kompozycji. I tak, cechy zastępują DI bez konieczności definiowania w punkcie wejścia kodu. To jedna z rzeczy, która sprawia, że ​​moim zdaniem są lepsze.
Karl Bielefeldt