— Привіт, Аміго!
— Привіт, Білаабо!
— Сьогодні у нас буде не просто цікава, а епічна тема.
Сьогодні я розповім тобі, що таке шаблони проектування (design patterns).
— Круто. Багато чув про них. Чекаю з нетерпінням.
— Досвідченим програмістам доводиться писати дуже багато класів. Але найскладніша частина цієї роботи – це вирішувати, які класи мають бути і як розподілити роботу між ними. .
Невдалі рішення зазвичай створюють проблем більше, ніж вирішують. Вони погано розширюються, створюють багато зайвих обмежень тощо. А вдалі рішення – навпаки.
— А можна якусь аналогію?
— Допустимо, ти будуєш будинок. І думаєш — з чого він повинен складатися. Ти вирішив, що тобі потрібні стіни, підлога та стеля. В результаті ти збудував будинок без фундаменту і з рівним дахом. Такий будинок тріскатиметься, а дах – протікатиме. Це – невдале рішення.
І навпаки: будинок, що складається з фундаменту, стін та двосхилий даху буде вдалим рішенням. Йому не страшні ні великі снігопади (сніг скочуватиметься з даху), ні зрушення ґрунту – фундамент забезпечуватиме стабільність. Таке рішення ми назвемо вдалим.
— Зрозуміло. Дякую.
— Ок. Тоді я продовжу.
Згодом збірку вдалих рішень оголосили «патернами (шаблонами) проектування», а збірка невдалих – антипаттернами.
Сам шаблон проектування — це як би відповідь на запитання. Його складно зрозуміти, якщо не чув самого питання.
Перша категорія патернів – це патерни, що породжують. Такі патерни описують вдалі рішення, пов'язані зі створенням об'єктів .
— А що такого складного у створенні об'єктів?
— А ось якраз зараз ми це і розберемо. Патерн Singleton - Сінглетон, Одинак. -4e9d-a5a6-c0acf53b163e/original.jpeg' target='_blank'>
програмі деякі об'єкти можуть існувати лише в єдиному екземплярі. Наприклад, консоль, логер, збирач сміття тощо.
Невдале рішення: відмовитися від створення об'єктів, просто створити клас у якого всі методи статичні .
Вдале рішення: створити єдиний об'єкт класу і зберігати його в статичній змінній. Заборонити створення другого об'єкта цього. Приклад:
class Sun { private static Sun instance; public static Sun getInstance() { if (instance == null) instance = new Sun(); return instance; } private Sun() { } }
Sun sun = Sun.getInstance();
Все просто.
По-перше, ми зробили конструктор private. Тепер його можна викликати лише зсередини нашого класу. Ми заборонили створення об'єкта Sun скрізь крім методів класу Sun.
По-друге, отримати об'єкт цього класу можна лише викликавши метод getInstance(). Це не тільки єдиний метод, який може створювати об'єкти класу Sun, але ще й стежить, щоб такий об'єкт був єдиним.
— Ясно.
— Коли людина думає – як саме це зробити? Паттерн каже – можеш спробувати так – це одне з вдалих рішень.
— Дякую. Тепер щось починає прояснюватись.
— Також про цей патерн можна прочитати тут.
Паттерн Factory – Фекторі, Фабрика .
Дуже часто програмісти стикаються ось з якою ситуацією. У тебе є певний базовий клас та багато підкласів. Наприклад – персонаж гри – GamePerson та класи решти персонажів гри, успадковані від нього.
Припустимо, у тебе є такі класи:
abstract class GamePerson { } class Warrior extends GamePerson { } class Mag extends GamePerson { } class Troll extends GamePerson { } class Elv extends GamePerson { }
Питання полягає в тому, як гнучко та зручно керувати створенням об'єктів цих класів.
Ось яке «вдале рішення» пропонує патерн Фабрика (Factory).
Во- перше, треба завести enum, значення якого будуть відповідати різним класам. об'єкта(ів) залежно від enum’а.
Приклад:
public enum PersonType { UNKNOWN, WARRIOR, MAG, TROLL, ELV, } public class PersonFactory { public static GamePerson createPerson(PersonType personType) { switch(personType ) { WARRIOR: return new Warrior(); MAG: return new Mag(); TROLL: return new Troll(); ELV: return new Elv(); default: throw new GameException(); } } }
GamePerson person = PersonFactory.createPerson(PersonType.MAG);
— Тобто. ми створили спеціальний клас для керування створенням об'єктів?
— Ага.
— А які переваги це дає?
— По-перше, там ці об'єкти можна ініціалізувати потрібними даними.
По-друге, між методами можна скільки завгодно передавати потрібний enum, щоб зрештою по ньому створили правильний об'єкт.
В третє, кількість значень enum не обов'язково має співпадати з кількістю класів. Типів персонажів може бути багато, а класів – мало.
Наприклад, для Mag & Warrior можна використовувати один клас – Human, але з різними налаштуваннями сили та магії (параметрами конструктора).
Ось як це може виглядати (для наочності додав ще темних ельфів):
< div class="code-heading">Прикладpublic enum PersonType { UNKNOWN, WARRIOR, MAG, TROLL, ELV, DARK_ELV } public class PersonFactory { public static GamePerson createPerson(PersonType personType) { switch(personType) { WARRIOR: return new Human(10, 0); //сила, магія MAG: return new Human(0, 10); //сила, магія TROLL: OGR: return new Troll(); ELV: return new Elv (true); // true - добрий, false - злий DARK_ELV: return new Elv (false); // true - добрий, false - злий default: throw new GameException (); } } }
GamePerson person = PersonFactory.createPerson(PersonType.MAG);
У прикладі вище ми використовували всього три класи, щоб створювати об'єкти шести різних типів. Це дуже зручно. Більше того, у нас вся ця логіка зосереджена в одному класі та в одному методі.
Якщо ми раптом вирішимо створити окремий клас для Огра, ми просто поміняємо тут пару рядків коду, а не перелопачуватимемо половину додатку.< /p> — Згоден. Вдале рішення.
— А я тобі про що говорю, шаблони проектування – це збірки вдалих рішень.
— Знати б ще, де їх застосовувати…
— Ага. Відразу не розберешся, згоден. Але все одно краще знати і не вміти, ніж не знати і не вміти. Ось тобі ще корисне посилання на цей патерн: Паттерн Factory
— О, дякую.
— Паттерн Abstract Factory – Абстрактна Фабрика.
Іноді, коли об'єктів дуже багато, напрошується створення фабрики фабрик. Таку фабрику прийнято називати Абстрактною Фабрикою.
— Це де таке може знадобитися?
— Ну, наприклад, ти маєш кілька груп ідентичних об'єктів. Це легше показати на прикладі.
Дивися, припустимо, у тебе в грі є три раси – люди, ельфи та демони. І кожна раса для балансу має воїна, лучника і мага. Залежно від цього, який бік грає людина, може створювати лише об'єкти своєї раси. Ось як це могло б виглядати в коді:
class Warrior { } class Archer { } class Mag { }
class HumanWarrior extends Warrior { } class HumanArcher extends Archer { } class HumanMag extends Mag { }
class ElvWarrior extends Warrior { } class ElvArcher extends Archer { } class ElvMag extends Mag { }
class DaemonWarrior extends Warrior { } class DaemonArcher extends Archer { } class DaemonMag extends Mag { }
А тепер створимо раси, ну чи можна ще назвати їх арміями.
abstract class Army { public Warrior createWarrior(); public Archer createArcher(); public Mag createMag(); }
class HumanArmy extends Army { public Warrior createWarrior() { return new HumanWarrior(); } public Archer createArcher() { return new HumanArcher(); } public Mag createMag() { return new HumanMag(); } }
class ElvArmy extends Army { public Warrior createWarrior() { return new ElvWarrior(); } public Archer createArcher() { return new ElvArcher(); } public Mag createMag() { return new ElvMag(); } }
class DaemonArmy extends Army { public Warrior createWarrior() { return new DaemonWarrior(); } public Archer createArcher() { return new DaemonArcher(); } public Mag createMag() { return new DaemonMag(); } }
— Як це використовувати?
— Можна скрізь у програмі використовувати класи Army, Warrior, Archer, Mag, а для створення потрібних об'єктів – просто передавати об'єкт потрібного класу-спадкоємця Army.
Приклад:
Army humans = new HumanArmy(); Army daemons = новий DaemonArmy(); Army winner = FightSimulator.simulate(humans, daemons);
У прикладі вище у нас є клас, який симулює бої між різними расами (арміями), і йому потрібно просто передати два об'єкти Army. З їх допомогою він сам створює різні війська та проводить віртуальні бої між ними, з метою виявити переможця.
— Зрозуміло. Дякую.Дійсно цікавий підхід.
Вдале рішення, що не кажи.
— Ага.
Ось ще хороше посилання на цю тему: Паттерн Abstract Factory< /p>
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ