"Witaj przyjacielu!"

"Cześć, Bilaabo!"

„Mamy jeszcze trochę czasu, więc opowiem ci o jeszcze trzech wzorach”.

- Jeszcze trzy? Ilu ich jest razem?

„Obecnie istnieją dziesiątki popularnych wzorców, ale liczba «skutecznych rozwiązań» jest nieograniczona”.

- Rozumiem. Czyli mam się nauczyć kilkudziesięciu wzorów?

„Dopóki nie masz prawdziwego doświadczenia w programowaniu, nie dadzą ci wiele”.

„Lepiej nabierz trochę więcej doświadczenia, a potem za rok wróć do tego tematu i spróbuj głębiej je zrozumieć. Przynajmniej kilkadziesiąt najpopularniejszych wzorców projektowych”.

„Grzechem jest nie skorzystać z czyjegoś doświadczenia i zamiast tego wymyślić coś po raz 110.”

"Zgadzam się."

— Więc zacznijmy.

Wzór adaptera (lub opakowania).

Wzory: Adapter, Proxy, Bridge - 1

„Wyobraź sobie, że przyjeżdżasz do Chin i stwierdzasz, że gniazdka elektryczne mają inny standard. Otwory nie są okrągłe, ale płaskie. W takim przypadku będziesz potrzebować adaptera”.

„Coś podobnego może zdarzyć się również w programowaniu. Klasy działają na podobnych, ale różnych interfejsach. Musimy więc zrobić między nimi adapter”.

„Tak to wygląda:”

Przykład
interface Time
{
 int getSeconds();
 int getMinutes();
 int getHours();
}

interface TotalTime
{
 int getTotalSeconds();
}

„Załóżmy, że mamy dwa interfejsy: Time  i  TotalTime ”.

„Interfejs Time pozwala uzyskać aktualny czas za pomocą metod getSeconds (),  getMinutes () i  getHours ()”.

„ Interfejs TotalTime pozwala uzyskać liczbę sekund, które upłynęły od północy do bieżącej chwili”.

„Co powinniśmy zrobić, jeśli mamy obiekt TotalTime , ale potrzebujemy obiektu Time lub odwrotnie?”

„Możemy napisać w tym celu klasy adapterów. Na przykład:”

Przykład
class TotalTimeAdapter implements Time
{
 private TotalTime totalTime;
 public TotalTimeAdapter(TotalTime totalTime)
 {
  this.totalTime = totalTime;
 }

 int getSeconds()
 {
  return totalTime.getTotalSeconds() % 60; // seconds
 }

 int getMinutes()
 {
  return totalTime.getTotalSeconds() / 60; // minutes
 }

 int getHours()
 {
  return totalTime.getTotalSeconds() / (60 * 60); // hours
 }
}
Stosowanie
TotalTime totalTime = TimeManager.getCurrentTime();
Time time = new TotalTimeAdapter(totalTime);
System.out.println(time.getHours() + " : " + time.getMinutes () + " : " +time.getSeconds());

„I adapter w innym kierunku:”

Przykład
class TimeAdapter implements TotalTime
{
 private Time time;
 public TimeAdapter(Time time)
 {
  this.time = time;
 }

 int getTotalSeconds()
 {
  return time.getHours() * 60 * 60 + time.getMinutes() * 60 + time.getSeconds();
 }
}
Stosowanie
Time time = new Time();
TotalTime totalTime = new TimeAdapter(time);
System.out.println(time.getTotalSeconds());

„Ach. Podoba mi się. Ale czy są jakieś przykłady?”

„Oczywiście! Na przykład InputStreamReader to klasyczny adapter. Konwertuje InputStream na Reader”.

„Czasami ten wzorzec jest również nazywany opakowaniem, ponieważ nowa klasa„ zawija ”inny obiekt”.

„ Tutaj możesz przeczytać kilka innych interesujących rzeczy ”.

Wzór proxy

„Wzorzec proxy jest nieco podobny do wzorca opakowania. Ale jego celem nie jest konwersja interfejsów, ale kontrola dostępu do oryginalnego obiektu przechowywanego w klasie proxy. Co więcej, zarówno oryginalna klasa, jak i proxy mają zwykle ten sam interfejs, co ułatwia zastąpienie obiektu oryginalnej klasy obiektem proxy”.

"Na przykład:"

Interfejs prawdziwej klasy
interface Bank
{
 public void setUserMoney(User user, double money);
 public int getUserMoney(User user);
}
Implementacja oryginalnej klasy
class CitiBank implements Bank
{
 public void setUserMoney(User user, double money)
 {
  UserDAO.updateMoney(user, money);
 }

 public int getUserMoney(User user)
 {
  return UserDAO.getMoney(user);
 }
}
Implementacja klasy proxy
class BankSecurityProxy implements Bank
{
 private Bank bank;
 public BankSecurityProxy(Bank bank)
 {
  this.bank = bank;
 }
 public void setUserMoney(User user, double money)
 {
  if (!SecurityManager.authorize(user, BankAccounts.Manager))
  throw new SecurityException("User can’t change money value");

  bank.setUserMoney(user, money);
 }

 public int getUserMoney(User user)
 {
  if (!SecurityManager.authorize(user, BankAccounts.Manager))
  throw new SecurityException("User can’t get money value");

  return bank.getUserMoney(user);
 }
}

„W powyższym przykładzie opisaliśmy interfejs Bank oraz klasę CitiBank , implementację tego interfejsu.”

„Interfejs pozwala uzyskać lub zmienić saldo konta użytkownika”.

Następnie stworzyliśmy BankSecurityProxy , który również implementuje interfejs Banku i przechowuje odniesienie do innego interfejsu Banku. Metody tej klasy sprawdzają, czy użytkownik jest właścicielem konta, czy kierownikiem banku. Jeśli tak nie jest, zgłaszany jest wyjątek SecurityException."

„Oto, jak to działa w praktyce:”

Kod bez kontroli bezpieczeństwa:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank.setUserMoney(user, 1000000);
Kod z kontrolami bezpieczeństwa:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank = new BankSecurityProxy(bank);
bank.setUserMoney(user, 1000000);

„W pierwszym przykładzie tworzymy obiekt banku i wywołujemy jego metodę setUserMoney .

„W drugim przykładzie opakowujemy oryginalny obiekt banku w obiekt BankSecurityProxy . Mają ten sam interfejs, więc kolejny kod nadal działa tak, jak wcześniej. Ale teraz kontrola bezpieczeństwa będzie przeprowadzana za każdym razem, gdy metoda zostanie wywołana”.

"Fajny!"

„Tak. Takich proxy można mieć wiele. Na przykład można dodać jeszcze jedno proxy, które sprawdza, czy saldo konta nie jest zbyt duże. Dyrektor banku może zdecydować się na włożenie dużej ilości pieniędzy na własne konto i uciec z funduszami na Kubę ”.

„Co więcej… Tworzenie wszystkich tych łańcuchów obiektów można umieścić w klasie BankFactory , w której można włączać/wyłączać te, których potrzebujesz”.

" BufferedReader działa na podobnej zasadzie. To Reader , ale wykonuje dodatkową pracę."

„To podejście pozwala «złożyć» obiekt o wymaganej funkcjonalności z różnych «elementów»”.

„Och, prawie zapomniałem. Proxy są używane znacznie szerzej niż to, co właśnie ci pokazałem. Możesz przeczytać o innych zastosowaniach tutaj ”.

Wzór mostka

Wzorce: adapter, proxy, mostek - 2

„Czasami podczas działania programu konieczna jest znacząca zmiana funkcjonalności obiektu. Załóżmy na przykład, że masz grę z postacią osła, który później zostaje zamieniony przez maga w smoka. Smok ma zupełnie inne zachowanie i właściwości, ale jest ten sam obiekt!"

„Czy nie możemy po prostu stworzyć nowego obiektu i skończyć z nim?”

„Nie zawsze. Załóżmy, że twój osioł przyjaźni się z grupą postaci, może był pod wpływem kilku zaklęć lub był zaangażowany w pewne zadania. Innymi słowy, przedmiot może być już używany w wielu miejscach… i połączone z wieloma innymi obiektami. W tym przypadku nie jest więc możliwe po prostu utworzenie nowego obiektu”.

— Cóż, w takim razie można zrobić?

„Wzór Bridge to jedno z najbardziej udanych rozwiązań.”

„Wzorzec ten polega na podziale obiektu na dwa obiekty: «obiekt interfejsu» i «obiekt implementacji»”.

„Jaka jest różnica między interfejsem a klasą, która go implementuje?”

„Dzięki interfejsowi i klasie otrzymujemy jeden obiekt. Ale tutaj — mamy dwa. Spójrz na ten przykład:”

Przykład
class User
{
 private UserImpl realUser;

 public User(UserImpl impl)
 {
  realUser = impl;
 }

 public void run() //Run
 {
  realUser.run();
 }

 public void fly() //Fly
 {
  realUser.fly();
 }
}

class UserImpl
{
 public void run()
 {
 }

 public void fly()
 {
 }
}

„A potem możesz zadeklarować kilka podklas UserImpl, na przykład UserDonkey (osioł) i UserDragon (smok).”

– Mimo wszystko nie bardzo rozumiem, jak to będzie działać.

"Cóż, coś takiego:"

Przykład
class User
{
 private UserImpl realUser;

 public User(UserImpl impl)
 {
  realUser = impl;
 }

 public void transformToDonkey()
 {
  realUser = new UserDonkeyImpl();
 }

 public void transformToDragon()
 {
  realUser = new UserDragonImpl();
 }
}
Jak to działa
User user = new User(new UserDonkey()); // Internally, we're a donkey
user.transformToDragon(); // Now we're a dragon internally

„Więc to coś w rodzaju proxy”.

„Tak, ale w proxy główny obiekt mógłby być przechowywany gdzieś osobno, a zamiast tego kod działa z proxy. Tutaj mówimy, że wszyscy pracują z głównym obiektem, ale jego części zmieniają się wewnętrznie”.

„Ach. Dzięki. Dasz mi link, żebym mógł przeczytać więcej na ten temat?”

„Oczywiście, Amigo, przyjacielu. Proszę bardzo: wzór mostka ”.