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

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

This lesson is not translated yet – the Russian original is below.

TODO: отвалидировать – текст собран автоматически из материалов курса на Google Drive, проверьте формулировки и код.

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

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

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

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

// Нарушение: считать сумму лезет тот, кто не владеет данными
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 – часть работы уходит отдельному объекту.