Для примера, пусть будет корабль. Он состоит из отсеков. В отсеках может быть установлено какое-либо оборудование. У корабля есть масса, складываемая из массы всех его отсеков. Масса каждого отсека, например, зависит от того, установлено в него оборудование или нет. А масса самого оборудования также может меняться, например, зависеть от массы топлива в топливном баке. И вот об изменении своей массы каждый компонент из этой цепочки должен оповещать.
Для наглядности, сферическая иерархия классов:
public class Spacecraft
{
public IEnumerable<Hardpoint> Hardpoints { get; private set; }
public Single Mass => Hardpoints.Select(hp => hp.Mass).Sum();
}
public class Hardpoint
{
public Equipment InstalledEquipment { get; private set; }
public Boolean IsEquipmentInstalled => InstalledEquipment != null;
public Single Mass => IsEquipmentInstalled ? InstalledEquipment.Mass : 0;
}
public abstract class Equipment
{
public event Action MassChanged;
public Single Mass
{
get { return _mass; }
protected set
{
_mass = value;
MassChanged?.Invoke();
}
}
private Single _mass;
}
(сферичнее некуда)
Иными словами, есть дерево свойств (в данном случае Mass), каждое из которых зависит от своих потомков и свойство на любом из уровней должно уметь оповещать о своем изменении (т.е. вопрос в реализации MassChanged у Hardpoint и Spacecraft наименее затратным способом). Конечно, я могу просто сделать в Hardpoint событие MassChanged и вызывать его по аналогичному событию от установленного оборудования или при его смене. Также в Spacecraft. Очень много однотипного кода подписок/отписок, error-prone.
Другой вариант - нарушить инкапсуляцию и сделать invocator события в Hardpoint публичным, вызывая его при вызове сеттера массы вместе с вызовом своего события. Также сделать и со Spacecraft. Чуть меньше кода, но грязно.
Есть ли какие-нибудь более продвинутые практики?