— Привет, Амиго!
— Привет, Билаабо!
— Сегодня у нас будет не просто интересная, а прямо-таки эпическая тема.
Сегодня я расскажу тебе, что такое шаблоны проектирования (design patterns).
— Круто. Много про них слышал. Жду с нетерпением.
— Опытным программистам приходится писать очень много классов. Но самая сложная часть этой работы – это решать, какие классы должны быть и как распределить работу между ними.
Чем чаще они решали такие вопросы, тем чаще стали понимать, что существуют некоторые удачные решения, и наоборот, неудачные.
Неудачные решения обычно создают проблем больше, чем решают. Они плохо расширяются, создают много лишних ограничений, и т.п. А удачные решения – наоборот.
— А можно какую-нибудь аналогию?
— Допустим, ты строишь дом. И думаешь — из чего же он должен состоять. Ты решил, что тебе нужны стены, пол и потолок. В результате ты построил дом без фундамента и с ровной крышей. Такой дом будет трескаться, а крыша – протекать. Это – неудачное решение.
И наоборот: дом, состоящий из фундамента, стен и двускатной крыши будет удачным решением. Ему нестрашны ни большие снегопады (снег будет скатываться с крыши), ни подвижки почвы – фундамент будет обеспечивать стабильность. Такое решение мы назовем удачным.
— Ясно. Спасибо.
— Ок. Тогда я продолжу.
Со временем сборник удачных решений объявили «паттернами (шаблонами) проектирования», а сборник неудачных – антипаттернами.
Сам шаблон проектирования — это как бы ответ на вопрос. Его сложно понять, если не слышал самого вопроса.
Первая категория паттернов – это порождающие паттерны. Такие паттерны описывают удачные решения, связанные с созданием объектов.
— А что такого сложного в создании объектов?
— А вот как раз сейчас мы это и разберем.
Паттерн Singleton – Синглетон, Одиночка.

Часто в программе некоторые объекты могут существовать только в единственном экземпляре. Например, консоль, логгер, сборщик мусора и т.д.
Неудачное решение: отказаться от создания объектов, просто создать класс у которого все методы статические.
Удачное решение: создать единственный объект класса и хранить его в статической переменной. Запретить создание второго объекта этого класса. Пример:
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, значения которого будут соответствовать различным классам.
Во-вторых, сделать специальный класс – Factory, у которого будет статический метод или методы, которые и будут заниматься созданием объекта(ов) в зависимости от 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, но с различными настройками силы и магии (параметрами конструктора).
Вот как это может выглядеть (для наглядности добавил еще темных эльфов):
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);
В примере выше мы использовали всего три класса, чтобы создавать объекты шести разных типов. Это очень удобно. Более того, у нас вся эта логика сосредоточена в одном классе и в одном методе.
Если мы вдруг решим создать отдельный класс для Огра, мы просто поменяем тут пару строк кода, а не будем перелопачивать половину приложения.
— Согласен. Удачное решение.
— А я тебе о чем говорю, шаблоны проектирования – это сборники удачных решений.
— Знать бы еще, где их применять…
— Ага. Сходу не разберёшься, согласен. Но все равно, лучше знать и не уметь, чем не знать и не уметь. Вот тебе еще полезная ссылка по этому паттерну: Паттерн 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 = new DaemonArmy();
Army winner = FightSimulator.simulate(humans, daemons);
В примере выше у нас есть класс, который симулирует бои между разными расами (армиями), и ему нужно просто передать два объекта Army. С их помощью он сам создает различные войска и проводит виртуальные бои между ними, с целью выявить победителя.
— Ясно. Спасибо. Действительно интересный подход.
Удачное решение, что ни говори.
— Ага.
Вот еще хорошая ссылка по этой теме: Паттерн Abstract Factory
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ