Strategy Pattern

1. Capcanele care apar la Mostenire și Compozitie

Mostenirea este un principiu de baza al OOP, utilizănd mosteneria ai posibilitate sa extinzi comportamentul unui obiect. Dacă un Urs este un Mamifer, vom extinde clasa Mamifer pentru a crea clasa Urs, este comod și util pentru reutilizarea codului, problema apare atunci când moștenirea este utilizată prea des sau în situații incorecte. Când clasele tale tind sa conțina multe relații ESTE-UN ( Ursul ESTE-UN Mamifer ), trebuie să reanalizezi designul aplicației, poți ajunge în situația în care codul nu este flexibil și greu de schimbat.

Un exemplu de utilizare greșită a moștenirii este:

Avem clasa abstractă Mamifer care conține metoda display() – neimplementată, fiecare moștenitor fiind necesar să implementeze metoda sa de afișare:

Cele 3 clase care extind clasa Mamifer arată ok, fiecare implementează metoda display, și avem doar o mică problemă cu clasa Balena care în realitate nu are blană – însă nu e foarte grav și pentru moment acceptam așa cum este.

În continuare apare necesitate implementarii metodei walk() – care va descrie mișcarea cu pași a mamiferelor.

După implementare mai apare o problema, Balena în realitate nu merge dar înoată, trebuie sa rescriem aceasta metodă și pentru claritate ar trebui și redenumită. Dar lăsăm pentru moment așa pentru ca nu e foarte grav, e acceptabil.

Între timp apare necesitatea implementarii comportamentului pentru un nou mamifer, Liliacul.

După implementare avem incompatibilități similare cu implementarea Balenii, doar că in realitate Liliacul nu are blană și nu merge sau înoata, dar zboară.

2. Probleme care le are designul de mai sus

Pentru ca adăugam majoritatea funcțiilor în clasa părinte, unii moștenitor nu pot sau trebuie să îndeplinească diferit comportamentul decât cel descris de părinte, este necesar sa rescriem comportamentul lor. Desi initial pare o problemă minoră, în timp clasele se poluează cu cod inutil care trebuie meținut.

  • Desi încercăm să avem o ierarhie organizată a claselor noastre, nu beneficiem de reutilizarea codului prea mult iar lucrurile devine încurcate și neclare.
  • În momentul în care analizam clasa Mamifer, obținem cunoștințe puține despre clasele care o implementează din cauza implementărilor variate
  • Modificarile în clasa parinte pot afecta comportamentul claselor copil într-un mod incorect
  • Comportamentul mamiferilor este greu de modificat în momentul rulării din cauză că comportamentul lor este definit la compilare ( ex: transformarea balenii în una zburătoare pentru a nu fi prinsă de pescari )

3. Interfețele nu sunt o soluție

La prima vedere se pare că atunci când vom crea 3 interfețe, una pentru mamiferele care zboară, alta pentru cele care merg și a treia pentru cele care înoată, și vom implementa aceste interfețe corespunzător – soluția rezolvă problema cu codul suplimentar și metodele care nu sunt necesare la unele clase care extind clasa mamifer.

Dar:

  • Aceasta tehnică ne limitează în reutilizare codului ( pentru fiecare mamifer trebuie implementat comportamentul )
  • Fiecare modificare a modului de a merge, înota sau zbura, va cauza probleme de mentenața pentru că e necesar sa fie rescrie în fiecare clasa de tip mamifer.
  • Problema cu modificarea comportamentului la runtime este prezentă, nu putem modifica decât modul de mergere, înot sau zbor.

Design principle #1 – Encapsulate what varies.

Indetifică codul care se modifică și separa-l de codul care rămâne nemodificat. Din designul codului de mai sus obsevam ca metoda walk() difera în dependeță de tipul mamiferului. Este bine sa separăm implementarea ei pentru a obține cod reutilizabil și nereptabil, pentru debugging simplificat în viitor.

Design principle #2 – Program to an interface, not an implementation.

Pentru ca am indentificat ceea ce variază în cod, o practică utilă este programarea utilizânt interfețele, nu implementarile. Interfața MoventBehavior definește metoda move() care va conține implementare în clasele Walk, Fly și Swim:

La clasa părinte Mamifer vom adaugă referință către MovementBehavior:

Comportamentul pentru deplasarea în continuare va fi setat dinamic.

Exemple de instațiere pentru Balena:

Balena balena1 = new Balena();
balena1.setMovementBehavior(new SwimBehavior());
balena1. move();

Metoda move() implementată în clasa parinte Mamifer:

public void move() {
    mB.move();
}

În momentul apelării metodei move() pentru obiectul balena1, comportamentul metodei va fi cel din clasa SwimBehavior().

Utilizând Strategy Pattern am obținut o metodă move() în clasa părinte Mamifer care nu necesită rescriere în clasele copil și în același timp comportamentele pentru clasele copii se află în clase aparte și pot fi reutilizate.

Design principle #3 – Code should be open for extension, but closed for modification.

În exemplu prezentat, clasa Mamifer poate avea comportamente pentru move() care nu au fost prevăzute în momentul proiectarii, comportamentele suplimentare adaugate nu necesită modificare implementarii pentru clasa Mamifer, este suficient implementarea interfeței MovementBehavior și setarea comportamentului.

 

Schema abstractă pentru Strattegy Pattern, Superclass are un comportament definit de AlgorithmInterface, mutând acest algoritm în afara clasei de baza, avem posibilitatea selectării algoritmului care dorim să fie executat iar algoritmii pot fi reutilizați de alte clase:

Definiția pentru strattegy pattern: Strattegy pattern definește o familie de algoritmi, încapsulează fiecare algoritm și face algoritmii interschimbabili în acea familie.

ARE-UN este mai bine decat ESTE-UN ( HAS-A is better than IS-A )

În implementarea inițială comportamentul pentru metoda move() era descris în clasa de bază ceea ce nu era comod pe parcursul extinderii aplicației, am modificat structura codului și sa ajuns la relația Un Mamifer are-un comportament de deplasare ceea ce este corect fața de Un Mamifer este-o/conține-o deplasare. Utilizănd compoziția, designul a devenit flexibil și comportamentul de mișcare poate fi setat și modificat în orice moment. Atunci când avem de ales între compoziție și moștenire, este recomandabil să alegem compoziția.

Design priciniple #4 – Favor composition over inheritance.

Links:

https://en.wikipedia.org/wiki/Strategy_pattern

https://thetechhunter.wordpress.com/2011/01/19/encapsulation-encapsulate-what-varies/

https://tuhrig.de/programming-to-an-interface/

https://en.wikipedia.org/wiki/Open/closed_principle

https://en.wikipedia.org/wiki/Composition_over_inheritance

http://www.w3resource.com/java-tutorial/inheritance-composition-relationship.php

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *

This site uses Akismet to reduce spam. Learn how your comment data is processed.