Подтвердить что ты не робот

Рефакторинг Singleton Overuse

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

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

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

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

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

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

  • Множество моих классов - это такие вещи, как SerialPortWriter или DataFileWriter, которые должны сидеть рядом, ожидая, когда эти данные будут транслироваться. Поскольку они активны все время, как я должен их упорядочить, чтобы слушать события, генерируемые при вводе данных?

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

4b9b3361

Ответ 1

Хорошо, вот мой лучший выстрел в атакующий этот вопрос:

(1) Статика

Проблема с static, которую вы можете иметь, заключается в том, что в .NET это означает разные вещи и говорят, С++. Статический в основном означает, что он доступен для самого класса. Что касается приемлемости, то это говорит о том, что вы больше всего используете для выполнения отдельных операций над классом. Или просто общие вещи вроде Math.Abs(...). То, что вы должны использовать для глобальной конфигурации, вероятно, является статически доступным свойством для хранения текущей/активной конфигурации. Кроме того, возможно, некоторые статические классы для загрузки/сохранения задают конфигурацию, однако config должен быть объектом, поэтому его можно передавать с помощью манипулирования и т.д.   открытый класс 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) Прослушивание данных

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

Ответ 2

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

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

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

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

так что, возможно, посмотрите, как вы можете настраивать объекты non singleton для совместной работы, а затем повесить их из реестра.

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

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

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

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

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

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

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

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

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

Ответ 3

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

Ответ 4

  • Вы (OP), похоже, озабочены OO-дизайном, ну, я скажу так, думая о вещах статических переменных. Основная концепция - инкапсуляция и повторное использование; то вы могли бы заботиться о повторном использовании, но вы почти всегда хотите инкапсуляцию. Если это статическая переменная, она действительно не инкапсулирована, не так ли? Подумайте о том, кому нужно получить к нему доступ, почему и как далеко вы можете Скрыть его от клиентского кода. Хорошие проекты часто могут менять внутренности без значительного поломки для клиентов, вот о чем вы хотите думать. Я согласен с Скоттом Майерсом (Effective С++) о многом. ООП выходит за рамки ключевого слова класса. Если вы никогда не слышали об этом, найдите свойства: да, они могут быть статичными, а С# имеет очень хороший способ их использования. В противоположность буквальному использованию статической переменной. Как я намекнул на начало этого элемента списка: подумайте о том, как не стрелять себе в ногу позже, так как класс меняется со временем, что что-то не так много программистов при проектировании классов.

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

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

  • Я предлагаю темы и события. Простота создания управляемых кодами событий на самом деле является одной из лучших вещей о С# IMHO.

Хмм, убивая одиночки...

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

Достаточно написанный класс может использовать статические данные для эффективного внедрения синглтона и заставить компилятор выполнять работу с ногами, сохраняя его, или как один, когда вы когда-либо будете получать что-либо. Do NOT заменяют синглтоны с наследованием, если вы не серьезно знаете, что вы делаете. Плохо сделанное наследование таких вещей приводит к более хрупкому коде, который хорошо знает waaaay. Классы должны быть немыми, данные умны. Это звучит глупо, если вы не посмотрите на это выражение глубоко. Использование наследования ИМХО для такой вещи, как правило, плохо (tm), языки имеют понятие модулей/пакетов по какой-либо причине.

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

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

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

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

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

Ответ 5

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

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

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

Ответ 6

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

(1) Группировать всю "глобальную" информацию в один класс. Позвольте называть его Configuration.

(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 в производный класс, он будет менее изящным.

Доводы

  • Не так много усилий, чтобы просто изменить наследование и конструкторы, и bang - вы можете переключить все Configuration.Instance на config.
  • Вы полностью избавляетесь от статических переменных; поэтому никаких проблем сейчас, если, например, ваше приложение внезапно превращается в библиотеку, и кто-то пытается одновременно вызвать несколько методов или что-то еще.

Ответ 7

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

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

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

Удачи.