Учебное пособие по основам шаблонов проектированияC#

Место общения программистов C#
Anonymous
Учебное пособие по основам шаблонов проектирования

Сообщение Anonymous »

Я много раз читал вопросы о шаблонах проектирования в C#, но большинство объяснений либо слишком абстрактны, либо содержат только неполные фрагменты кода.
Поэтому здесь я предоставляю небольшое структурированное объяснение наиболее распространенных шаблонов проектирования с помощью простых примеров кода C#. Цель — облегчить понимание того, что делает каждый шаблон, почему он существует и как он выглядит на практике.
Ниже представлено компактное руководство по наиболее часто используемым шаблонам проектирования в C#. Каждый раздел имеет одинаковую структуру:
  • Какую проблему он решает
  • Основная идея
  • Полноценный рабочий пример C#
1. Абстрактная фабрика
Проблема
Во многих приложениях необходимо создавать объекты, которые принадлежат друг другу как семейство (например, компоненты пользовательского интерфейса Windows или компоненты пользовательского интерфейса Mac). Проблема в том, что вы не хотите, чтобы ваш код зависел от конкретных реализаций, таких как WindowsButton или MacButton.
Если вы повсюду напрямую создаете экземпляры конкретных классов, ваш код становится тесно связанным и его трудно изменить.
Идея
Шаблон абстрактной фабрики решает эту проблему, вводя фабричный интерфейс, который создает связанные объекты, не раскрывая их конкретные классы.
Вместо того, чтобы напрямую вызывать new WindowsButton(), вы просите фабрику предоставить вам кнопку.
Это позволяет переключать целые семейства продуктов без изменения клиентского кода.
Код
using System;

interface IButton
{
void Render();
}

class WindowsButton : IButton
{
public void Render() => Console.WriteLine("Windows Button");
}

class MacButton : IButton
{
public void Render() => Console.WriteLine("Mac Button");
}

interface IGUIFactory
{
IButton CreateButton();
}

class WindowsFactory : IGUIFactory
{
public IButton CreateButton() => new WindowsButton();
}

class MacFactory : IGUIFactory
{
public IButton CreateButton() => new MacButton();
}

class Application
{
private IButton button;

public Application(IGUIFactory factory)
{
button = factory.CreateButton();
}

public void Render()
{
button.Render();
}
}

2. Фасад
Проблема
Реальные программные системы часто содержат множество сложных подсистем.
Например, запуск компьютера включает в себя:
  • инициализацию процессора
  • загрузку памяти
  • операции с диском
  • Шаги BIOS
Если клиенту приходится вызывать все это напрямую, код становится:
  • трудночитаемым
  • трудно поддерживать
  • легко неправильно использовать
Итак, проблема в следующем:
Сложная подсистема не должна раскрывать пользователю всю свою сложность. Без структуры клиент должен знать и координировать их все, что приводит к очень сложному коду.
Идея
Шаблон Фасад обеспечивает простой унифицированный интерфейс для сложной системы.
Вместо взаимодействия с несколькими классами клиент взаимодействует только с одним классом «фасада».
Код
using System;

class CPU
{
public void Start() => Console.WriteLine("CPU started");
}

class Memory
{
public void Load() => Console.WriteLine("Memory loaded");
}

class HardDrive
{
public void Read() => Console.WriteLine("Disk read");
}

class ComputerFacade
{
private CPU cpu = new CPU();
private Memory memory = new Memory();
private HardDrive disk = new HardDrive();

public void StartComputer()
{
cpu.Start();
memory.Load();
disk.Read();
}
}


3. Builder
Проблема
Некоторые объекты сложны и требуют нескольких шагов для создания. Если вы используете конструкторы со многими параметрами, код становится трудным для чтения и подвержен ошибкам.
Идея
Шаблон Builder отделяет построение объекта от представления.
Вместо того, чтобы строить все сразу, вы создаете объект шаг за шагом.
Код
using System;

class Product
{
public string Parts = "";
public void Show() => Console.WriteLine(Parts);
}

interface IBuilder
{
void BuildPartA();
void BuildPartB();
Product GetResult();
}

class ConcreteBuilder : IBuilder
{
private Product product = new Product();

public void BuildPartA() => product.Parts += "PartA ";
public void BuildPartB() => product.Parts += "PartB ";

public Product GetResult() => product;
}

class Director
{
public void Construct(IBuilder builder)
{
builder.BuildPartA();
builder.BuildPartB();
}
}

4. Декоратор
Проблема
Иногда требуется расширить поведение объектов, не изменяя их исходный код и не создавая множество подклассов.
Идея
Шаблон «Декоратор» оборачивает объект и динамически добавляет новое поведение.
Вместо изменения исходного класса вы «обертываете» его другим классом.
Код
using System;

interface IComponent
{
void Operation();
}

class ConcreteComponent : IComponent
{
public void Operation() => Console.WriteLine("Base Operation");
}

class Decorator : IComponent
{
protected IComponent component;

public Decorator(IComponent component)
{
this.component = component;
}

public virtual void Operation()
{
component.Operation();
}
}

class ConcreteDecorator : Decorator
{
public ConcreteDecorator(IComponent component) : base(component) {}

public override void Operation()
{
base.Operation();
Console.WriteLine("Extended behavior");
}
}

5. Итератор
Проблема
Коллекции должны быть проходимы, но:
  • внутренняя структура должна оставаться скрытой
  • клиент не должен зависеть от реализации
Идея
Итератор шаблон предоставляет стандартный способ перемещения по элементам один за другим.
Код
using System;
using System.Collections;
using System.Collections.Generic;

class Numbers : IEnumerable
{
private int[] values = { 1, 2, 3, 4 };

public IEnumerator GetEnumerator()
{
foreach (var v in values)
yield return v;
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

6. Прототип
Проблема
Создание объектов с нуля может быть дорогостоящим или ненужным, если аналогичный объект уже существует.
Идея
Вместо того, чтобы создавать новые объекты, вы клонируете существующие.
Код
using System;

class Prototype
{
public int Value;

public Prototype Clone()
{
return (Prototype)this.MemberwiseClone();
}
}

7. Стратегия
Проблема
Вы хотите иметь возможность переключать алгоритмы во время выполнения, не меняя код, который их использует.
Идея
Инкапсулировать алгоритмы в отдельные классы и сделать их взаимозаменяемыми.
Код
using System;

interface IStrategy
{
void Execute();
}

class StrategyA : IStrategy
{
public void Execute() => Console.WriteLine("Strategy A");
}

class StrategyB : IStrategy
{
public void Execute() => Console.WriteLine("Strategy B");
}

class Context
{
private IStrategy strategy;

public Context(IStrategy strategy)
{
this.strategy = strategy;
}

public void SetStrategy(IStrategy strategy)
{
this.strategy = strategy;
}

public void Execute()
{
strategy.Execute();
}
}

8. Цепочка ответственности
Проблема
Запрос должен обрабатываться несколькими обработчиками, но заранее неизвестно, какой из них.
Идея
Передавать запрос по цепочке, пока его кто-нибудь не обработает.
Код
using System;

abstract class Handler
{
protected Handler next;

public void SetNext(Handler next)
{
this.next = next;
}

public virtual void Handle(string request)
{
next?.Handle(request);
}
}

class HandlerA : Handler
{
public override void Handle(string request)
{
if (request == "A")
Console.WriteLine("Handled by A");
else
base.Handle(request);
}
}

class HandlerB : Handler
{
public override void Handle(string request)
{
if (request == "B")
Console.WriteLine("Handled by B");
else
base.Handle(request);
}
}

9. Синглтон
Проблема
Вам нужен ровно один экземпляр класса (например, конфигурация, ведение журнала, диспетчер подключений к базе данных).
Идея
Ограничить создание объекта, чтобы глобально существовал только один экземпляр.
Код
using System;

class Singleton
{
private static Singleton instance;
private static readonly object lockObj = new object();

private Singleton() {}

public static Singleton Instance
{
get
{
lock (lockObj)
{
if (instance == null)
instance = new Singleton();

return instance;
}
}
}

public void DoSomething()
{
Console.WriteLine("Singleton working");
}
}

Вернуться в «C#»