"Cześć, Amigo!"

"Cześć, Bilaabo!"

„Nasz dzisiejszy temat będzie nie tylko interesujący — będzie wręcz epicki”.

„Dziś powiem wam, czym są wzorce projektowe ” .

"Fajnie! Dużo o nich słyszałem. Nie mogę się doczekać!"

„Doświadczeni programiści muszą napisać wiele klas. Jednak najtrudniejszą częścią tej pracy jest podjęcie decyzji, które klasy utworzyć i jak podzielić między nie pracę”.

„Im więcej rozwiązywali takie pytania, tym bardziej zdawali sobie sprawę, że niektóre rozwiązania są dobre, a inne złe”.

„Złe rozwiązania zwykle stwarzają więcej problemów niż rozwiązują. Źle się rozszerzają, tworzą wiele niepotrzebnych ograniczeń itp. A dobre rozwiązania są przeciwieństwem”.

"Czy jest jakaś analogia, którą możesz zrobić?"

„Powiedzmy, że budujesz dom. I zastanawiasz się, z czego będzie zrobiony. Decydujesz, że potrzebujesz ścian, podłogi i sufitu. W rezultacie budujesz dom z płaskim dachem i nie fundament. Taki dom pęknie, a dach będzie przeciekał. To jest złe rozwiązanie."

„Odwrotnie, dom składający się z fundamentu, ścian i dwuspadowego dachu byłby dobrym rozwiązaniem. Obfite opady śniegu nie stanowią problemu, ponieważ śnieg będzie się zsuwał z dachu. Nie ma się czego obawiać przesuwania się gruntu, ponieważ fundament zapewni stabilność. Takie rozwiązanie nazwalibyśmy dobrym."

"Rozumiem. Dzięki."

„OK. W takim razie będę kontynuował”.

„Z czasem dobre rozwiązania zaczęto nazywać wzorcami projektowymi, a złe – antywzorcami”.

„Wzorzec projektowy jest jak odpowiedź na pytanie. Trudno go zrozumieć, jeśli nigdy nie słyszałeś pytania”.

" Pierwsza kategoria wzorców to wzorce kreacyjne. Takie wzorce opisują dobre rozwiązania związane z tworzeniem obiektów."

„Co jest takiego skomplikowanego w tworzeniu obiektów?”

„Tak się składa, że ​​właśnie to zamierzamy teraz zbadać”.

Wzór singletona.

Wzorce projektowe: singleton, fabryka, metoda fabryczna, fabryka abstrakcyjna - 1

„Często w programach może istnieć tylko jedna kopia niektórych obiektów. Na przykład konsola, rejestrator, moduł zbierania elementów bezużytecznych itp.”

Złe rozwiązanie: nie twórz żadnych obiektów — po prostu stwórz klasę, której wszystkie metody są statyczne”.

" Dobre rozwiązanie:  utwórz pojedynczy obiekt i zapisz go w zmiennej statycznej. Zapobiegnij utworzeniu drugiego obiektu tej klasy. Na przykład:"

Przykład
class Sun
{
 private static Sun instance;
 public static Sun getInstance()
 {
  if (instance == null)
  instance = new Sun();

  return instance;
 }

 private Sun()
 {
 }
}
Jak to się nazywa
Sun sun = Sun.getInstance();

"To proste."

„Najpierw uczyniliśmy konstruktora prywatnym. Teraz można go wywoływać tylko z wnętrza naszej klasy. Zablokowaliśmy tworzenie obiektów Sun wszędzie poza metodami klasy Sun”.

„Po drugie, obiekt tej klasy można uzyskać tylko przez wywołanie metody getInstance(). Nie tylko jest to jedyna metoda, która może utworzyć obiekt Sun, ale zapewnia również, że istnieje tylko jeden taki obiekt”.

"Widzę."

„Kiedy myślisz: «Teraz, jak dokładnie miałbym to zrobić?», wzorzec mówi: «Możesz tego spróbować — to jedno z najlepszych rozwiązań»”.

„Dzięki. Teraz wszystko zaczyna się wyjaśniać”.

„Możesz również przeczytać o tym wzorze  tutaj ”.

Fabryczny wzór.

Wzorce projektowe: singleton, fabryka, metoda fabryczna, fabryka abstrakcyjna - 2

„Oto sytuacja, z którą bardzo często spotykają się programiści. Masz pewną klasę podstawową i wiele podklas. Na przykład klasę postaci w grze (GamePerson) i klasy dla wszystkich innych postaci, które ją dziedziczą”.

„Powiedzmy, że masz następujące zajęcia:”

Przykład
abstract class GamePerson
{
}

class Warrior extends GamePerson
{
}

class Mag extends GamePerson
{
}

class Troll extends GamePerson
{
}

class Elf extends GamePerson
{
}

„Pytanie brzmi, jak elastycznie i wygodnie zarządzać tworzeniem obiektów tych klas”.

„Jeśli ten problem wydaje ci się naciągany, wyobraź sobie, że w grze musisz stworzyć dziesiątki mieczy i tarcz, setki magicznych zaklęć i tysiące potworów. Nie obejdzie się tutaj bez wygodnego podejścia do tworzenia obiektów. "

„ Wzór Factory oferuje dobre rozwiązanie”.

„Najpierw musimy utworzyć wyliczenie, którego wartości odpowiadają różnym klasom”.

„Po drugie, utwórz specjalną klasę Factory , która ma statyczne metody, które tworzą obiekty na podstawie wartości wyliczeniowej”.

"Na przykład:"

Przykład
public enum PersonType
{
 UNKNOWN,
 WARRIOR,
 MAG,
 TROLL,
 ELF,
}

public class PersonFactory
{
 public static GamePerson createPerson(PersonType personType)
 {
  switch(personType)
  {
   WARRIOR:
   return new Warrior();
   MAG:
   return new Mag();
   TROLL:
   return new Troll();
   ELF:
   return new Elf();
   default:
   throw new GameException();
  }
 }
}
Jak to się nazywa
GamePerson person = PersonFactory.createPerson(PersonType.MAG);

„Innymi słowy, stworzyliśmy specjalną klasę do zarządzania tworzeniem obiektów?”

"Tak."

„Więc jakie to daje korzyści?”

„Po pierwsze, w tej klasie obiekty można zainicjować za pomocą niezbędnych danych”.

„Po drugie, możesz przekazywać potrzebną wartość wyliczeniową między metodami tak bardzo, jak chcesz, aby ostatecznie utworzyć pożądany obiekt”.

„Po trzecie, liczba pól wyliczeniowych nie musi odpowiadać liczbie klas. Może istnieć wiele rodzajów znaków, ale niewiele klas”.

„Na przykład Mag i Wojownik mogą używać jednej klasy (Człowiek), ale o różnej sile i właściwościach magicznych (argumenty konstruktora).”

„Oto jak to może wyglądać (dla jasności dodałem również mroczne elfy):”

Przykład
public enum PersonType
{
 UNKNOWN,
 WARRIOR,
 MAG,
 TROLL,
 ELF,
 DARK_ELF
}

public class PersonFactory
{
 public static GamePerson createPerson(PersonType personType)
 {
  switch(personType)
  {
   WARRIOR:
   return new Human(10, 0); // Strength, magic
   MAG:
   return new Human(0, 10); // Strength, magic
   TROLL:
   OGR:
   return new Troll();
   ELF:
   return new Elf(true); // true – good, false – evil
   DARK_ELF:
   return new Elf(false); // true – good, false – evil
   default:
   throw new GameException();
  }
 }
}
Jak to się nazywa
GamePerson person = PersonFactory.createPerson(PersonType.MAG);

„W powyższym przykładzie użyliśmy tylko trzech klas do stworzenia sześciu różnych typów obiektów. Jest to bardzo wygodne. Ponadto cała ta logika jest skoncentrowana w jednej klasie i jednej metodzie”.

„Załóżmy teraz, że zdecydujemy się stworzyć oddzielną klasę dla Ogre — po prostu zmienimy tutaj kilka linii kodu, a nie połowę aplikacji”.

„Zgadzam się. To dobre rozwiązanie”.

„I o tym właśnie mówię: wzorce projektowe to zbiory dobrych rozwiązań”.

„Chciałbym też wiedzieć, gdzie ich użyć…”

"Tak. Zgadzam się, nie zrozumiesz od razu. Jednak lepiej wiedzieć i nie móc zrobić, niż nie wiedzieć i nie móc zrobić. Oto kolejny przydatny link dla Ciebie o tym wzorze: Wzór fabryczny "

"Oh dziękuję."

Abstrakcyjny wzór fabryczny”.

„Czasami, gdy masz dużo obiektów, pomysł stworzenia fabryki dla fabryk nasuwa się sam. Taka fabryka nazywa się fabryką abstrakcyjną ”.

„Gdzie to będzie potrzebne?!”

„Załóżmy, że masz kilka grup identycznych obiektów. Łatwiej to pokazać na przykładzie”.

„Teraz powiedzmy, że masz w grze trzy rasy: ludzi, elfy i demony. Dla równowagi każda rasa ma wojowników, łuczników i magów. Gracz może tworzyć obiekty tylko należące do rasy, dla której gra w grze. Oto jak wyglądałoby to w kodzie:

Deklaracja klas żołnierzy
class Warrior
{
}
class Archer
{
}
class Mag
{
}
ludzie
class HumanWarrior extends Warrior
{
}

class HumanArcher extends Archer
{
}

class HumanMag extends Mag
{
}
Elfy
class ElfWarrior extends Warrior
{
}

class ElfArcher extends Archer
{
}

class ElfMag extends Mag
{
}
Demony
class DaemonWarrior extends Warrior
{
}

class DaemonArcher extends Archer
{
}

class DaemonMag extends Mag
{
}

A teraz stwórzmy rasy, lub możemy je również nazwać armiami.

Armie
abstract class Army
{
 public Warrior createWarrior();
 public Archer createArcher();
 public Mag createMag();
}
Ludzka armia
class HumanArmy extends Army
{
 public Warrior createWarrior()
 {
  return new HumanWarrior();
 }
 public Archer createArcher()
 {
  return new HumanArcher();
 }
 public Mag createMag()
 {
  return new HumanMag();
 }
}
Armia elfów
class ElfArmy extends Army
{
 public Warrior createWarrior()
 {
  return new ElfWarrior();
 }
 public Archer createArcher()
 {
  return new ElfArcher();
 }
 public Mag createMag()
 {
  return new ElfMag();
 }
}
Armia demonów
class DaemonArmy extends Army
{
 public Warrior createWarrior()
 {
  return new DaemonWarrior();
 }
 public Archer createArcher()
 {
  return new DaemonArcher();
 }
 public Mag createMag()
 {
  return new DaemonMag();
 }
}

– Ale jak tego używasz?

„Możesz używać klas armii, wojownika, łucznika i maga w dowolnym miejscu programu, a także tworzyć niezbędne obiekty — wystarczy przekazać obiekt żądanej podklasy armii”.

"Na przykład:"

Przykład
Army humans = new HumanArmy();
Army daemons = new DaemonArmy();

Army winner = FightSimulator.simulate(humans, daemons);

„W powyższym przykładzie mamy klasę, która symuluje bitwy między różnymi rasami (armiami). Wystarczy przejść przez dwa obiekty Armii. Sama klasa wykorzystuje je do tworzenia różnych oddziałów i prowadzi między nimi wirtualne bitwy w celu zidentyfikowania zwycięzcy ”.

„Rozumiem. Dzięki. Naprawdę interesujące podejście”.

„Dobre rozwiązanie, niezależnie od tego, co mówisz”.

"Tak."

„Oto kolejny dobry link na ten temat:  abstrakcyjny wzór fabryczny