Дополнительные принципы

Принцип Tell-Don't-Ask

Последнее обновление: 24.04.2022

Принцип Tell-Don't-Ask позволяет объединить данные и связанное с ними поведение. Применение этого принципа позволяет вместо опроса данных и последующих действий напрямую выполнять данные действия.

Например, рассмотрим сначала пример с опросом данных объекта:

AlarmClock clock = new AlarmClock(6);
clock.CurrentHour = 6;

if (clock.CurrentHour == clock.GetUpHour)   // ask
    clock.Alarm();

class AlarmClock
{
    public int GetUpHour { get; set; }
    public int CurrentHour { get; set; }
    public AlarmClock(int hour) => GetUpHour = hour;

    public void Alarm() => Console.WriteLine("Рота! Подъем!");
}

Здесь определен класс условного будильника - класс AlarmClock. Свойство GetUpHour хранит час, когда будильник даст сигнал. Свойство CurrentHour хранит текущий час. А метод Alarm собственно выполняет сигнал будильника.

В основной части программы проверяем текущий час и, если он равен времени звонка будильника, то вызываем метод Alarm.

if (clock.CurrentHour == clock.GetUpHour)   // ask
    clock.Alarm(); 

Это стандартный опрос данных, за котором действуют некоторое связанные с ними действия. То что называется ask

Теперь уберем в соответствии с принципом Tell-Don't-Ask изменим программу:

AlarmClock clock = new AlarmClock(6);
clock.CurrentHour = 6;


class AlarmClock
{
    int currentHour;
    public int GetUpHour { get; set; }
    public int CurrentHour
    {
        get => currentHour;
        set
        {
            if (value == GetUpHour) Alarm(); // tell
            currentHour = value;
        }
    }
    public AlarmClock(int hour) => GetUpHour = hour;

    public void Alarm() => Console.WriteLine("Рота! Подъем!");
}

Теперь проверка времени перемещена непосредственно в сам объект AlarmClock. Соответственно вне объекта теперь нам не надо запрашивать его данные.

Обычно подобное решение является предпочтительным, так данные и действия над ними тесно связаны. А изменения одного аспекта (данных или поведения) приведет к изменению второго аспекта.

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

IMessage[] messages = new IMessage[]
{
    new VoiceMessage(Array.Empty<byte>(), "tom", "sam"), // условное голосовое сообщение
    new TextMessage("Hello", "sam", "tom") // текстовое сообщение
};

foreach(var message in messages)
{
    if (message is TextMessage textMessage) // ask
        textMessage.Print();
    else if (message is VoiceMessage voiceMessage)  // ask
        voiceMessage.Play();
}

interface IMessage
{
    string Sender { get;}
    string Receiver { get; }
}

class TextMessage : IMessage
{
    public string Sender { get;}
    public string Receiver { get;}
    public string Text { get;}
    public TextMessage(string text, string sender, string receiver)
    {
        Text = text;
        Receiver = receiver;
        Sender = sender;
    }
    public void Print() => Console.WriteLine($"Текстовое сообщение: {Text}");
}

class VoiceMessage : IMessage
{
    public string Sender { get;}
    public string Receiver { get;}
    public byte[] Voice { get; }
    public VoiceMessage(byte[] voice, string sender, string receiver)
    {
        Voice = voice;
        Receiver = receiver;
        Sender = sender;
    }
    public void Play() => Console.WriteLine($"Воспроизведение голосового сообщения");
}

Здесь определен интерфейс IMessage, который представляет условное сообщение. И есть две реализации данного интерфейса: класс текстового сообщения TextMessage и класс голосового сообщения VoiceMessage. Оба класса реализуют некоторый общий функционал. Однако для вывода текстового сообщения на консоль применяется метод Print, а для воспроизведения голосового сообщения применяется метод Play.

Если у нас есть набор сообщений, и мы хотим узнать, что за контент они содержат, нам необходимо выполнить опрос - проверить их типы и затем выполнить соответствующий метод:

foreach(var message in messages)
{
    if (message is TextMessage textMessage) // ask
        textMessage.Print();
    else if (message is VoiceMessage voiceMessage)  // ask
        voiceMessage.Play();
}

И вместо опроса мы можем напрямую выполнить определенное действия:

IMessage[] messages = new IMessage[]
{
    new VoiceMessage(Array.Empty<byte>(), "tom", "sam"), // условное голосовое сообщение
    new TextMessage("Hello", "sam", "tom") // текстовое сообщение
};

foreach(var message in messages)
{
    message.Launch();
}

interface IMessage
{
    string Sender { get;}
    string Receiver { get; }
    void Launch();
}

class TextMessage : IMessage
{
    public string Sender { get;}
    public string Receiver { get;}
    public string Text { get;}
    public TextMessage(string text, string sender, string receiver)
    {
        Text = text;
        Receiver = receiver;
        Sender = sender;
    }
    private void Print() => Console.WriteLine($"Текстовое сообщение: {Text}");
    public void Launch() => Print();     // tell
}

class VoiceMessage : IMessage
{
    public string Sender { get;}
    public string Receiver { get;}
    public byte[] Voice { get; }
    public VoiceMessage(byte[] voice, string sender, string receiver)
    {
        Voice = voice;
        Receiver = receiver;
        Sender = sender;
    }
    private void Play() => Console.WriteLine($"Воспроизведение голосового сообщения");
    public void Launch() => Play();  // tell
}

Теперь в интерфейсе IMessage определен метод Launch(), в котором каждый из классов-сообщений выполняет соответствующий метод.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850