sharpek.net

moje trzy grosze

Wzorce projektowe: Strategia

Wzorzec strategii jest jednym z podstawowych wzorców projektowych które warto się na początku poznać, stosunkowo często z niego można korzystać.

Studium przypadku

Załóżmy że Tworzymy grę RPG, pomińmy w jakiej technologii to robimy oraz dokładną implementację. Zajmijmy się tylko i wyłącznie sposobem ataku postaci. Wiemy już że na początku będą tylko 4 typy postaci, oto one: strzelec, łucznik, rycerz, kanonier.

Wiadomo wszystkie postaci mają część wspólną. Warto więc zrobić sobie klasę bazową o nazwie Postac która będzie zawierała inne części wspólne naszych postaci. Zakładamy że w czasie gry będzie można zmienić rodzaj naszej postaci z kanoniera na strzelca i tak dalej… W tym momencie pomysł ze stworzeniem 4 klas dziedziczących po klasie bazowej odpada, bo w czasie gry ciężko będzie nam zmienić naszą postać w strzelca.

Jednym ze sposobów rozwiązania tego problemu jest stworzenie w naszej klasie Postac metody atakuj i pola typ. Metoda atakuj w zależności od typu będzie wykonywała odpowiedniki atak. Oto nasza klasa postać:

public class Postac {
 
        private String type = null;
 
        public Postac(String _type) {
                this.type = _type;
        }
 
        public void atakuj() {
                if (type == "strzelec") {
                        System.out.println("Strzelam z pistoletu");
                } else if(type == "łucznik") {
                        System.out.println("Strzlema z łuku");
                } else if(type == "rycerz") {
                        System.out.println("Uderzam mieczem");
                } else if(type =="kanonier") {
                        System.out.println("Strzelam z armaty");
                }
        }
        public void laduj() {
                if (type == "strzelec") {
                        System.out.println("Wkładam naboj do pistoletu");
                } else if(type == "łucznik") {
                        System.out.println("Naprężam łuk");
                } else if(type == "rycerz") {
                        System.out.println(""); // Rycerz nie musi ładować swego miecza, no chyba że jest JEDI
                } else if(type =="kanonier") {
                        System.out.println("Wsypuje proch");
                        System.out.println("Wkładam kulę do armaty");
                }
        }
 
}

Rozwiązanie na pierwszy rzut oka jest dobre ale warto się zastanowić co będzie w chwili gdy postanowimy dodać kolejną postać ? Np. Skrytozabojca. W tym momencie jesteśmy zmuszeni dopisać kolejne kawałki kodu do metod atakuj, laduj. Nie jest to dobrym pomysł. Nie dodaliśmy tutaj kilku innych metod takich jak celuj, czysc, lub podaj się. W takim wypadku dodawanie kolejnych postać może powodować naprawdę duży problem i podatność na błędy (Można zapomnieć zaimplementować metody atakuj dla skrytozabojcy). Biorąc pod uwagę że nie będziemy żyć wiecznie, osoba która dostanie w spadku naszą grę chcąc dodać nową postać, wiele czasu poświęci na analizę naszego kodu i zrozumienie jak to działa.

Wzorce w ataku!

To jest właściwy moment aby skorzystać z naszego wzorca projektowego. Wzorzec ten jak sama nazwa wskazuje odpowiada tak jakby za strategię działania (Jest to bardzo duży skrót myślowy). Chcąc go wykorzystać należy stworzyć interfejs Atak. Po co ? Aby zachować zgodność typu i ułatwić pracę w przyszłości innym programistą. Nasz interfejs będzie posiadał tylko dwie publiczne metody – atak, laduj. Oto on:

interface Atak {
 
    public void atak();
    public void laduj();
 
}

Będzie on implementowany przez klasy odpowiadające za atak. Dla zobrazowania tego stwórzmy sobie dwie klasy które będą tzw. Strategią ataku:

class AtakStrzelc implements Atak {
 
        public void atak() {
                System.out.println("Strzelam z pistoletu");
        }
 
       public void laduj() {
                System.out.println("Wkładam naboj do pistoletu");
       }
 
}
class AtakKanonier implements Atak {
 
        public void atak() {
                System.out.println("Strzelam z armaty");
        }
 
        public void laduj() {
                 System.out.println("Wsypuje proch");
                 System.out.println("Wkładam kulę do armaty");
        }
 
}

Teraz wystarczy odrobinę przerobić naszą klasę Postac. Na początku zamieniamy pole String type, zamienimy polem Atak atak. Również konstruktor zmieni trochę parametry jakie będzie przyjmował. Dodatkowo dodam metodę zmień która będzie przyjmowała dokładnie takie same parametry jak konstruktor, będzie ona odpowiadała za zmianę sposobu ataku naszej postaci w czasie gry.

public class Postac {
 
        private Atak atak;
 
        public Postac(Atak _atak) {
                this.atak = _atak;
        }
 
        public void zmien(Atak _atak) {
                this.atak = _atak;
        }
 
        public void atakuj() {
                atak.atak();
        }
 
}

Obecnie metoda atakuj wywołuje metodę atakuj z klasy typu Atakuj. Mały przykład jak to działa.

Postac czesiek = new Postac(new AtakStrzelec());
czesiek.laduj()
czesiek.atakuj();
// Strzelam z pistoletu
czesiek.zmien(new AtakKanonier());
czesiek.laduj();
czesiek.atakuj();
// Strzelam z armaty

Oczywiście to wszystko jest strasznie uproszczone. Niektórym osobą może się to wydawać sztuką dla sztuki, ale warto pamiętać że nie innych istotnych rzeczy jak celuj(), uciekaj(). Gdybyśmy wzbogacili nasz przypadek o kolejne metody, to edycja przedstawionej na początku klasy byłaby trudna.

 

Comments: 6

Leave a reply »

 
 
 

Cześć Sharpek.
Fajne wytłumaczenie wzorca, bardzo mi się podoba, ale kod przez Ciebie zamieszczony zawiera kilka błędów. Poprawiłem go i chciałem Ci posłać, ale nigdzie na stronie nie potrafię znaleźć żadnego kontaktu do Ciebie.

 
 

Wrzuć na pastie.org i daj linka tutaj

 
 

zamiast typ == łucznik itp uzywaj equals ;)

 
 

@mario: wiadomo :)

 
 

Bardzo dobry przykład

public zmien(Atak _atak) {
this.atak = _atak;
}
tutaj tylko brakuje void

 
 

@vivi: dzięki, poprawione!

 
 

Leave a Reply

 
(will not be published)
 
 
 
 

Preview: