Рефакторинг злоупотребления Singleton

Сегодня у меня было прозрение, и случалось так, что я делал все неправильно. Некоторая история: Я наследовал приложение C#, которое было действительно просто набором статических методов, абсолютно процедурной путаницей кода C#. Я осуществил рефакторинг это лучшее, которое я знал в то время, вводя много постколледжа знание ООП. Короче говоря, многие объекты в коде оказались Одиночными элементами.

Сегодня я понял, что мне были нужны 3 новых класса, которые будут каждый следовать тому же Шаблону "одиночка" для соответствия остальной части программного обеспечения. Если я буду продолжать падать этот скользкий путь, то в конечном счете каждый класс в моем приложении будет Singleton, которая будет действительно не логически отличаться от исходной группы статических методов.

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

  1. Как приемлемый использование статических переменных должно инкапсулировать конфигурационную информацию? У меня есть мозговой блок при использовании статичного, и я думаю, что это происходит из-за раннего класса OO в колледже, где преподаватель упомянутые помехи был плох. Но, мне придется реконфигурировать класс каждый раз, когда я получаю доступ к нему? При доступе к аппаратным средствам, это хорошо для отъезда статического указателя на адреса и переменные необходимым, или если я постоянно выполняю Open() и Close() операции?

  2. Прямо сейчас у меня есть отдельный метод, действующий как контроллер. А именно, я постоянно опрашиваю несколько внешних инструментов (через драйверы оборудования) для данных. Этот тип контроллера должен быть способом пойти, или я должен породить отдельные потоки для каждого инструмента при запуске программы? Если последний, как я делаю это объектно-ориентированным? Если я создаю названные классы InstrumentAListener и InstrumentBListener? Или есть ли некоторый стандартный способ приблизиться к этому?

  3. Существует ли лучший способ реализовать глобальную конфигурацию? Прямо сейчас я просто имею Configuration.Instance.Foo опрыснутый подробно всюду по коду. Почти каждый класс использует его, поэтому возможно, сохраняя его, поскольку Singleton имеет смысл. Какие-либо мысли?

  4. Много моих классов является вещами как SerialPortWriter или DataFileWriter, который должен сидеть без дела, ожидая этих данных к потоку в. Так как они активны все время, как я должен расположить их для прислушиваний к событиям, сгенерированным, когда данные входят?

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

45
задан drharris 27 May 2010 в 22:24
поделиться

7 ответов

Хорошо, вот моя лучшая попытка атаковать этот вопрос:

(1) Статика

Проблема со статикой, с которой вы можете столкнуться, заключается в том, что она означает разные вещи в .NET и, скажем, в C++. Статический в основном означает, что он доступен в самом классе. Что касается его приемлемости, я бы сказал, что это скорее то, что вы используете для выполнения операций над классом, не связанных с конкретным экземпляром. Или просто общие вещи вроде Math.Abs(...). То, что вам следует использовать для глобальной конфигурации, вероятно, статически доступное свойство для хранения текущей/активной конфигурации. Также возможно несколько статических классов для загрузки/сохранения конфигурации, однако конфигурация должна быть объектом, чтобы ее можно было передавать, манипулировать и т.д. public class MyConfiguration { public const string DefaultConfigPath = "./config.xml";

  protected static MyConfiguration _current;
  public static MyConfiguration Current
  {
    get
    {
      if (_current == null)
        Load(DefaultConfigPath);
      return _current;
    }
  }

  public static MyConfiguration Load(string path)
  {
    // Do your loading here
    _current = loadedConfig;
    return loadedConfig; 
  }

  // Static save function

  //*********** Non-Static Members *********//

  public string MyVariable { get; set; }
  // etc..
}

(2) Контроллер/аппаратное обеспечение

Возможно, вам стоит рассмотреть реактивный подход, IObserver<> или IObservable<>, это часть Reactive Framework (Rx).

Другой подход заключается в использовании ThreadPool для планирования задач опроса, так как вы можете получить большое количество потоков, если у вас много аппаратных средств для объединения. Пожалуйста, перед использованием любого вида Threading обязательно узнайте о нем побольше. Очень легко совершить ошибки, о которых вы можете даже не подозревать. Эта книга является прекрасным источником и научит вас многому.

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

(3) Глобальная конфигурация

Возможно, я затронул эту тему в пункте #1, но в целом это то, куда мы идем, если вы обнаружите, что печатаете слишком много, вы всегда можете вытащить это оттуда, предполагая, что .Instance является объектом.

MyConfiguration cfg = MyConfiguration.Current
cfg.Foo // etc...

(4) Прослушивание данных

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

13
ответ дан 26 November 2019 в 21:31
поделиться

Я ограничиваюсь максимум двумя синглетонами в приложении / процессе. Один из них обычно называется SysConfig и содержит вещи, которые в противном случае могли бы оказаться глобальными переменными или другими поврежденными концепциями. У меня нет названия для второго, так как до сих пор я так и не достиг своего предела. : -)

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

Создайте базовый класс инструмента, реализующий многопоточный слушатель. Производные классы, которые будут иметь драйверы для конкретных инструментов и т. Д. Создайте экземпляр производного класса для каждого инструмента, а затем сохраните объект в каком-либо контейнере. Во время очистки просто перебирайте контейнер. Каждый экземпляр инструмента должен быть сконструирован путем передачи ему некоторой регистрационной информации о том, куда отправлять его выходные данные / статус / что угодно. Используйте здесь свое воображение. ОО-материал становится довольно мощным.

0
ответ дан 26 November 2019 в 21:31
поделиться

Отличный вопрос. Несколько быстрых мыслей от меня ...

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

Я не уверен, как на самом деле работает ваше оборудование, но если предположить, что есть некоторые базовые функции, одинаковые для всех них (например, как вы взаимодействуете с ними на уровне необработанных данных или что-то подобное), то это идеальный пример. для создания иерархии классов. Базовый класс реализует низкоуровневые / аналогичные вещи с виртуальными методами для дочерних классов, чтобы переопределить, чтобы фактически правильно интерпретировать данные / передавать их дальше / что-то еще.

Удачи.

0
ответ дан 26 November 2019 в 21:31
поделиться

Поскольку вы знаете о внедрении зависимостей, рассматривали ли вы возможность использования контейнера IoC для управления временем жизни? См. мой ответ на вопрос о статических классах.

1
ответ дан 26 November 2019 в 21:31
поделиться

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

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

HardwareRegistry.SerialPorts.Serial1.Send("blah");

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

так что, возможно, посмотрите, как можно настроить совместную работу не-одиночных объектов, а затем повесить это на реестр.

Статический: -

Здесь множество исключений из правил, но в целом, избегайте этого, но это полезно для выполнения одиночных вычислений и создания методов, которые выполняют «общие» вычисления вне контекста объекта. (например, Math.Min)

Мониторинг данных: -

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

вам нужно что-то вроде InstrumentListner, который использует InstrumentProtocol (который вы подклассифицируете для каждого протокола), я не знаю, LogData. Здесь может быть полезен шаблон команды.

Конфигурация: -

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

Последовательные порты: -

Я много работаю с ними, у меня есть последовательное соединение, которое генерирует поток символов, который публикуется как событие. Затем у меня есть что-то, что интерпретирует поток протокола в значимые команды. Мои классы протокола работают с общим "IConnection", от которого наследуется SerialConnection ..... У меня также есть TcpConnections, MockConnections и т. Д., Чтобы иметь возможность вводить тестовые данные или передавать последовательные порты с одного компьютера на другой и т. Д. Классы протоколов просто интерпретируют поток, имеют машину состояний и отправляют команды. Протокол предварительно сконфигурирован с подключением. В протоколе регистрируются различные вещи, поэтому, когда в нем есть значимые данные, они будут запускаться и делать свое дело. Все это создается из конфигурации в начале или перестраивается на лету, если что-то меняется.

4
ответ дан 26 November 2019 в 21:31
поделиться
  1. Вы (OP), кажется, озабочены OO дизайн, ну, я скажу так, когда буду думать о статических переменных. Основная концепция - инкапсуляция и повторное использование; кое-что, о повторном использовании можно меньше заботиться, но почти всегда нужна инкапсуляция. Если это статическая переменная, она на самом деле не инкапсулирована, не так ли? Подумайте, кому нужен доступ к нему, почему и как далеко вы можете СКРЫТЬ его от клиентского кода. Хороший дизайн часто может изменить свое внутреннее устройство без особого ущерба для клиентов, это то, о чем вы хотите думать . Я согласен со Скоттом Мейерсом («Эффективный C ++») во многих вещах. ООП выходит далеко за рамки ключевого слова class. Если вы никогда об этом не слышали, поищите свойства: да, они могут быть статическими, и в C # есть очень хороший способ их использования. В отличие от буквально с использованием статической переменной . Как я намекнул в начале этого пункта списка: подумайте о том, как не выстрелить себе в ногу позже , поскольку класс меняется со временем, это то, что многие программисты не делают при разработке классов.

  2. Взгляните на ту структуру Rx, о которой кто-то упомянул.Модель потоков, которую следует использовать для такой ситуации, как вы описали, нелегко решить без дополнительных подробностей о варианте использования ИМХО. Убедитесь, что вы знаете, что делаете с потоками. Многие люди не могут разгадать нити, чтобы спасти свою жизнь; это не так уж и сложно, быть безопасным можно при (повторном) использовании кода. Помните, что контроллеры часто должны быть отделены от объектов, которыми они управляют (например, не одного и того же класса); если вы этого не знаете, найдите книгу по MVC и купите группу из четырех человек.

  3. Зависит от того, что вам нужно. Для многих приложений достаточно класса, который почти полностью заполнен статическими данными; как синглтон бесплатно. Это можно сделать очень ОО. Иногда вы бы предпочли иметь несколько экземпляров или поиграться с инъекциями, что усложняет задачу.

  4. Я предлагаю темы и события. Простота создания кода, управляемого событиями, на самом деле одна из приятных вещей в C # ИМХО.

Хм, убиваем синглтоны ...

По моему опыту, многие из наиболее распространенных использования, которые молодые программисты используют синглтоны, являются не более чем пустой тратой ключевого слова class. А именно то, что они имели в виду как модуль с отслеживанием состояния, превращаемый в класс highlander ; и есть несколько плохих одноэлементных реализаций, которым можно соответствовать. Я не знаю, потому ли это, что они не научились тому, что они делают, или потому что у них была только Java в колледже. Вернувшись в страну C, это называется использованием данных в области видимости файла и предоставлением API. В C # (и Java) вы как бы привязаны к тому, чтобы быть классом, больше, чем со многими языками. ООП! = Ключевое слово класса; выучите lhs хорошо.

Прилично написанный класс может использовать статические данные для эффективной реализации синглтона и заставлять компилятор делать всю работу по сохранению его единым целым или единым целым, которое вы когда-либо собираетесь получить. Не НЕ заменяйте синглтоны на наследование, если вы серьезно не знаете, что, черт возьми, делаете. Плохо выполненное наследование таких вещей приводит к более хрупкому коду, который слишком много знает. Классы должны быть тупыми, данные - умными. Это звучит глупо, если вы не посмотрите внимательно на утверждение . Использование наследования IMHO для таких вещей, как правило, плохо (тм), у языков есть понятие модулей / пакетов по какой-то причине.

Если вы готовы, то ведь вы ведь конвертировали его в синглтоны давным-давно, верно? Сядьте и подумайте: как мне лучше всего структурировать это приложение, чтобы оно работало в ХХХ темпе, а затем подумайте, как это ХХХ способом влияет на вещи, например, если сделать это одним способом, будет источник раздора между потоками? За такой час можно многое прочесть. Когда вы станете старше, вы научитесь лучшим техникам.

Вот одно предложение для ХХХ способа начать с: (визуализировать) написать (^ Hing) составной класс контроллера, который работает как менеджер над объектами, на которые он ссылается. Эти объекты были вашими одиночными объектами, а не контроллером, который их хранит, и они всего лишь экземпляры этих классов.Это не лучший дизайн для многих приложений (особенно это может быть проблемой для многопоточных приложений IMHO), но он, как правило, решает то, что заставляет большинство молодых людей достигать синглтона, и он будет работать подходящим образом для огромного количества программы. Это похоже на шаблон проектирования CS 102. Забудьте про синглтон, который вы изучили в CS 607.

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

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

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

1
ответ дан 26 November 2019 в 21:31
поделиться

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

(1) Сгруппируйте всю "глобальную" информацию в один класс. Назовем его Конфигурация.

(2) Для всех классов, которые использовали эти статические объекты, измените их так, чтобы они (в конечном счете) наследовались от нового абстрактного базового класса, который выглядит примерно так

abstract class MyBaseClass {
    protected Configuration config; // You can also wrap it in a property
    public MyBaseClass(Configuration config) {
        this.config = config;
    }
}

(3) Соответственно измените все конструкторы классов, производных от MyBaseClass. Затем просто создайте один экземпляр Configuration в начале и передавайте его везде.

Минусы:

  • Вам придется рефакторить многие конструкторы и все места, в которых они вызываются
  • Это не будет хорошо работать, если вы не выводите свои классы верхнего уровня из Object. Ну, вы можете добавить поле config в производный класс, это просто менее элегантно.

Плюсы

  • Не так много усилий, чтобы просто изменить наследование и конструкторы, и бац - можно поменять все Configuration.Instance на config.
  • Вы полностью избавляетесь от статических переменных; так что теперь не будет проблем, если, например, ваше приложение вдруг превратится в библиотеку, и кто-то попытается вызвать несколько методов одновременно или что-то еще.
0
ответ дан 26 November 2019 в 21:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: