— Привіт, друже!

— Привіт, Білаабо!

— У нас ще залишилося трохи часу, тому я розповім тобі про ще три патерни.

— Ще три, а скільки їх всього?

— Ну, зараз є кілька десятків популярних патернів, але кількість «вдалих рішень» не обмежена.  Зрозуміло. І що, мені доведеться вивчати кілька десятків патернів?

— Поки в тебе немає досвіду реального програмування, вони дадуть тобі не дуже багато.

Ти краще наберися досвіду, а потім, через рік, повернися до цієї теми і спробуй розібратися в них ґрунтовніше. Хоча б кілька десятків найпопулярніших.

Гріх не користуватися чужим досвідом і самому щось винаходити в черговий 110-й раз.

— Згоден.

— Тоді почнемо.

Паттерн Adapter(Wrapper) – Адаптер (Обгортка)

Паттерни 1

Уяви, що ти приїхав до Китаю, а там інший стандарт розеток. Отвори не круглі, а пласкі. Тоді тобі знадобиться перехідник, або іншими словами адаптер.

У програмуванні теж може бути щось подібне. Класи оперують схожими, але різними інтерфейсами. І треба зробити перехідник між ними.

Ось як це може виглядати:

Приклад
interface Time { int getSeconds (); int getMinutes(); int getHours(); } interface TotalTime { int getTotalSeconds(); }

Припустимо, у нас є два інтерфейси - Time і TotalTime.

Інтерфейс Time дозволяє дізнатися поточний час за допомогою методів getSeconds(), getMinutes() і getHours().

Інтерфейс TotalTime дозволяє отримати кількість секунд, яке пройшло від півночі до поточного моменту.

Що робити, якщо у нас є об'єкт типу TotalTime, а потрібен Time і навпаки?

Для цього ми можемо написати класи-адаптери. Приклад:

Приклад
 class TotalTimeAdapter implements Time { private TotalTime totalTime; public TotalTimeAdapter(TotalTime totalTime) { this.totalTime = totalTime ; } public int getSeconds() { return totalTime.getTotalSeconds() % 60; //секунди } public int getMinutes() { return (totalTime.getTotalSeconds() % (60*60 ))/60; //хвилини } public int getHours() { return totalTime.getTotalSeconds() / (60*60) ; //годинник } } 
Як користуватися
TotalTime totalTime = ... ; // програма отримує об'єкт, який реалізує інтерфейс TotalTime Time time = новий TotalTimeAdapter(totalTime); System.out.println(time.getHours()+":"+time.getMinutes()+":"+time.getSeconds());

І адаптер в інший бік:

Приклад
 class TimeAdapter implements TotalTime { private Time time< /span>; public TimeAdapter(Time time) { this.time = time ; } public int getTotalSeconds() { return time.getHours()*60*60+time.getMinutes()*60 + time.getSeconds(); } }
Як користуватися
Time time = ... ; // програма отримує об'єкт, який реалізує інтерфейс Time < span class="text-green">TotalTime totalTime = new TimeAdapter(time); System.out.println(totalTime.getTotalSeconds());
< — Ага. Мені подобається. А приклади є?

— Звичайно, наприклад, InputStreamReader – це класичний адаптер. Перетворює тип InputStream до типу Reader.

Іноді цей патерн ще називають обгортка, тому що новий клас як би «обертає» собою інший об'єкт.

Інші цікаві речі можна почитати тут.

Паттерн Proxy — Заступник

Паттерн проксі чимось схожий на патерн "обгортка". Але його завдання – не перетворювати інтерфейси, а контролювати доступ до оригінального об'єкта, збереженого всередині проксі-класу. При цьому і оригінальний клас і проксі зазвичай мають той самий інтерфейс, що полегшує заміну об'єкта оригінального класу, на об'єкт проксі.

Приклад:

Інтерфейс реального класу
interface Bank { public void setUserMoney(User user, double money); public int getUserMoney(User user); }
Реалізація оригінального класу
class CitiBank implements Bank { public void setUserMoney(User user, double money) { UserDAO. updateMoney(user, money); } public int getUserMoney(User user) { return UserDAO.getMoney(user); } }
Реалізація проксі-класу
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); } }

У прикладі вище ми описали інтерфейс банку - Bank, і одну його реалізацію - CitiBank.

Цей інтерфейс дозволяє отримати або змінити кількість грошей на рахунку користувача.

А потім ми створили BankSecurityProxy, який також реалізує інтерфейс Bank і зберігає в собі посилання на інший інтерфейс Bank. Методи цього класу перевіряють: чи є даний користувач власником рахунку або менеджером банку, і якщо ні – кидає виняток безпеки – SecurityException.

Ось як це працює на ділі:

Код без перевірки безпеки:
User user = AuthManager.authorize(login, password); Bank bank =BankFactory.createUserBank(user); bank.setUserMoney(user, 1000000);
Кодз включеною перевіркою безпеки:
User user = AuthManager.authorize( login, password); Bank bank = BankFactory.createUserBank(user); bank = new BankSecurityProxy(bank); bank.setUserMoney(user, 1000000);

У першому прикладі ми створюємо об'єкт банк і викликаємо у нього метод setUserMoney.

У другому На прикладі ми обертаємо оригінальний об'єкт банк в об'єкт BankSecurityProxy. Інтерфейс вони один, отже подальший код як працював, і працює. Але тепер при кожному виклику методу відбуватиметься перевірка безпеки.

— Круто!

— Ага. Таких проксі може бути багато. Наприклад, можна додати ще один проксі, який перевірятиме – чи не надто велика сума. Може, менеджер банку вирішив покласти собі на рахунок купу грошей і втекти з ними на Кубу.

Більше того. Створення всіх цих ланцюжків об'єктів можна помістити в клас BankFactory і підключати/відключати потрібні з них.

За схожим принципом працює BufferedReader. Це Reader, але який робить ще додаткову роботу.

Такий підхід дозволяє «збирати» з «шматочків» об'єкт потрібної тобі функціональності.

Тільки не забув. Проксі використовуються набагато ширше, ніж я щойно тобі показав. Про інші типи використання ти можеш почитати тут.

Паттерн Bridge – Міст

Паттерни: Adapter, Proxy, Bridge - 2 < Іноді, у процесі роботи програми, треба сильно змінити функціональність об'єкта. Наприклад, був у тебе в грі персонаж осел, а потім маг перетворив його на дракона. У дракона зовсім інша поведінка і властивості, але(!) це той самий об'єкт.

— А чи не можна просто створити новий об'єкт і все?

— Не завжди. Припустимо, твій осел був у друзях біля купи персонажів, або, наприклад, на ньому були накладені деякі заклинання, або він брав участь у якихось квестах. Тобто. цей об'єкт вже може бути задіяний у купі місць та прив'язаний до купи інших об'єктів. Отже, просто створити новий інший об'єкт у цьому випадку – не варіант.

— І що робити?

— Одним із найбільш вдалих рішень є патерн Міст.

Цей патерн пропонує розділити об'єкт на два об'єкти. На «об'єкт інтерфейсу» та «об'єкт реалізації».

— А в чому відмінність від інтерфейсу та класу, що його реалізує?

— У випадку з інтерфейсом та класом в результаті буде створено один об'єкт, а тут — два. Дивись приклад:

Приклад
 class User { private UserImpl realUser; public User(UserImpl impl) { realUser = impl; } public void run() //бігти { realUser.run(); } public void fly() //летіти { realUser.fly(); } } class UserImpl { public void run() { } public void fly() { } }

А потім можна оголосити кілька класів спадкоємців від UserImpl, наприклад UserDonkey(осел) та UserDragon(дракон).

— ; Все одно не дуже зрозумів, як це працюватиме.

— Приблизно так:

Приклад
< code>class User { private UserImpl realUser; public User(UserImpl impl) { realUser = impl; } public void transformToDonkey() { realUser = new UserDonkeyImpl(); } public void transformToDragon() { realUser = new UserDragonImpl(); } }
Як це працює
User user = new User(new UserDonkey()); //всередині ми - осел user.transformToDragon(); //тепер усередині ми – дракон

— Чимось нагадує проксі.

— Так, тільки в проксі головний об'єкт міг зберігатися десь окремо, а код працював із проксі-об'єктами замість нього. Тут пропонується, що всі працюють саме з головним об'єктом, а змінюються його внутрішні частини.

— Ага. Дякую. А даси посилання почитати ще про це?

— Звичайно, друг Аміго.