Паттерн Strategy
Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy — урок 55 из 70.
Паттерн Стратегия — это поведенческий паттерн, который позволяет определить семейство алгоритмов, инкапсулировать каждый из них в отдельный класс и сделать их взаимозаменяемыми во время выполнения. Вместо жесткого кодирования поведения вы внедряете нужную стратегию в объект.
Паттерн состоит из трех частей: интерфейса стратегии, который определяет контракт алгоритма, конкретных классов стратегий, которые реализуют различные варианты поведения, и класса контекста, который использует стратегию:
public interface IPaymentStrategy
{
void Pay(decimal amount);
}
public class CreditCardPayment : IPaymentStrategy
{
public void Pay(decimal amount) =>
Console.WriteLine($"Paid {amount} via Credit Card");
}
public class PayPalPayment : IPaymentStrategy
{
public void Pay(decimal amount) =>
Console.WriteLine($"Paid {amount} via PayPal");
}
public class ShoppingCart
{
private IPaymentStrategy _paymentStrategy;
public void SetPaymentMethod(IPaymentStrategy strategy)
{
_paymentStrategy = strategy;
}
public void Checkout(decimal total)
{
_paymentStrategy.Pay(total);
}
}Контекст (ShoppingCart) не знает, какой метод оплаты он использует — он просто вызывает Pay() для любой предоставленной стратегии:
var cart = new ShoppingCart();
cart.SetPaymentMethod(new CreditCardPayment());
cart.Checkout(99.99m); // Оплачено 99.99 через Credit Card
cart.SetPaymentMethod(new PayPalPayment());
cart.Checkout(49.99m); // Оплачено 49.99 через PayPalПаттерн «Стратегия» (Strategy) устраняет сложную условную логику. Добавление нового способа оплаты означает создание нового класса, который реализует IPaymentStrategy — изменения в ShoppingCart не требуются. Это делает ваш код открытым для расширения, но закрытым для изменения.
Задание
ЛегкоДавайте создадим калькулятор стоимости доставки, используя паттерн Стратегия (Strategy). Различные методы доставки имеют разные алгоритмы ценообразования, и ваша система должна позволять переключаться между ними во время выполнения программы без изменения основной логики обработки заказов.
Вы организуете свой код в трех файлах:
ShippingStrategy.cs: Определите интерфейсIShippingStrategyв пространстве именShippingс методомCalculateCost(double weight), который возвращаетdouble. Затем создайте три конкретных класса стратегий:StandardShipping— рассчитывает стоимость какweight * 1.5ExpressShipping— рассчитывает стоимость какweight * 3.0 + 5.0(включает фиксированную плату за экспресс-доставку)OvernightShipping— рассчитывает стоимость какweight * 5.0 + 10.0(премиальная услуга с более высокой базовой платой)
OrderProcessor.cs: Создайте классOrderProcessorв том же пространстве имен. Это ваш класс контекста, который использует стратегию доставки. Он должен иметь:- Приватное поле для хранения текущей
IShippingStrategy - Метод
SetShippingMethod(IShippingStrategy strategy)для смены стратегии доставки - Метод
ProcessOrder(double weight), который использует текущую стратегию для расчета и возврата стоимости доставки
- Приватное поле для хранения текущей
Program.cs: Объедините все вместе, создавOrderProcessorи продемонстрировав, как можно менять стратегии доставки во время выполнения. Один и тот же процессор может рассчитывать стоимость, используя разные алгоритмы, просто меняя свою стратегию.
Вы получите два входных значения:
- Тип метода доставки:
standard,expressилиovernight - Вес посылки в виде десятичного числа (например,
4.5)
Создайте OrderProcessor, установите соответствующую стратегию доставки на основе входного типа, затем обработайте заказ и выведите рассчитанную стоимость.
Например, если входные данные — express и 4.5, результат должен быть:
18.5Это демонстрирует мощь паттерна Стратегия — ваш OrderProcessor сам по себе не содержит никакой логики расчета доставки. Он просто делегирует задачу той стратегии, которая ему была предоставлена, что позволяет легко добавлять новые методы доставки (например, DroneDelivery) без какой-либо модификации процессора!
Шпаргалка
Паттерн Стратегия (Strategy pattern) — это поведенческий паттерн, который определяет семейство алгоритмов, инкапсулирует каждый из них в отдельный класс и делает их взаимозаменяемыми во время выполнения.
Паттерн состоит из трех частей:
- Интерфейс стратегии: определяет контракт алгоритма
- Классы конкретных стратегий: реализуют различные варианты поведения
- Класс контекста: использует стратегию, не зная деталей её реализации
Пример структуры:
// Интерфейс стратегии
public interface IPaymentStrategy
{
void Pay(decimal amount);
}
// Конкретные стратегии
public class CreditCardPayment : IPaymentStrategy
{
public void Pay(decimal amount) =>
Console.WriteLine($"Paid {amount} via Credit Card");
}
public class PayPalPayment : IPaymentStrategy
{
public void Pay(decimal amount) =>
Console.WriteLine($"Paid {amount} via PayPal");
}
// Класс контекста
public class ShoppingCart
{
private IPaymentStrategy _paymentStrategy;
public void SetPaymentMethod(IPaymentStrategy strategy)
{
_paymentStrategy = strategy;
}
public void Checkout(decimal total)
{
_paymentStrategy.Pay(total);
}
}Использование — переключение стратегий во время выполнения:
var cart = new ShoppingCart();
cart.SetPaymentMethod(new CreditCardPayment());
cart.Checkout(99.99m); // Вывод: Paid 99.99 via Credit Card
cart.SetPaymentMethod(new PayPalPayment());
cart.Checkout(49.99m); // Вывод: Paid 49.99 via PayPalПреимущества:
- Устраняет сложную условную логику
- Принцип открытости/закрытости: открыт для расширения, закрыт для изменения
- Добавление новых стратегий требует только создания новых классов, без изменения существующего кода
Попробуйте сами
using System;
using Shipping;
class Program
{
public static void Main(string[] args)
{
// Чтение входных данных
string shippingType = Console.ReadLine();
double weight = Convert.ToDouble(Console.ReadLine());
// TODO: Создайте экземпляр OrderProcessor
// TODO: На основе shippingType ("standard", "express" или "overnight"),
// создайте соответствующую стратегию доставки и установите её в процессоре
// TODO: Обработайте заказ и выведите рассчитанную стоимость
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыПространства имен и директивыВведение в классы и объектыКлючевое слово 'this'Методы и параметрыПоля и свойстваКонструкторыИнициализаторы объектовИтоги — Простой калькулятор4Наследование
Основы синтаксиса наследования (:)Ключевое слово 'base'Ключевые слова Virtual и OverrideЗапечатанные классыБазовый класс 'object'Итоги — Иерархия сотрудников7Продвинутые возможности
Перегрузка операторовИндексаторы (this[])Переопределение ToString()Методы расширенияПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПотокобезопасный SingletonПаттерн FactoryПаттерн Observer (События)Паттерн Strategy13Финальные испытания
Платформа для онлайн-обученияКомпонент игрового персонажаСервис аренды транспорта2Свойства и статические члены
Автоматически реализуемые свойстваСвойства только для чтения и записиСтатические поля и методыСтатические классыЧлены в виде выражений5Полиморфизм и интерфейсы
Полиморфизм: компиляция vs выполнениеИнтерфейс vs абстрактный классМножественные интерфейсыЯвные интерфейсыUpcasting и DowncastingИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция вместо наследованияGenerics (классы и методы)Делегаты и событияАтрибуты и рефлексияIDisposable и оператор usingОсновы Dependency Injection11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Архитектура классов
Экземплярные и статические данныеКлючевые слова 'readonly' и 'const'Опорные поляПовторение — Менеджер банковских счетов6Инкапсуляция
Модификаторы доступаСвойства для инкапсуляцииРеализация скрытия данныхПаттерны неизменяемостиИтоги — Записи студентов9Переменное количество аргументов
Ключевое слово 'params'Необязательные параметрыИменованные аргументыПерегрузка методов