« Все записи

Маленькие чудеса C#/.NET: перекрестно-вызываемые конструкторы

Опять эти «Маленькие чудеса...». Уже чувствую себя заложником этой серии, но продолжаю переводить. Оригинал статьи.

Еще один маленький «лакомый кусочек», который я люблю использовать, он вроде бы хорошо известен, но я замечал много раз, что менее опытные разработчики, как правило, оставляют избыточный код, когда они перегружают свои конструкторы.

Проблема — повторяющийся код менее сопровождаемый.

Допустим, вы разрабатываете систему обмена сообщениями, и хотите создать класс для представления свойств приемника (Receiver) — вы проектируете класс ReceiverProperties для представления этой коллекции свойств.

Допустим, вы решили сделать ReceiverProperties неизменяемым (immutable), для этого у вас есть несколько конструкторов, которые можно использовать для альтернативного построения:

// Создает набор свойств приемника.
public ReceiverProperties(ReceiverType receiverType, string source, bool isDurable, bool isBuffered)
{
    ReceiverType = receiverType;
    Source = source;
    IsDurable = isDurable;
    IsBuffered = isBuffered;
}

// Создает набор свойств приемника с включенной  по умолчанию буферизацией.
public ReceiverProperties(ReceiverType receiverType, string source, bool isDurable)
{
    ReceiverType = receiverType;
    Source = source;
    IsDurable = isDurable;
    IsBuffered = true;        
}

// Создает набор свойств приемника с включенной по умолчанию буферизацией и отключенным признаком долговременности.
public ReceiverProperties(ReceiverType receiverType, string source)
{
    ReceiverType = receiverType;
    Source = source;
    IsDurable = false;
    IsBuffered = true;        
}

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

Хотя, строго говоря, в этом коде нет ничего плохого, она страдает от недостаточной сопровождаемости. Что произойдет, если вы добавите новое свойство к классу? Вы должны помнить, что оно должно быть инициализировано надлежащим образом в каждом вызове конструктора.

Это может привести к серьезным ошибкам и становится потенциально более опасным, когда конструкторы содержат более сложную логику, обработку ошибок, или имеется множество возможных перегрузок (особенно, если они не помещаются на одном экране).

Решение — перекрестно-вызываемые конструкторы

Держу пари, почти все знают, как вызвать конструктор базового класса, но ведь вы можете «перекрестно» вызвать любой из конструкторов в том же самом классе с помощью ключевого слова this, так же, как вы используете base для вызова базового конструктора.

// Создает набор свойств приемника.
public ReceiverProperties(ReceiverType receiverType, string source, bool isDurable, bool isBuffered)
{
    ReceiverType = receiverType;
    Source = source;
    IsDurable = isDurable;
    IsBuffered = isBuffered;
}

// Создает набор свойств приемника с включенной  по умолчанию буферизацией.
public ReceiverProperties(ReceiverType receiverType, string source, bool isDurable)
    : this(receiverType, source, isDurable, true)
{
}

// Создает набор свойств приемника с включенной по умолчанию буферизацией и отключенным признаком долговременности.
public ReceiverProperties(ReceiverType receiverType, string source)
    : this(receiverType, source, false, true)
{
}

Обратите внимание, намного меньше кода. Кроме того, ваш код теперь не содержит повторяющейся логики. Вы можете определить основной конструктор, который принимает все аргументы, а остальные конструкторы с параметрами по умолчанию, просто «перекрестно» вызывают главный конструктор, передавая текущие значения.

Да, в некоторых случаях параметры по умолчанию могут чем-то помочь, но параметры по умолчанию работают только для констант времени компиляции (null, строковых и числовых литералов). Например, если вы создаете TradingDataAdapter, который опирается на реализацию ITradingDao, который является объектом доступа к данным (DAO) и извлекает записи из базы данных, вам может понадобиться два конструктора: один, который принимает ссылку на ITradingDao, и конструктор по умолчанию, который создает определенный ITradingDao для удобства использования:

public TradingDataAdapter(ITradingDao dao)
{
    _tradingDao = dao;

    // остальная логика конструктора
}

public TradingDataAdapter()
{
    _tradingDao = new SqlTradingDao();

    // та же логика конструктора, как выше
}

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

public TradingDataAdapter(ITradingDao dao)
{
    _tradingDao = dao;

    // остальная логика конструктора
}

public TradingDataAdapter()
    : this(new SqlTradingDao())
{
}

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

Резюме

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

Это делает код более легким в обслуживании и даже более легким для чтения. Есть несколько случаев, когда перекрестно-вызывемые конструкторы могут быть неоптимальными, или их невозможно использовать (если, например, перегруженные конструкторы принимают совершенно разные типы и не просто реализуют «текущее» поведение).

Вы, конечно, можете использовать параметры по умолчанию, но поведение параметров по умолчанию в иерархии классов может быть проблематичным (значения по умолчанию не наследуются и фактически могут отличаться), так что иногда использование нескольких конструкторов предпочтительнее.

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

Progg it

comments powered by Disqus