Программа курса← Объектно ориентированное программирование
1История ООП
2Объект и Класс
3Базовые концепции ООП
4Принцип проектирования GRASP
5Принцип проектирования SOLID
6Паттерны GOF
7Методические материалы

Information Expert (Информационный эксперт)

Базовый принцип разделения ответственности.

Ответственность на себя должен брать тот, кто обладает максимумом информации для её исполнения. Если класс – «эксперт» над какими-то данными, то методы для обработки этих данных должны быть в этом классе.

Как это выглядит на практике

Типичное нарушение: сумму заказа считает сервис, которому для этого приходится вытаскивать из заказа список позиций. Данные лежат в одном месте, логика – в другом, и любое изменение структуры позиций ломает сервис.

// Нарушение: считать сумму лезет тот, кто не владеет данными
public class OrderService
{
    public decimal CalculateTotal(Order order)
    {
        decimal total = 0;
        foreach (var item in order.Items)
            total += item.Price * item.Quantity;

        return total;
    }
}

// Information Expert: считает тот, у кого есть данные
public class Order
{
    private readonly List<OrderItem> _items = new();

    public decimal Total => _items.Sum(i => i.Price * i.Quantity);

    public void Add(OrderItem item) => _items.Add(item);
}
// Нарушение: считать сумму лезет тот, кто не владеет данными
class OrderService {
public:
    double CalculateTotal(const Order& order) const {
        double total = 0;
        for (const auto& item : order.items()) {
            total += item.price * item.quantity;
        }

        return total;
    }
};

// Information Expert: считает тот, у кого есть данные
class Order {
public:
    void Add(const OrderItem& item) { items_.push_back(item); }

    double Total() const {
        double total = 0;
        for (const auto& item : items_) {
            total += item.price * item.quantity;
        }

        return total;
    }

private:
    std::vector<OrderItem> items_;
};
// Нарушение: считать сумму лезет тот, кто не владеет данными
type OrderService struct{}

func (OrderService) CalculateTotal(order Order) float64 {
	total := 0.0
	for _, item := range order.Items {
		total += item.Price * float64(item.Quantity)
	}

	return total
}

// Information Expert: считает тот, у кого есть данные
type Order struct {
	items []OrderItem
}

func (o *Order) Add(item OrderItem) { o.items = append(o.items, item) }

func (o *Order) Total() float64 {
	total := 0.0
	for _, item := range o.items {
		total += item.Price * float64(item.Quantity)
	}

	return total
}
# Нарушение: считать сумму лезет тот, кто не владеет данными
class OrderService:
    def calculate_total(self, order: "Order") -> float:
        return sum(i.price * i.quantity for i in order.items)

# Information Expert: считает тот, у кого есть данные
class Order:
    def __init__(self) -> None:
        self._items: list[OrderItem] = []

    def add(self, item: OrderItem) -> None:
        self._items.append(item)

    @property
    def total(self) -> float:
        return sum(i.price * i.quantity for i in self._items)
// Нарушение: считать сумму лезет тот, кто не владеет данными
class OrderService {
    calculateTotal(order: Order): number {
        return order.items.reduce((sum, i) => sum + i.price * i.quantity, 0);
    }
}

// Information Expert: считает тот, у кого есть данные
class Order {
    private readonly items: OrderItem[] = [];

    add(item: OrderItem): void {
        this.items.push(item);
    }

    get total(): number {
        return this.items.reduce((sum, i) => sum + i.price * i.quantity, 0);
    }
}
// Нарушение: считать сумму лезет тот, кто не владеет данными
public class OrderService {
    public BigDecimal calculateTotal(Order order) {
        return order.getItems().stream()
                .map(i -> i.getPrice().multiply(BigDecimal.valueOf(i.getQuantity())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}

// Information Expert: считает тот, у кого есть данные
public class Order {
    private final List<OrderItem> items = new ArrayList<>();

    public void add(OrderItem item) {
        items.add(item);
    }

    public BigDecimal getTotal() {
        return items.stream()
                .map(i -> i.getPrice().multiply(BigDecimal.valueOf(i.getQuantity())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}
// Нарушение: считать сумму лезет тот, кто не владеет данными
class OrderService {
    fun calculateTotal(order: Order): Double = order.items.sumOf { it.price * it.quantity }
}

// Information Expert: считает тот, у кого есть данные
class Order {
    private val items = mutableListOf<OrderItem>()

    fun add(item: OrderItem) {
        items += item
    }

    val total: Double get() = items.sumOf { it.price * it.quantity }
}

Когда принцип не работает

Эксперт по данным не всегда должен брать ответственность целиком. Если для расчёта нужны ещё и налоговые правила, и курсы валют, заказ быстро превратится в класс, который знает всё на свете. Тогда вступает в силу High Cohesion – часть работы уходит отдельному объекту.