2

Kilka słów o traitsach

Kod źródłowy (source code)

Prawdopodobnie każdy student studiów informatycznych wie czym są klasy i interfejsy w programowaniu obiektowym. Niekoniecznie rozwiązują one jednak wszystkie problemy, które możemy spotkać w czasie rozwoju tworzonych przez nas systemów informatycznych i czasem dobrze byłoby użyć czegoś pośredniego. Zacznijmy jednak od początku.

Dziedziczenie służy do wprowadzania pewnego zachowania z klasy bazowej do klasy potomnej. W nowszych językach obiektowych niedopuszczalne jest jednak wielodziedziczenie z powodu możliwości wystąpienia konfliktu nazw dziedziczonych metod. Z tego powodu wprowadzono interfejsy (których można implementować wiele w jednej klasie), które ustalają nam pewne zachowania… jednak bez ich konkretnej implementacji.

Aby umożliwić przekazywanie funkcjonalności pomiędzy klasami (wraz z implementacją) wymyślono co najmniej kilka rozwiązań, np. domieszki (mixins) czy poruszane w tym artykule traitsy (cechy), opracowane przez Software Composition Group. W zasadzie można uznać, że są czymś pośrednim pomiędzy klasą bazową, a interfejsem, chociaż można również powiedzieć, że jest to specjalny typ klasy bazowej, który umożliwia wielodziedziczenie.

Bardzo dobrym przykładem jak działa taki mechanizm jest fragment kodu z dokumentacji języka PHP, który raczej nie wymaga dodatkowego komentarza:

  1. <?php
  2. trait Hello {
  3.     public function sayHello() {
  4.         echo 'Hello ';
  5.     }
  6. }
  7.  
  8. trait World {
  9.     public function sayWorld() {
  10.         echo 'World';
  11.     }
  12. }
  13.  
  14. class MyHelloWorld {
  15.     use Hello, World;
  16.     public function sayExclamationMark() {
  17.         echo '!';
  18.     }
  19. }
  20.  
  21. $o = new MyHelloWorld();
  22. $o->sayHello();
  23. $o->sayWorld();
  24. $o->sayExclamationMark();
  25. ?>
  26.  
  27. //wynik: Hello World!

W razie konfliktu nazw zostanie zwrócony błąd. Można również użyć specjalnego operatora insteadof, który zdefiniuje, która metoda ma być brana pod uwagę. Trzeba jednak wspomnieć, że nie wszystkie języki programowania posiadają wsparcie dla traitsów. Twórcy niektórych z nich twierdzą, że ich języki mają wsparcie dla traitsów, ale w taki sposób, że niekiedy jest to po prostu zwykła ściema (podobnie jak np. uważam, że przeładowanie metod w PHP jest ściemą). Niemniej trzeba założyć, że PHP, Scala czy Python całkiem nieźle obsługują tę funkcjonalność.

Sposób, który przedstawiłem powyżej pozwala na statyczną zmianę zachowania klas (czyli ustaloną na etapie kompilacji). Można jednak pójść o krok dalej i spróbować zmieniać to zachowanie dynamicznie (w ciągu działania programu). W ten sposób można chociażby uniknąć tzw. eksplozji podklas we wzorcu projektowym stan. Jednym ze sposobów na rozwiązanie tego problemu jest zastosowanie replaceable opracowanego przez Lorenzo Bettiniego.  Niestety, z tego co mi wiadomo, obecnie żaden język programowania nie wspiera tego rozwiązania. A szkoda, bo zmiana zachowania klas w locie wydaje się całkiem interesująca i idea traitsów stanowiłaby coś więcej niż tylko kolejny sposób na obejście wielodziedziczenia (inna sprawa, że raczej nie nadawałoby się to do wielkich projektów ze względu na problemy z debugowaniem).

Podsumowując: jeśli tylko programiści zdecydowali by się na rozwój traitsów (a szczególnie replaceable) to języki programowania zyskałyby kolejną bardzo ważną funkcjonalność, chociaż ciężko byłoby taki kod debugować, ze względu na zmiany zachowania klas podczas wykonywania programu. Można by jednak w o wiele bardziej elegancki sposób poradzić sobie z niektórymi problemami (np. ze wspomnianą eksplozją podklas we wzorcu stan). Z powodu jednak niezbyt wielkiego zainteresowania tą funkcjonalnością najlepiej dobrze poznać wzorce projektowe i za ich pomocą starać się rozwiązywać takie problemy. Kto wie, być może za kilkadziesiąt lat temat traitsów wróci do łask, gdy developerzy zmienią swoje nastawienie do programowania.

2 Comments

  1. Od Javy8, można dostarczać do interfejsów domyślne implementacje metod, które zachowują się w podobny sposób, to samo w C#.
    Samo replacable i magiczne zmiany zachowania w runtime’ie wydają mi się potencjalnym źródłem błędów. Dodatkowy obiekt z implementacją metody plus wstrzykiwanie zależności jest dużo bardziej przejrzyste, a dostarczą dokładnie tę samą funkcjonalność, o ile dobrze zrozumiałem koncepcję 😉

    • Dzięki takim komentarzom zawsze można dowiedzieć się czegoś nowego 😀 Dzięki 🙂

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *