This lesson was translated automatically – it may contain errors.
The basic principle of splitting responsibility.
Responsibility should be taken by whoever holds the most information needed to fulfil it. If a class is the «expert» on some data, then the methods that work with that data belong in that class.
What it looks like in practice
The typical violation: the order total is computed by a service that has to pull the list of items out of the order to do it. The data lives in one place, the logic in another, and any change to the structure of the items breaks the service.
// Violation: the total is computed by someone who does not own the data
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: computed by the one who holds the data
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);
}
// Violation: the total is computed by someone who does not own the data
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: computed by the one who holds the data
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_;
};
// Violation: the total is computed by someone who does not own the data
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: computed by the one who holds the data
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
}
# Violation: the total is computed by someone who does not own the data
class OrderService:
def calculate_total(self, order: "Order") -> float:
return sum(i.price * i.quantity for i in order.items)
# Information Expert: computed by the one who holds the data
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)
// Violation: the total is computed by someone who does not own the data
class OrderService {
calculateTotal(order: Order): number {
return order.items.reduce((sum, i) => sum + i.price * i.quantity, 0);
}
}
// Information Expert: computed by the one who holds the data
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);
}
}
// Violation: the total is computed by someone who does not own the data
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: computed by the one who holds the data
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);
}
}
// Violation: the total is computed by someone who does not own the data
class OrderService {
fun calculateTotal(order: Order): Double = order.items.sumOf { it.price * it.quantity }
}
// Information Expert: computed by the one who holds the data
class Order {
private val items = mutableListOf<OrderItem>()
fun add(item: OrderItem) {
items += item
}
val total: Double get() = items.sumOf { it.price * it.quantity }
}
When the principle does not work
The expert on the data should not always take on the whole responsibility. If the calculation also needs tax rules and currency rates, the order quickly turns into a class that knows everything about everything. That is where High Cohesion kicks in – part of the work moves out to a separate object.