Ładowanie
popup

Method Chaining

2010-10-04 10:56:39

Dziś napiszę o często stosowanej przeze mnie (i ogólnie w programowaniu wysokopoziomowym) technice programowania, mianowicie - "Method Chaining". Główną jej zaletą jest zwiększenie czytelności kodu, który staje się czytelniejszy i po prostu krótszy. Dobry przykład zastosowania tej techniki znajdziemy w bibliotece jQuery, albo w standardowej bibliotece C++ (choć przez przeładowanie operatorów << i >> można jej od razu nie dostrzec). Polega ona na wywoływaniu kilku funkcji na rzecz danego obiektu w jednej instrukcji. np.:

obiekt.funkcja1().funkcja2().funkcja3();

Powyższy przykład bez zastosowania Method Chaining wyglądał by tak:

obiekt.funkcja1(); obiekt.funkcja2(); obiekt.funkcja3();

Działanie kodu jest proste: każda wywołana funkcja w "łańcuchu" zwraca jako rezultat adres/referencję do obiektu dla którego jest wywoływana (wskaźnik this). Poniżej inny przykład z jakiegoś mojego kodu:

// z wykorzystaniem method chaining geometry.Scale(vec_scale).Translate(trans_vec).Rotate(rotate_vec,rotate_angle).CalculateNormals().Apply(); // bez wykorzystania method chaning geometry.SetScale(vec_scale); geometry.SetTranslate(trans_vec); geometry.SetRotate(rotate_vec,rotate_angle); geometry.SetCalculateNormals();

Kod ten kolejno wykonuje jakieś operacje na modelu o nazwie geometry, czyli kolejno: zmienia skalę modelu, dokonuje transformacji, następnie jest rotacja o zadany kąt według jakiegoś wektora, na koniec wyliczenie wektorów normalnych i wykonanie wcześniej ustalonych obliczeń funkcją apply. Jak widać kod jest bardziej skondensowany i czytelniejszy, a samo jego pisanie jest szybsze i przyjemniejsze (zwłaszcza z pomocą narzędzi typu InteliSense). Przykładowa implementacja klasy Geometry może wyglądać tak:

class Geometry{ private: bool todo_calc_normals,todo_rotate,todo_translate,todo_scale; public: Geometry():todo_calc_normals(false),todo_rotate(false),todo_translate(false),todo_scale(false){} Geometry &CalculateNormals(){ todo_calc_normals=true; return *this; } Geometry &Scale(const Vec3f &iScale){ todo_scale=true; // tu zapisujemy jeszcze gdzies w pamieci wektory 'iScale' return *this; } Geometry &Translate(const Vec3f &iTranslation){ todo_translate=true; // zapisujemy w pamieci 'iTranslation' return *this; } Geometry &Rotate(const Vec3f &iRotation,float iAngle){ todo_rotate=true; // zapisujemy w pamieci wektor 'iRotation' i kat 'iAngle' return *this; } void Apply(){ // na podstawie zebranych danych obliczamy dodane zadania } };

Jak widać kod jest bardzo prosty - całą robotę wykonuje instrukcja return *this - czyli jak wspomniałem - zwracanie referencji do obiektu na którym dalej chcemy wywoływać następną funkcję. Należy pamiętać w powyższym kodzie o odpowiednim ustawieniu flag 'todo_*' i zapamiętywaniu danych wejściowych niezbędnych do zakończenia obliczeń. Dzięki funkcji Apply bardzo łatwo można wprowadzić spore optymalizacje: zamiast osobno przechodzić dane modelu dla policzenia kolejno kilku transformacji na modelu, można na raz dokonać obliczeń raz przechodząc dane.

Technika ta oczywiście nie ogranicza się tylko do wywoływania funkcji jednego obiektu - spore możliwości pokazuje gdy wywołujemy łańcuch metod na kilku różnych obiektach, wspaniale upraszczając kod. Na koniec zamieszczam jeszcze kilka imponujących przykładów z wykorzystaniem biblioteki jQuery które zainspirowały mnie do napisania tej notki:

$('ul.first') .find('.foo').css('background-color', 'red').end() .find('.bar').css('background-color', 'green').end();

Powyższy przykład szuka wszystkich pod-elementów wypunktowania ul o klasie first i zmienia kolor tła na czerwony dla tych z klasą foo i na zielony dla pod-elementów z klasą bar.

$('form#login') .find('label.optional').hide().end() .find('input:password').css('border', '1px solid red').end() .submit(function(){ return confirm('Are you sure you want to submit?'); });

Kod ten wybiera jakiś formularz do logowania (o identyfikatorze login), później ukrywa wszystkie labele o klasie optional tego formularza. Ustawia czerwoną ramkę na pole tekstowe hasła formularza i ustawia mu jeszcze callback wywoływany przy wysłaniu formularza.

Dodatkowo funkcja end pozwala na przedłużenie łańcucha metod cofając wskaźnik elementu na którym pracujemy zwracając referencję do swojego rodzica. Nie trzeba zapamiętywać żadnych wyszukanych zmiennych, czy wyszukiwać elementów kilka razy - wszystko jest wykonane za pomocą jednego łańcucha metod.

przykłady pochodzą z:

Tagi: Programowanie

Komentarze:

Przydatność takiego łańcuchowania znacznie się zmniejsza, jeśli nasz język ma chociaż jeden z dwóch poniższych feature'ów:

1) przeciążanie operatorów
2) słowo kluczowe 'with'

Java(script) nie posiada żadnego z nich, więc to nie przypadek, że chaining jest w nim dość popularny. To także mniej lub bardziej specyficzna cecha jQuery.

Oczywiście w językach, które mają przeciążanie operatorów chaining może być nadal dobrym wyjściem, jak chociażby w tym przykładzie z klasą Geometry. Zwykle aczkolwiek składanie przekształceń jest realizowane poprzez uprzednie przygotowanie macierzy za pomocą zagnieżdżonych wywołań funkcji, a dopiero potem "przepuszczenie" przez nią całej geometrii naraz. (Tak jest choćby w DirectX).
Data: 2010-10-06 18:04:00
avatar
jpacanowski
JavaScript posiada słowo kluczowe 'with'.
Data: 2016-08-04 17:30:52

Dodaj komentarz:

Nick
Adres mail (nie wyświetlany, wymagany)
Strona www (opcjonalne)