В ней были представлены 23 шаблона, ставших сейчас основными. Данная работа дала толчок к изучению паттернов программистами. Издание «банды четырех» (так в шутку прозвали авторов книги) до сих пор остается одним из ИТ-бестселлеров, и его постоянно публикуют.
В целом паттерны представляют собой некую архитектурную конструкцию, помогающую описать и решить определенную общую задачу проектирования. Они приобрели такую популярность потому, что разработка ПО ко времени их формализации уже была достаточно развита. Многие понимали, что не стоит изобретать велосипед, а использование паттернов часто бывает полезным как отдельному разработчику, так и целой команде.
Впрочем, применение шаблонов проектирования связано и с определенными проблемами. В частности, распространено мнение, что только специалист, обладающий достаточно высокой квалификацией и понимающий, какие из паттернов ему нужны, сумеет правильно использовать их в своих программах. Кроме того, зачастую некоторые разработчики, изучившие лишь несколько шаблонов проектирования, начинают употреблять их повсюду, даже там, где они не слишком хорошо справляются с задачей и усложняют создаваемое ПО.
«Банда четырех» разделила паттерны проектирования на три основные группы:
-
порождающие — призванные создавать объекты;
-
структурные — меняющие структуру взаимодействия между классами;
-
поведенческие — отвечающие за поведение объектов.
Кстати, в наши дни несложно выделить и другие группы и даже антипаттерны, подсказывающие, как не надо разрабатывать ПО.
Теперь выберем для обсуждения какой-нибудь паттерн, например абстрактную фабрику. Так, Abstract Factory — это один из самых известных порождающих шаблонов проектирования. Он позволяет разработчику создать интерфейс для объектов, каким-либо образом связанных между собой. Причем не требуется указывать конкретные классы, поскольку работать с каждым из них можно будет через этот интерфейс. С помощью такой фабрики удастся создавать группы объектов, реализующих общее поведение. Преимущество данного паттерна заключается в том, что он изолирует конкретные классы, благодаря чему легко заменять семейства продуктов. А к его недостаткам следует отнести то, что при расширении возможностей фабрики путем добавления нового типа продуктов придется редактировать все конкретные реализации Abstract Factory, а это порой бывает недопустимо, например, если уже создано 100 конкретных фабрик.
А теперь рассмотрим, как на практике можно использовать этот паттерн. Сначала создадим абстрактную фабрику CarFactory, содержащую семейство из двух объектов — автомобиля и двигателя для него.
abstract class CarFactory
{
public abstract AbstractCar CreateCar();
public abstract AbstractEngine CreateEngine();
}
В результате у нас появился абстрактный класс с двумя методами, обеспечивающими получение соответствующих абстрактных объектов. Теперь реализуем первую конкретную фабрику, создающую класс, описывающий автомобиль BMW и двигатель для него:
class BMWFactory : CarFactory
{
public override AbstractCar CreateCar()
{
return new BMWCar();
}
public override AbstractEngine CreateEngine()
{
return new BMWEngine();
}
}
Сделаем то же самое для автомобиля марки Audi, чтобы у нас возникла вторая конкретная фабрика:
class AudiFactory : CarFactory
{
public override AbstractCar CreateCar()
{
return new AudiCar();
}
public override AbstractEngine CreateEngine()
{
return new AudiEngine();
}
}
Теперь опишем абстрактный класс для наших автомобилей. В данном случае у них будет один метод, позволяющий узнать максимальную скорость машины. С его помощью мы обратимся и ко второму объекту — двигателю:
abstract class AbstractCar
{
public abstract void MaxSpeed(AbstractEngine engine);
}
Все двигатели, в свою очередь, будут содержать один параметр — максимальную скорость. Эта простая общедоступная переменная позволит сократить объем программы в данном примере:
abstract class AbstractEngine
{
public int max_speed;
}
Реализуем класс для автомобиля BMW:
class BMWCar : AbstractCar
{
public override void MaxSpeed(AbstractEngine engine)
{
Console.WriteLine(«Макcимальная скорость: « + engine.max_speed.ToString());
}
}
А затем определяем параметры его двигателя:
class BMWEngine : AbstractEngine
{
public BMWEngine()
{
max_speed = 200;
}
}
Проделаем то же самое для класса, описывающего автомобиль Audi:
Задаем двигатель для него:class AudiCar : AbstractCar
{
public override void MaxSpeed(AbstractEngine engine)
{
Console.WriteLine(«Макcимальная скорость: « + engine.max_speed.ToString());
}
}
class AudiEngine : AbstractEngine
{
public AudiEngine()
{
max_speed = 180;
}
}
Теперь мы создадим класс Client, где покажем, как осуществляется работа с абстрактной фабрикой. В конструктор такого класса будут передаваться все конкретные фабрики, которые и начнут создавать объекты автомобиль и двигатель. Следовательно, в конструктор класса Client допустимо передать любую конкретную фабрику, работающую с любыми марками автомобилей. А метод Run позволит узнать максимальную скорость конкретной машины.
class Client
{
private AbstractCar abstractCar;
private AbstractEngine abstractEngine;
public Client(CarFactory car_factory)
{
abstractCar = car_factory.CreateCar();
abstractEngine = car_factory.CreateEngine ();
}
public void Run()
{
abstractCar.MaxSpeed(abstractEngine);
}
}
Ниже показано, как вызвать метод Run с различными параметрами:
public static void Main()
{
// Абстрактная фабрика № 1
CarFactory bmw_car = new BMWFactory ();
Client c1 = new Client(bmw_car);
c1.Run();
// Абстрактная фабрика № 2
CarFactory audi_factory = new AudiFactory();
Client c2 = new Client(audi_factory);
c2.Run();
Console.Read();
}
На этом мы сейчас закончим знакомство с абстрактной фабрикой. Чтобы получше разобраться с этим паттерном, рекомендую написать на его основе несколько программ, приняв для примера условие, что фабрика связана с какими-то бизнес-объектами. В следующей нашей статье мы продолжим изучение шаблонов проектирования.