Скрытие глобального состояния со свойствами - PullRequest
1 голос
/ 22 октября 2011

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

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, что казалось уродливым и неправильным, поэтому я решил использовать такое свойство:

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

    public ContactUpdater()
    {
    }
}

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

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

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

Ответы [ 3 ]

1 голос
/ 22 октября 2011

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

class Group : IGroup { .... }

public class ContactUpdater
{
    private IGroup _group;

    public ContactUpdater(IGroup group)
    {
        _group = group;
    }
}
1 голос
/ 22 октября 2011

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

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

    private readonly IGroup _group;
}

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

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

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

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

1 голос
/ 22 октября 2011

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

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

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

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

...