TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.75K subscribers
Post #3168 1.9K
День 2641. #ЗаметкиНаПолях
Паттерн «Интерпретатор» в .NET. Начало
Когда бизнес-правила похоронены в цепочках if-else.

Система скидок начиналась с простого: 10% на заказы свыше $100. Одно условие, одно правило. Затем отдел маркетинга захотел: "20% для VIP-клиентов И заказом свыше $200." Затем: "Бесплатную доставку товаров категории "Электроника" ИЛИ за заказ свыше $500." И т.д., и т.п.

Теперь метод предоставления скидок — это 150 строк вложенных условных операторов, к которым никто не хочет прикасаться:
public decimal CalculateDiscount(
Order order, Customer customer)
{
if (customer.IsVip && order.Total > 200)
return order.Total * 0.20m;
else if (order.Total > 100)
return order.Total * 0.10m;
else if (order.Items.Any(i => i.Category == "Electronics") || order.Total > 500)
order.Shipping = 0;

// … ещё 20 условий

return order.Total;
}


Каждое новое правило требует модификации кода, повторного развёртывания и тестирования всей цепочки. Маркетинг не может обновлять правила без участия разработчиков. А логика совершенно непрозрачна — никто не может прочитать её и понять весь набор правил.

Проблема в том, что эти правила представляют собой бизнес-логику, которая часто меняется, но они заблокированы внутри скомпилированного кода. Нужен способ представить правила в виде данных — чего-то, что можно анализировать, компоновать и динамически оценивать.

Паттерн «Интерпретатор»
Паттерн «Интерпретатор» определяет грамматику для языка и предоставляет интерпретатор, который оценивает выражения на этом языке. Каждое правило в грамматике - класс. Сложные правила компонуются из простых.

Определим интерфейс выражений и атомарные выражения:
// Контекст - данные, которые будут оцениваться в выражениях
public class RuleContext(Order order, Customer customer)
{
public Order Order { get; } = order;
public Customer Customer { get; } = customer;
}

// Абстрактное выражение
public interface IExpression
{
bool Interpret(RuleContext ctx);
}

// Окончательные выражения: атомарные условия
public class OrderTotalGreaterThan(decimal threshold)
: IExpression
{
public bool Interpret(RuleContext ctx)
=> context.Order.Total > threshold;
}

public class CustomerIsVip : IExpression
{
public bool Interpret(RuleContext ctx)
=> context.Customer.IsVip;
}

public class OrderContainsCategory(string category)
: IExpression
{
public bool Interpret(RuleContext ctx)
=> ctx.Order.Items.Any(i =>
i.Category.Equals(category,
StringComparison.OrdinalIgnoreCase));
}

// …

Теперь нетерминальные выражения – логические действия:
// И
public class AndExpression(IExpression left, IExpression right)
: IExpression
{
public bool Interpret(RuleContext ctx)
=> left.Interpret(ctx) && right.Interpret(ctx);
}

// ИЛИ
public class OrExpression(IExpression left, IExpression right)
: IExpression
{
public bool Interpret(RuleContext ctx)
=> left.Interpret(ctx) || right.Interpret(ctx);
}

// НЕ
public class NotExpression(IExpression expression)
: IExpression
{
public bool Interpret(RuleContext ctx)
=> !expression.Interpret(ctx);
}


Составляем правила из выражений:
// VIP-клиент и заказ > $200
var vipHighValueRule = new AndExpression(
new CustomerIsVip(),
new OrderTotalGreaterThan(200));

var ctx = new RuleContext(order, customer);

if (vipHighValueRule.Interpret(ctx))
discount = 0.20m;

Правила — это компонуемые структуры данных. Вы можете хранить их, сериализовать и создавать из конфигурации.

Почему это лучше
1. Правила — это данные, а не код. Вы можете хранить определения правил в БД и создавать деревья выражений во время выполнения. Для новых правил не требуется повторное развёртывание.
2. Компонуемость. Операторы И, ИЛИ и НЕ объединяют любые выражения. Сложные правила строятся из простых, протестированных компонентов.
3. Тестируемость. Каждое выражение представляет собой отдельный класс с одним методом. Тестируйте OrderTotalGreaterThan независимо от всего остального.

Окончание следует…

Источник:
https://thecodeman.net/posts/interpreter-pattern-in-dotnet
  • 👍 8
More from @netdeveloperdiary
  1. Sep 27, 2026День 2797. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Окончание Начало Продол…
  2. Sep 26, 2026День 2796. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Продолжение Начало Три…
  3. Sep 25, 2026День 2795. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Начало Проблема с позиц…
  4. Sep 24, 2026День 2794. #Оффтоп #Здоровье Сегодня будет необычный пост. Завтра в Москве стартует конфер…
  5. Sep 23, 2026День 2793. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
  6. Sep 22, 2026День 2792. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
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 →