TGViewer
Библиотека шарписта | C#, F#, .NET, ASP.NET Библиотека шарписта | C#, F#, .NET, ASP.NET @csharpproglib · 21.7K subscribers
Post #7083 2.42K
👨‍💻 Паттерн стратегия в C#. Когда он нужен, а когда нет

Стратегия инкапсулирует семейство алгоритмов и позволяет подменять их на лету. Звучит полезно. На практике это часто превращается в десятки файлов ради замены одной функции. В современном C# для этого есть средства попроще.

Классическая реализация

Стандартный подход выглядит так. Интерфейс ISortStrategy, пять конкретных реализаций, класс-контекст, который принимает стратегию и вызывает её метод.

public interface ISortStrategy
{
void Sort(List<int> list);
}

public class BubbleSortStrategy : ISortStrategy
{
public void Sort(List<int> list)
{
// реализация пузырьковой сортировки
}
}

public class QuickSortStrategy : ISortStrategy
{
public void Sort(List<int> list)
{
// реализация быстрой сортировки
}
}

public class SortContext
{
private ISortStrategy _strategy;

public SortContext(ISortStrategy strategy)
{
_strategy = strategy;
}

public void SetStrategy(ISortStrategy strategy)
{
_strategy = strategy;
}

public void ExecuteSort(List<int> list)
{
_strategy.Sort(list);
}
}


Для каждого нового алгоритма мы создаём отдельный класс. Контекст ничего не знает о конкретной реализации и работает через интерфейс. Всё по канону GoF.

Проблема в том, что для простых случаев это избыточно.

Что предлагает C# вместо этого

Начиная с .NET 3.5 в языке есть Func<T>, Action<T> и лямбда-выражения. Strategy по сути означает «передай поведение как параметр». А в C# функции и так являются объектами первого класса.

Тот же пример без интерфейса и дополнительных классов:

public class SortContext
{
public void ExecuteSort(List<int> list, Action<List<int>> sortAlgorithm)
{
sortAlgorithm(list);
}
}

// использование
var context = new SortContext();

context.ExecuteSort(numbers, list => list.Sort());
context.ExecuteSort(numbers, list =>
{
// своя логика сортировки
});


Один метод принимает делегат Action<List<int>>. Никаких интерфейсов, никаких отдельных файлов. Поведение передаётся напрямую.

Ещё пример. Допустим, есть расчёт скидки:

public class PriceCalculator
{
public decimal Calculate(decimal price, Func<decimal, decimal> discountStrategy)
{
return discountStrategy(price);
}
}

var calculator = new PriceCalculator();

decimal result = calculator.Calculate(100m, price => price * 0.9m);
decimal vipResult = calculator.Calculate(100m, price => price * 0.8m);


Func<decimal, decimal> принимает цену, возвращает цену со скидкой. Стратегия задаётся в одну строку прямо в месте вызова.

Когда интерфейсы всё-таки нужны

Делегаты хороши для простых случаев. Но если стратегия содержит несколько методов, внутреннее состояние или сложную логику, интерфейс остаётся правильным выбором.

public interface IPaymentStrategy
{
bool Validate(PaymentDetails details);
PaymentResult Process(PaymentDetails details);
void Rollback(string transactionId);
}


Здесь три связанных метода. Засунуть их в три отдельных Func можно, но читаемость пострадает. Интерфейс даёт единую точку контракта и нормально тестируется через моки.

Ещё один аргумент за интерфейс: когда реализации регистрируются в DI-контейнере и выбираются в рантайме. Например, разные провайдеры оплаты в зависимости от региона пользователя. Тут интерфейс упрощает конфигурацию и тестирование.

Если стратегия укладывается в одну функцию, используйте Func<T> или Action<T>. Это проще, короче и не требует дополнительных абстракций. Если стратегия включает несколько связанных операций или вам нужна подмена реализаций через DI, интерфейс по-прежнему оправдан. Паттерн не устарел, но применять его стоит осознанно, а не по привычке.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#il_люминатор
  • 👍 15
  • ❤ 4
More from @csharpproglib
  1. Sep 21, 2026⚙️ Настоящие атомарные операции Если Volatile решает проблему видимости, то Interlocked ре…
  2. Sep 20, 2026💪 Разминка перед трудовыми буднями Что произойдёт? ❤️ — список станет [1, 3] 🔥 — Invalid…
  3. Sep 19, 2026🧩 Middleware в ASP.NET Core: 3 ловушки Middleware — звено HTTP pipeline: app.Use(async (c…
  4. Sep 18, 2026📍 Навигация: Вакансии • Задачи • Собесы 🐸Библиотека шарписта #garbage_collector
  5. Sep 17, 2026🦆 Duck typing в C# — почти как в TypeScript В TypeScript достаточно, чтобы у объекта был…
  6. Sep 17, 2026🧠 Почему .NET-под съедает всю память? kubectl top pod показывает 1 ГБ, а managed heap зан…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →