Скрытие глобального состояния с помощью свойств

#c# #coding-style #state

#c# #стиль кодирования #состояние

Вопрос:

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

 public class ContactUpdater
{
    //this is used in a few method and requires a web request so it's fairly
    //expensive to populate - therefore we only want to do it once.
    private Group _group;

    public ContactUpdater
    {
        _group = GetGroup();
    }
}
  

Такой подход означал, что мой код был завален _group , что казалось уродливым и неправильным, поэтому я решил использовать свойство, подобное so:

 public class ContactUpdater
{
    private Group _group;
    private Group Group
    {
        get
        {
            if (_group == null)
            {
                _group = GetGroup();
            }
            return _group;
        }
    }

    public ContactUpdater()
    {
    }
}
  

Теперь у меня есть пустой конструктор, и везде, где мне нужно использовать группу, я теперь просто вызываю Group , а не _group .

У меня есть пара вопросов:

  1. Использую ли я глобальное состояние в обоих этих фрагментах кода?
  2. Избегает ли второй пример глобального состояния?
  3. Является ли один из примеров кода предпочтительным способом кодирования?

Комментарии:

1. что делает GetGroup() ?

2. Он использует API контактов Google для поиска группы контактов с определенным именем.

Ответ №1:

Как указал @Conrad Frix, это зависит от того, что делает GetGroup() . Обычно я бы отделил группу от программы обновления с помощью интерфейса и использовал внедрение конструктора:

 public class ContactUpdater
{
    public ContactUpdater(IGroup group)
    {
        _group = group;
    }

    private readonly IGroup _group;
}
  

Таким образом, ContactUpdater можно протестировать независимо от Group и GetGroup(), передав поддельную или фиктивную реализацию интерфейса IGroup.

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

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

Обновление: поскольку GetGroup() вызывает внешнюю службу, я бы абсолютно отделил это.

Комментарии:

1. Теперь это имеет смысл. Пометка в качестве ответа в основном потому, что вы также обратили внимание на соглашение о подчеркивании.

Ответ №2:

Поскольку неясно, что делает GetGroup(), вы можете использовать или не использовать глобальное состояние. И поле, и свойство хорошо инкапсулированы, так что это не так.

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

 get
{
    if (_group == null)
    {
        _group = GetGroup();
 }
 return _group;
  

Если вы используете это для отсрочки загрузки группы с помощью свойства отложенной загрузки, вы должны, как отмечает TrueWill, использовать Lazy<T>

Комментарии:

1. Однако это шаблон отложенной загрузки. Я бы, вероятно, вместо этого использовал Lazy<T> .

Ответ №3:

Оба способа в порядке. Итак, вы не используете глобальные состояния в этих кодах. Однако, возможно, что ни один из них не является лучшим подходом. Лучшим вариантом может быть внедрение этой зависимости в конструктор класса, чтобы отделить этот групповой класс. Если вы это сделаете, ваш класс ContactUpdater будет изолирован от класса Group, и вы сможете выполнить модульное тестирование кода. Более того, вы сможете использовать один и тот же экземпляр Group во всех экземплярах ContactUpdater.

 class Group : IGroup { .... }

public class ContactUpdater
{
    private IGroup _group;

    public ContactUpdater(IGroup group)
    {
        _group = group;
    }
}
  

Комментарии:

1. 1 — ты опередил меня примерно на минуту! Я предлагаю сделать частное поле доступным только для чтения.