Программирование
Шаблоны проектирования: 5 самых известных
Я представляю шаблоны проектирования, о которых должен знать каждый инженер-программист — 5 самых распространенных.
Когда программисты говорят о шаблонах проектирования, они обычно говорят об установленных и повторно используемых конструкциях или архитектурах, которые решают конкретные проблемы. По большей части шаблоны проектирования определяются независимо от языков программирования. Если вы новичок в программировании, вы, вероятно, все еще не понимаете, о чем я говорю. В любом случае, вы, вероятно, использовали шаблон дизайна в какой-то момент, даже не подозревая об этом. Далее я представляю пять шаблонов проектирования, о которых должен знать каждый инженер-программист.
Оглавление:
- Кто создает и кто использует шаблоны проектирования?
- Шаблон “Одиночка” (Singleton, Синглтон)
- Шаблон “Фасад” (Facade)
- Шаблон “Адаптер” (Adapter)
- Шаблон “Внедрение зависимости” (Dependency Injection)
- Что еще почитать про шаблоны проектирования
Кто создает и кто использует шаблоны проектирования?
Что ж, каждый может создать. Если мы вкратце взглянем на историю шаблонов проектирования, мы обнаружим, что они восходят к Кристоферу Александру, архитектору, который написал статьи и книги, такие как «Узор улиц» или «Язык шаблонов», в которых он говорит об актуальных архитектурных решениях, материалах и инструментах.
В основе […] лежит идея, что люди должны проектировать свои дома, улицы и сообщества. Эта идея […] исходит из наблюдения, что большинство чудесных мест в мире были созданы не архитекторами, а людьми — Кристофер Александр и др., «Язык шаблонов»
На мой взгляд, это очень подходящая метафора, потому что каждый может определить шаблон дизайна, даже если это обычно делают более опытные разработчики. Вы ожидаете, что опытный архитектор программного обеспечения создаст очень сложные шаблоны проектирования, однако каждый разработчик находится в уникальной ситуации и сталкивается с проблемами, требующими индивидуальных решений. Не каждую проблему нужно решать с помощью шаблона проектирования, но опытный инженер-программист признает хорошую возможность.
Если вы относительный новичок в программировании, вы, вероятно, не начнете определять шаблоны проектирования после создания некоторых «Hello World». Более вероятно, что вы начнете получать опыт, изучая и «употребляя» определенные фреймворки, такие как Vue, Angular или ASP. И пока вы будете это делать, вы, вероятно, узнаете о шаблонах проектирования. Когда вы будете подниматься по лестнице технологического мастерства, вы будете узнавать еще больше, пока в какой-то момент вы не перестанете использовать только технологические фреймворки и шаблоны проектирования, но также начнете создавать свои собственные.
Но хватит вступления. Давайте перейдем к моему списку из пяти шаблонов проектирования, которые необходимо знать разработчикам программного обеспечения.
Шаблон “Одиночка” (Singleton, Синглтон)
Синглтон — очевидный выбор и, вероятно, встречается в каждом списке шаблонов проектирования. И для этого есть веские причины: его относительно легко понять, он используется во многих проектах и является неотъемлемой частью других шаблонов проектирования. Популярные примеры применения Singleton включают в себя драйверы баз данных и экземпляры логеров. Синглтон гарантирует, что существует только один экземпляр класса. В следующем фрагменте кода показано, как создать синглтон (C #):
class Singleton | |
{ | |
// Create a new instance if none exist already | |
public static Singleton Instance { get; } = new Singleton(); | |
private Singleton() | |
{ | |
} | |
} |
Статический экземпляр метода доступа гарантирует, что будет создан новый экземпляр класса, если он еще не существует. В противном случае он возвращает уже созданный экземпляр. Поскольку он является общедоступным статическим средством доступа, к одному и тому же экземпляру можно получить доступ из любой точки сборки.
Но почему вам может понадобиться только один экземпляр?
Приведенный ранее пример драйвера базы данных на самом деле является устаревшим, потому что вам следует применять некий шаблон параллелизма, а не синглтон. В этом случае синглтон будет так называемым «анти-шаблоном». Как упоминалось ранее, синглтоны часто являются частью других шаблонов проектирования, таких как шаблон Строителя, Фабрики или Прототипа, которые существуют для создания экземпляров объектов, потому что, например, им могут потребоваться глобальные конфигурации для инициализации, и эти конфигурации и свойства должны поддерживаться единичный экземпляр любой ценой. Еще одно преимущество синглтона — ленивая инициализация — помните, что объект будет создан при первом доступе.
Шаблон “Фасад” (Facade)
Паттерн Фасад указывает на связь с реальной архитектурой, имитируя видимый фасад здания. Вы видите только то, что видно спереди, но не видите, что находится внутри — комнаты, интерьер, электричество и так далее. С технической точки зрения: фасад — это открытый интерфейс для сложной системы, скрытой от пользователя. Если хотите, это упрощает сложную систему. Фасады обычно используются в объектно-ориентированных языках программирования.
Основываясь на примере архитектуры: представьте, что у вас есть система, состоящая из двух компонентов — компонентов «Жилье» и «Жилой дом». Эти компоненты необходимы вам для организации вечеринки, которая может выглядеть следующим образом.
class Singleton | |
{ | |
// Create a new instance if none exist already | |
public static Singleton Instance { get; } = new Singleton(); | |
private Singleton() | |
{ | |
} | |
} |
Фасад для планирования вечеринки будет использовать компоненты соответствующим образом и упростит сложную систему.
public interface IPartyFacade | |
{ | |
public void StartParty(); | |
public void StopParty(); | |
} | |
public class PartyFacade : IPartyFacade | |
{ | |
private readonly IHousing _housing = new Housing(); | |
private readonly ILiving _living = new Living(); | |
public void StartParty() | |
{ | |
_housing.CleanRoom(Room.PartyRoom); | |
_housing.Garden(); | |
_living.Shopping(...); | |
_housing.CookMeal(Meal.Pizza); | |
_living.InviteGuests(5); | |
_living.ServeBeverage(Beverage.Beer); | |
_living.ServeBeverage(Beverage.Wine); | |
_housing.TurnOnHeating(Room.PartyRoom); | |
} | |
public void StopParty() | |
{ | |
_living.PayInvoices(); | |
_housing.CleanRoom(Room.Kitchen); | |
_housing.CleanRoom(Room.PartyRoom); | |
_housing.TurnOffHeating(Room.PartyRoom); | |
} | |
} |
Фасад хочет, чтобы вы только начали вечеринку или остановили ее. Вам не нужно беспокоиться о уборке, приготовлении пищи или покупках. С другой стороны, IPartyFacade не позволяет добавлять другие напитки в список. Вы жертвуете гибкостью ради удобства.
Шаблон “Адаптер” (Adapter)
Шаблон адаптера используется, когда у вас есть компонент, который вы хотите интегрировать в среду, которая изначально не предназначалась для использования данным образом. Чтобы проиллюстрировать этот пример, предположим, что у нас есть система, которая отвечает за зарядку мобильных телефонов. До сих пор это работало только с iPhone. Теперь вы добавляете телефоны Android, которые также нуждаются в зарядке, но вы не можете использовать зарядное устройство iPhone из-за проблем несовместимости. Ваш адаптер будет PhoneAdapter, реализующим как iPhone, так и Android Phone, который сможет различать оба телефона и предоставить вам правильный метод зарядки.
public class PhoneAdapter : AndroidCharger | |
{ | |
private readonly IPhone _iphone; | |
public PhoneAdapter(IPhone iphone) { | |
_iphone = iphone; | |
} | |
public override void UseMicroUsb() { | |
Console.WriteLine("Connecting MicroUsb"); | |
_iphone.UseLightning(); | |
} | |
public override void recharge() | |
{ | |
_iphone.recharge(); | |
} | |
} |
Шаблон “Декоратор” (Decorator)
Этот шаблон проектирования динамически расширяет базовый класс с помощью функций, которых нет в базовом классе, не затрагивая реализации базовых классов. Это может показаться довольно абстрактным, поэтому для пояснения взгляните на следующий пример:
public interface IHouse | |
{ | |
string GetDetails(); | |
double GetPrice(); | |
} | |
public class AtticHouse : IHouse | |
{ | |
public double GetPrice() | |
{ | |
return 30000; | |
} | |
public string GetDetails() | |
{ | |
return "House with attic"; | |
} | |
} | |
public class FlatRoofHouse : IHouse | |
{ | |
public double GetPrice() | |
{ | |
return 20000; | |
} | |
public string GetDetails() | |
{ | |
return "House with flat roof"; | |
} | |
} | |
public abstract class HouseAccessories : IHouse | |
{ | |
private readonly IHouse _house; | |
public HouseAccessories(IHouse house) | |
{ | |
_house = house; | |
} | |
public virtual double GetPrice() | |
{ | |
return _house.GetPrice(); | |
} | |
public virtual string GetDetails() | |
{ | |
return _house.GetDetails(); | |
} | |
} | |
public class MultiStoryPackage : HouseAccessories | |
{ | |
public MultiStoryPackage(IHouse house) : base(house) | |
{ | |
} | |
public override string GetDetails() | |
{ | |
return base.GetDetails() + " + Multi Story Package"; | |
} | |
public override double GetPrice() | |
{ | |
return base.GetPrice() + 5000; | |
} | |
} | |
public class CustomPaintingPackage : HouseAccessories | |
{ | |
public CustomPaintingPackage(IHouse house) : base(house) | |
{ | |
} | |
public override string GetDetails() | |
{ | |
return base.GetDetails() + " + Custom Painting Package"; | |
} | |
public override double GetPrice() | |
{ | |
return base.GetPrice() + 1000; | |
} | |
} | |
public class RealEstateAgent | |
{ | |
public static void SellHouse() | |
{ | |
var basicHouse = new AtticHouse(); | |
HouseAccessories upgraded = new MultiStoryPackage(basicHouse); | |
upgraded = new CustomPaintingPackage(upgraded); | |
Console.WriteLine($"House: '{upgraded.GetDetails()}' Cost: {upgraded.GetPrice()}"); | |
} | |
} |
Абстрактный класс HouseAccessories украшает IHouse и обеспечивает дополнительную функциональность. В методе SellHouse мы используем базовый AtticHouse и расширяем его, добавляя MultiStoryPackage, а также CustomPaintingPackage. Полученный результат будет такой:
House: House with attic + Multi Story Package + Custom Painting Package Cost: 36000 Дом: Дом с мансардой + многоэтажный пакет + индивидуальная покраска Стоимость пакета: 36000
Шаблон “Внедрение зависимости” (Dependency Injection)
Последний шаблон проектирования в моем списке — внедрение зависимостей. Этот вариант проще, чем кажется на первый взгляд, но, по моему опыту, он имеет тенденцию сбивать с толку начинающих программистов. Идея состоит в том, чтобы уменьшить проблемы, отделив создание объекта от его использования. Это может повысить удобочитаемость и возможность повторного использования кода, но, с другой стороны, обеспечивает определенный уровень сложности — разработчикам приходится перемещаться по большему количеству классов, в IDE могут возникать проблемы с разрешением ссылок, а также нужны дополнительные предварительные усилия по реализации и настройке зависимостей.
public class ExampleClass | |
{ | |
// the dependend class to be injected. Must be injected with the constructor. | |
private readonly IMyDependency _myDependency; | |
// While constructing an instance of our class, we inject an instance of the dependent class. | |
public ExampleClass(IMyDependency myDependency) | |
{ | |
_myDependency = myDependency; | |
} | |
public void OnGet() | |
{ | |
_myDependency.Write("Successfully injected our dependency."); | |
} | |
} |
Как видите, зависимость определяется интерфейсом, а не классом реализации. Это может быть какой-то класс регистратора, но на самом деле ему не нужно знать. Единственное, что должен знать использующий класс, это то, что у зависимости есть функция с именем Write, чтобы иметь возможность ее использовать. Однако инфраструктура внедрения зависимостей должна знать, какую реализацию использовать. В следующем кратком примере ASP.NET Core показано, как удовлетворить это требование:
public class ExampleClass | |
{ | |
// the dependend class to be injected. Must be injected with the constructor. | |
private readonly IMyDependency _myDependency; | |
// While constructing an instance of our class, we inject an instance of the dependent class. | |
public ExampleClass(IMyDependency myDependency) | |
{ | |
_myDependency = myDependency; | |
} | |
public void OnGet() | |
{ | |
_myDependency.Write("Successfully injected our dependency."); | |
} | |
} |
Что еще почитать про шаблоны проектирования
Как было сказано в начале статьи, существует множество шаблонов проектирования, и многие из них, такие как шаблон Стратегия или Хранитель, заслуживают внимания.
Если вам интересно копнуть глубже, я рекомендую книгу «Паттерны проектирования. Повторно используемые элементы объектно-ориентированного программного обеспечения» Эриха Гаммы и других.
Дополнительно:
- Современные шаблоны проектирования архитектуры для профессионалов
- Шпаргалка по шаблонам проектирования
-
Видео и подкасты для разработчиков4 недели назад
Как устроена мобильная архитектура. Интервью с тех. лидером юнита «Mobile Architecture» из AvitoTech
-
Новости4 недели назад
Видео и подкасты о мобильной разработке 2025.10
-
Новости3 недели назад
Видео и подкасты о мобильной разработке 2025.11
-
Видео и подкасты для разработчиков2 недели назад
Javascript для бэкенда – отличная идея: Node.js, NPM, Typescript