Шаблоны проектирования на C#
Шаблоны проектирования являются важными инструментами в разработке программного обеспечения, которые обеспечивают проверенные решения распространенных проблем проектирования. В C# разработчики могут использовать различные шаблоны проектирования для создания надежного и поддерживаемого программного обеспечения.
В этой статье мы рассмотрим некоторые из наиболее распространенных шаблонов проектирования в C# и приведем реальные примеры для каждой категории.
Порождающие шаблоны проектирования (Creational Design Patterns )
Порождающие шаблоны проектирования имеют дело с механизмами создания объектов. Они помогают контролировать процесс создания экземпляров объектов и обеспечивают гибкость в способах создания объектов.
Фабричный метод (Factory pattern)
Проблема: непосредственное создание объектов может привести к тесной связи между клиентским кодом и конкретными классами, что затрудняет адаптацию к изменениям или расширение системы новыми классами.
Фабричный метод решает эту проблему, предоставляя интерфейс для создания объектов, не раскрывая их конкретных реализаций.
Решение: фабричный метод определяет соответствующий класс или метод, отвечающий за создание объектов. Клиенты запрашивают объекты у него, используя общий интерфейс, позволяющий им работать с абстрактными типами, оставляя создание конкретных объектов на усмотрение класса или метода.
// Пример: Создание различных типов транспортных средств
// с использованием фабричного метода
public interface IVehicle
{
void Drive();
}
public class Car : IVehicle
{
public void Drive() => Console.WriteLine("Езда на авто");
}
public class Bicycle : IVehicle
{
public void Drive() => Console.WriteLine("Езда на велосипеде");
}
public class VehicleFactory
{
public IVehicle CreateVehicle(string type)
{
switch (type)
{
case "car": return new Car();
case "bicycle": return new Bicycle();
default: throw new ArgumentException("Неправильный тип машины");
}
}
}
Шаблон проектирования Строитель (Builder pattern)
Проблема: При работе со сложными объектами с большим количеством параметров конфигурации конструкторы с большим количеством параметров становятся громоздкими и подвержены ошибкам.
Шаблон Builder упрощает создание объекта, отделяя процесс построения от фактического представления.
Решение: В шаблоне Builder представлен класс director, который управляет созданием сложных объектов с помощью интерфейса builder. Интерфейс builder предоставляет методы для пошаговой настройки свойств объекта, что делает создание объектов с различными конфигурациями более удобным для чтения и сопровождения.
// класс Product
class Pizza
{
public string Dough { get; set; }
public string Sauce { get; set; }
}
// интерфейс Builder
interface IPizzaBuilder
{
IPizzaBuilder SetDough(string dough);
IPizzaBuilder SetSauce(string sauce);
Pizza Build();
}
// Concrete builder
class PizzaBuilder : IPizzaBuilder
{
private Pizza pizza = new Pizza();
public IPizzaBuilder SetDough(string dough)
{
pizza.Dough = dough;
return this;
}
public IPizzaBuilder SetSauce(string sauce)
{
pizza.Sauce = sauce;
return this;
}
public Pizza Build()
{
return pizza;
}
}
// Director (необязательный)
class PizzaDirector
{
private readonly IPizzaBuilder pizzaBuilder;
public PizzaDirector(IPizzaBuilder builder)
{
pizzaBuilder = builder;
}
public Pizza CreateVegetarianPizza()
{
return pizzaBuilder
.SetDough("Цельная пшеница")
.SetSauce("Помидор")
.Build();
}
public Pizza CreatePepperoniPizza()
{
return pizzaBuilder
.SetDough("Тонкая корочка")
.SetSauce("Острый помидор")
.Build();
}
}
Одноэлементный шаблон (Singleton Pattern)
Проблема: обеспечение того, чтобы у класса был только один экземпляр, может быть сложной задачей, особенно когда доступ к этому экземпляру требуется нескольким частям кодовой базы.
Шаблон Singleton решает эту проблему, ограничивая создание экземпляра класса одним экземпляром и предоставляя глобальную точку доступа к этому экземпляру.
Решение: Singleton предполагает создание закрытого конструктора для предотвращения прямого создания экземпляра и статического метода или свойства для доступа к единственному экземпляру. При первом запросе экземпляра он создается; последующие запросы возвращают существующий экземпляр, гарантируя, что существует только один экземпляр класса.
// Пример: Создание единого менеджера конфигурации
public class ConfigurationManager
{
private static ConfigurationManager _instance;
private ConfigurationManager() { }
public static ConfigurationManager Instance
{
get
{
if (_instance == null)
{
_instance = new ConfigurationManager();
}
return _instance;
}
}
public string GetSetting(string key)
{
// Retrieve setting from configuration file
return "Значение " + key;
}
}
Структурные шаблоны проектирования
Шаблон адаптер (Adapter Pattern)
Проблема: интеграция новых компонентов или библиотек с несовместимыми интерфейсами в существующую систему может оказаться проблематичной.
Шаблон Adapter позволяет этим компонентам работать вместе, создавая адаптер, который действует как мост между несовместимыми интерфейсами.
Решение: Шаблон Adapter определяет класс адаптера, который реализует ожидаемый интерфейс для клиентского кода. Этот класс адаптера делегирует вызовы методам адаптированного объекта, позволяя ему взаимодействовать с клиентским кодом, не предоставляя несовместимый интерфейс.
// Пример: Адаптация старого принтера к современному интерфейсу
public interface IMachine
{
void Start();
void Stop();
}
public class OldPrinter
{
public void PowerOn() => Console.WriteLine("Старый принтер включен");
public void PowerOff() => Console.WriteLine("Старый принтер выключен");
}
public class PrinterAdapter : IMachine
{
private readonly OldPrinter _printer;
public PrinterAdapter(OldPrinter printer)
{
_printer = printer;
}
public void Start() => _printer.PowerOn();
public void Stop() => _printer.PowerOff();
}
Шаблон мост (Bridge Pattern)
Проблема: отделение абстракции от ее реализации имеет решающее значение в крупных программных системах для обеспечения гибкости и удобства обслуживания.
Шаблон Bridge обеспечивает такое разделение, определяя две отдельные иерархии для абстракции и реализации, позволяя им развиваться независимо.
Решение: Шаблон Bridge предполагает создание абстрактного класса (абстракции), который содержит ссылку на интерфейс (реализацию). Абстракция передает интерфейсу детали реализации, позволяя использовать различные реализации взаимозаменяемо, не влияя на абстракцию.
// Пример: Создание различных форм
// с помощью различных методов рисования
public interface IDrawAPI
{
void DrawCircle(int radius);
}
public class RedCircle : IDrawAPI
{
public void DrawCircle(int radius) => Console.WriteLine($"Рисование красного круга с радиусом {radius}");
}
public class GreenCircle : IDrawAPI
{
public void DrawCircle(int radius) => Console.WriteLine($"Рисование зеленого круга с радиусом {radius}");
}
public abstract class Shape
{
protected IDrawAPI drawAPI;
protected Shape(IDrawAPI drawAPI)
{
this.drawAPI = drawAPI;
}
public abstract void Draw();
}
public class Circle : Shape
{
private int radius;
public Circle(int radius, IDrawAPI drawAPI) : base(drawAPI)
{
this.radius = radius;
}
public override void Draw() => drawAPI.DrawCircle(radius);
}
Шаблон декоратор (Decorator Pattern)
Проблема: динамическое добавление новых обязанностей к объектам без изменения их иерархии классов может оказаться сложной задачей.
Шаблон Decorator устраняет эту проблему, придавая объектам дополнительные свойства без изменения их структуры.
Решение: в шаблоне Decorator представлены классы декораторов, которые обертывают основной объект (компонент) и предоставляют дополнительные функциональные возможности. Эти декораторы реализуют тот же интерфейс, что и основной объект, что позволяет объединять их для расширения поведения объекта, сохраняя при этом основной объект неизменным.
// Пример: Добавление начинки в пиццу
public abstract class Pizza
{
public abstract string GetDescription();
public abstract double GetCost();
}
public class MargheritaPizza : Pizza
{
public override string GetDescription() => "Пицца Маргарита";
public override double GetCost() => 6.99;
}
public abstract class PizzaDecorator : Pizza
{
protected Pizza pizza;
public PizzaDecorator(Pizza pizza)
{
this.pizza = pizza;
}
public override string GetDescription() => pizza.GetDescription();
public override double GetCost() => pizza.GetCost();
}
public class ExtraCheese : PizzaDecorator
{
public ExtraCheese(Pizza pizza) : base(pizza) { }
public override string GetDescription() => $"{pizza.GetDescription()}, Много сыра";
public override double GetCost() => pizza.GetCost() + 1.50;
}
Поведенческие шаблоны проектирования
Паттерн наблюдателя (Observer Pattern)
Проблема: установление зависимостей между объектами, которые должны получать уведомления об изменениях в состоянии другого объекта, может привести к тесной связи.
Шаблон Observer решает эту проблему, определяя отношение "один ко многим", при котором субъект уведомляет нескольких наблюдателей об изменениях, не зная их конкретных типов.
Решение: шаблон Observer использует субъекта (observable), который ведет список наблюдателей. Когда состояние субъекта изменяется, он уведомляет всех зарегистрированных наблюдателей. Наблюдатели реализуют интерфейс, определяющий метод обновления, позволяющий им реагировать на изменения в состоянии субъекта.
// Пример: внедрение функции наблюдателя за фондовым рынком
public interface IObserver
{
void Update(string message);
}
public class Stock : IObserver
{
private string symbol;
private double price;
public Stock(string symbol, double price)
{
this.symbol = symbol;
this.price = price;
}
public void Update(string message)
{
Console.WriteLine($"{symbol} - Цена: {price} - {message}");
}
}
public class StockMarket
{
private List<IObserver> observers = new List<IObserver>();
public void AddObserver(IObserver observer)
{
observers.Add(observer);
}
public void UpdatePrices()
{
// имитирует изменения цен и уведомляет наблюдателей
foreach (var observer in observers)
{
observer.Update("Цена увеличена на 0.5%");
}
}
}
Командный шаблон (Command Pattern)
Проблема: инкапсуляция запросов в виде объектов обеспечивает гибкость при обработке команд, таких как постановка в очередь, ведение журнала или отмена операций.
Шаблон Command позволяет достичь этого путем инкапсуляции запроса в виде объекта command, отделяя отправителя от получателя.
Решение: шаблон Command определяет объекты command, которые инкапсулируют определенные действия и параметры. Эти команды реализуют общий интерфейс с методом execute. Команду вызывает класс invoker, который можно легко расширить или поменять местами, не изменяя отношения отправитель-получатель.
// Пример: Реализация дистанционного управления
// с помощью различных команд
public interface ICommand
{
void Execute();
}
public class Light
{
public void TurnOn() => Console.WriteLine("Свет включен");
public void TurnOff() => Console.WriteLine("Свет выключен");
}
public class LightOnCommand : ICommand
{
private Light light;
public LightOnCommand(Light light)
{
this.light = light;
}
public void Execute() => light.TurnOn();
}
public class LightOffCommand : ICommand
{
private Light light;
public LightOffCommand(Light light)
{
this.light = light;
}
public void Execute() => light.TurnOff();
}
Стратегический шаблон (Strategy Pattern)
Проблема: переключение между различными алгоритмами или поведением во время выполнения может быть сложной задачей без введения условных операторов.
Шаблон Strategy обеспечивает решение, определяя семейство взаимозаменяемых алгоритмов и упрощая их переключение.
Решение: Шаблон Strategy предполагает определение набора алгоритмов в виде отдельных классов, каждый из которых реализует общий интерфейс. Класс context содержит ссылку на объект стратегии и делегирует ему выполнение алгоритма. Это позволяет изменять стратегии во время выполнения, повышая гибкость и удобство сопровождения.
// Пример: Сортировка списка с использованием
// различных алгоритмов сортировки
public interface ISortStrategy
{
void Sort(List<int> list);
}
public class BubbleSort : ISortStrategy
{
public void Sort(List<int> list)
{
Console.WriteLine("Сортировка с помощью сортировки пузырьком");
// Реализация сортировки алгоритмом сортировки пузырьком
}
}
public class QuickSort : ISortStrategy
{
public void Sort(List<int> list)
{
Console.WriteLine("Сортировка с помощью Быстрой сортировки");
// Реализация алгоритма быстрой сортировки
}
}
public class SortContext
{
private ISortStrategy strategy;
public SortContext(ISortStrategy strategy)
{
this.strategy = strategy;
}
public void SortList(List<int> list)
{
strategy.Sort(list);
}
}
Заключение
Шаблоны проектирования - это мощный инструмент для разработчиков на C#, позволяющий улучшить дизайн и удобство сопровождения их программного обеспечения. Независимо от того, решаете ли вы задачи создания объектов, компоновки или взаимодействия, шаблоны проектирования предлагают проверенные решения распространенных проблем при разработке программного обеспечения. Начните внедрять эти шаблоны в свои проекты на C#, чтобы создавать более чистый и удобный в обслуживании код.
Комментарии
Для того чтобы оставить свое мнение, необходимо зарегистрироваться на сайте