День 1098. #BestPractices
Лучшие Практики Разработки в C#. Продолжение
Начало
4. Не злоупотребляйте внедрением зависимостей
Представьте, что вы определяете класс Car и из метода Accelerate хотите записать в лог текущую скоростью автомобиля. Вы бы внедрили ILogger, верно? Да, это сработает, и по сегодняшним стандартам дизайна это идеально. Однако, вот такой вопрос: можем ли мы сказать, что класс Car на самом деле зависит от ILogger до такой степени, что без него он не сможет выполнять свою работу?
Ответ прост: Car не должен зависеть от ILogger. Вы можете возразить, что даже если он не полностью зависит от него, он все равно нуждается в нём. Не совсем так. На самом деле ILogger нужен основному приложению, которое знает и о Car, и о ILogger. Основное приложение должно получить некоторую информацию от класса Car, а затем использовать ILogger для регистрации этой информации. Поэтому правильной реализацией будет удалить эту зависимость и реализовать события. В нашем примере класс Car должен определить событие SpeedChanged, а основное приложение должно подписаться на него:
public delegate void SpeedChangedEventHandler(object sender, double speed);
public class Car
{
public event SpeedChangedEventHandler SpeedChanged;
public void Accelerate()
{
var speed = …;
…
OnSpeedChanged(speed);
}
protected virtual void OnSpeedChanged(double speed)
{
SpeedChanged?.Invoke(this, speed);
}
}
Заметьте, что мы не вызываем событие напрямую из метода Accelerate, а оборачиваем вызов в виртуальный метод, который, во-первых, безопасно вызывает событие, а во-вторых, может быть переопределён (например, если для каких-то типов наследников не нужно вызывать это событие).
5. Использование моментальных снимков (Snapshot)
Продолжая пример выше, допустим, мы захотели отображать значение скорости на экране. Создадим объект трекера, который будет подписан на событие SpeedChanged:
public class Tracker
{
private double currentSpeed = 0;
public Tracker(Car car)
{
car.SpeedChanged += (o, speed) =>
{
currentSpeed = speed;
ShowOnScreen(speed);
};
ShowOnScreen(currentSpeed); // здесь будет 0
}
…
}
Проблема в том, что к моменту создания трекера объект Car уже может быть создан, запущен и иметь некоторую постоянную скорость. Тогда до изменения скорости трекер будет выводить на экран 0. Здесь поможет шаблон «Моментальный снимок» (Snapshot). Это неизменяемый объект-значение, хранящий состояние объекта Car. Для него как нельзя лучше подойдёт тип записи:
public record CarState(double speed, double temperature, …);
public class Car
{
public CarState Snapshot { get; private set; } = new CarState(0,0,…);
public void Accelerate()
{
var speed = …;
…
Snapshot = Snapshot with { Speed = speed };
OnSpeedChanged(Snapshot.Speed);
}
…
}
Тогда при создании трекера его можно инициализировать данными из моментального снимка объекта Car:
public Tracker(Car car)
{
currentSpeed = car.Snapshot.Speed;
car.SpeedChanged += (o, speed) =>
{
…
}
}
Окончание следует…
Источник: levelup.gitconnected.com/design-…b7c3500a