重构Singleton过度使用

今天,我顿悟了一下,那是我一切都做错了。 一些历史:我inheritance了一个C#应用程序,这实际上只是一个静态方法的集合,是一个完全程序化的C#代码。 我重构了当时我所知道的最好的,带来了大量的大学后OOP知识。 长话短说,代码中的许多实体变成了单身人士。

今天我意识到我需要3个新的类,每个类都遵循相同的单例模式来匹配其余的软件。 如果我一直摔倒这个滑坡,最终我的应用程序中的每一个类都将是单例,这与单独的静态方法在逻辑上没有任何区别。

我需要帮助重新考虑这个问题。 我知道dependency injection,这通常是用来打破单身诅咒的策略。 不过,我有一些与重构有关的具体问题,以及所有关于这样做的最佳实践。

  1. 如何使用静态variables来封assembly置信息? 我有一个使用静态的大脑,我认为这是由于在大学早期的OOclass,教授说静态是坏的。 但是,每次我访问它时,是否需要重新configuration这个类? 在访问硬件时,可以将静态指针留给所需的地址和variables,还是应该继续执行Open()Close()操作?

  2. 现在我有一个方法作为控制器。 具体来说,我不断轮询几个外部仪器(通过硬件驱动程序)的数据。 如果这种types的控制器应该走的路,或者我应该在程序启动时为每个仪器产生单独的线程? 如果是后者,我如何使这个对象为导向? 我应该创build名为InstrumentAListenerInstrumentBListener类吗? 还是有一些标准的方法来处理这个?

  3. 有没有更好的方法来做全球configuration? 现在我只需将Configuration.Instance.Foo散布在整个代码中。 几乎每个class级都使用它,所以也许保持它作为一个单身人士是有道理的。 有什么想法吗?

  4. 很多我的类都是像SerialPortWriterDataFileWriter这样的东西,它们必须等待这些数据stream入。由于它们一直处于活动状态,我应该如何安排这些以便监听数据传入时产生的事件?

任何其他资源,书籍或有关如何摆脱单身人士和其他模式过度的评论将是有帮助的。

好的,这是我攻击这个问题的最佳拍档:

(1)静力学

你可能会遇到的static问题是它在.NET中意味着不同的事情,并且说C ++。 静态基本上意味着它可以在课堂上使用。 至于它的可接受性编号说,这是更多的东西你会用来做一个类的非实例特定的操作。 或者只是像Math.Abs(...)一般的东西。 你应该使用的全局configuration可能是一个静态访问的属性来保存当前/活动configuration。 也可能是一些静态类加载/保存设置configuration,但configuration应该是一个对象,所以它可以传递操纵等。

  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来调度你的轮询任务,因为如果你有很多的硬件可以获得大量的线程。 请确保在使用任何types的线程之前了解了很多。 犯错误是很容易的,你甚至可能没有意识到。 这本书是一个很好的来源,并会教你很多。

无论哪种方式,你应该build立服务(只是一个真正的名字)来pipe理你的硬件,负责收集有关服务的信息(本质上是一种模式模式)。 从那里你的中央控制器可以使用它们来访问控制器中保存程序逻辑的数据,以及服务中的硬件逻辑。

(3)全局configuration

在第一点我可能已经触及了这个问题,但通常这就是我们去的地方,如果你发现自己打字太多,你总是可以把它拉出来,假设.Instance是一个对象。

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

(4)听数据

响应式框架也可以帮助你,或者你可以build立一个事件驱动的模型 ,使用触发器来传入数据。 这将确保您在数据传入之前不会阻塞某个线程。它可以大大降低应用程序的复杂性。

对于初学者来说,可以通过“Registry”模式来限制单身人士的使用,这实际上意味着你有一个单身人士,可以让你得到一堆其他预先configuration好的物体。

这不是一个“修复”,而是一个改进,它使得单身的许多对象更加正常和可testing。 例如…(完全是人为的例子)

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

但真正的问题似乎是你正在努力使一组对象很好地结合在一起。 在OO中有两种步骤:configuration对象,让对象做它们的事情。

所以也许看看你可以如何configuration非单身对象一起工作,然后挂掉registry。

静态的 :-

在这里有许多规则的例外,但总的来说,避免它,但它是做单身,并创build方法,在对象的上下文外进行“一般”计算是有用的。 (如Math.Min)

数据监测: –

它通常更好地做你提示,创build一个线程与一大堆预configuration的对象,将做你的监测。 使用消息传递在线程之间进行通信(通过线程安全队列)来限制线程locking问题。 使用registry模式来访问硬件资源。

你想要一个像InstrumentListner的东西,它使用InstrumentProtocol(你为每个协议子类),我不知道,LogData。 命令模式可能在这里使用。

组态:-

获取您的configuration信息并使用类似“构build器”模式的内容来将您的configuration转换为以特定方式设置的一组对象。 即不要让你的类意识到configuration,使一个对象以特定的方式configuration对象。

串口: –

我做了一堆这些工作,我有一个串行连接,它产生一个字符stream,它作为一个事件发布。 然后我有东西将协议stream解释为有意义的命令。 我的协议类使用SerialConnectioninheritance的通用“IConnection”…..我也有TcpConnections,MockConnections等,能够注入testing数据,或从一台计算机pipe道串行端口等。协议类只是解释一个stream,有一个状态机和调度命令。 该协议预先configuration了一个连接,各种各样的东西被注册与协议,所以当它有有意义的数据,他们将被触发,并做他们的事情。 所有这一切都是从一开始的configuration构build的,或者如果发生了一些变化,则可以立即重build。

既然你知道dependency injection,你有没有考虑过使用一个IoC容器来pipe理生命周期? 看到我对静态类的问题的答案 。

  1. 你(OP)似乎专注于面向对象的devise,当我想到静态variables的时候,我会这么说。 核心概念是封装和重用; 有些东西你可能不太在乎重用,但你几乎总是想要封装。 如果它是一个静态variables,它不是真正的封装,是吗? 考虑谁需要访问它,为什么,以及您可以从客户端代码隐藏多远。 良好的devise通常可以改变他们的内部,而不会给客户带来太多的破坏,这是你想要考虑的 。 我赞同Scott Meyers(Effective C ++)关于许多事情。 OOP超越了class关键字。 如果你从来没有听说过它,查找属性:是的,他们可以是静态的,C#有一个很好的使用方法。 与字面上使用静态variables相反。 就像我在这个列表项目的开头部分所暗示的那样: 想想随着时间的推移, 如何不在自己的脚下开始自己的脚步 ,这是许多程序员在devise课程时不能做的事情。

  2. 看看有人提到的Rx框架。 如果没有关于用例的更多细节,恕我直言,使用线程模型来处理这种情况并不容易。 确保你知道你在用线程做什么。 很多人都无法想出挽救生命的线索, 没有那么难,当(重新)使用代码的时候可以放心。 记住,控制器应该经常与它们所控制的对象分离(例如不是同一个类)。 如果你不知道,请在MVC上找一本书,去买四个帮派。

  3. 取决于你需要什么。 对于许多应用程序来说,几乎完全充满静态数据的类就足够了; 像免费的单身人士。 它可以做得很OO。 有时你宁愿有多个实例或玩注射,这使得它更复杂。

  4. 我build议线程和事件。 实现代码事件驱动的简单实际上是关于C#恕我直言的更好的事情之一。

呃杀掉单身人士

根据我的经验,年轻的程序员把单身人士比较常用的东西,只不过是浪费了类关键字。 也就是说,它们是作为一个有状态的模块被卷入一个高地的阶级的东西; 并有一些不好的单身实现在那里匹配。 不pipe这是因为他们没有学到他们在做什么,或者只有大学的Java,我不知道。 回到C地域,它被称为在文件范围使用数据并暴露一个API。 在C#(和Java)中,你是一种比许多语言更受欢迎的类。 OOP!= class关键字; 很好地学习这个lhs。

一个写得很好的类可以使用静态数据来有效地实现一个单例,并且让编译器完成一个单独的工作,或者像你将要获得的一样。 除非你真的知道你在做什么,否则不要用inheritance来代替单身人士。 这样的事情inheritance不好,导致更脆弱的代码,知道waaaay多。 类应该是愚蠢的,数据是聪明的。 这听起来很蠢,除非你深深地看待这个陈述。 对于这样的事情,使用inheritance恕我直言,通常是一件坏事(tm),语言有一个理由模块/包的概念。

如果你是为了它,嘿,你确实把它转换成单一的年龄吧? 坐下来思考一下:我怎样才能最好地构build这个应用程序,以使其工作XXX方式,然后想想如何做XXX的方式影响的东西,例如是这样做的一种方式将成为线程之间的争用来源? 你可以在一个小时内完成很多事情。 当你长大了,你会学到更好的技术。

下面是以XXX方式开始的一个build议:(可视化)写入(^ Hing)复合控制器类,该复合控制器类作为其引用的对象的pipe理器。 这些对象是你的单例,而不是控制器拥有它们,而只是那些类的实例。 这不是许多应用程序的最佳devise(尤其是可以成为一个严重的线程恕我直言,恕我直言)的问题,但它通常会解决什么原因导致大多数幼雏达到一个单身人士,它将适合于大量的程式。 呃,就像devise模式CS 102一样。忘记你在CS 607中学到的单体。

这个控制类,也许“应用程序”会更加适合),基本上解决了你对单例和存储configuration的需求,如何以崇高的OO方式来做到这一点(假设你理解OOP),而不是在脚下(再次),是你自己的教育锻炼。

如果它表明,我不是所谓的单身模式的粉丝,特别是如何经常被滥用。 移动一个代码库,通常取决于你准备使用多less重构。 单身就像全局variables:方便而不是黄油。 嗯,我想我会把它放在我的报价文件中,有一个很好的词组…

老实说,你知道更多关于代码库和应用程序的问题,然后任何人在这里。 所以没有人可以真正为你devise,build议less说话,至less我是从哪里来的。

我把自己限制在一个应用程序/进程中最多两个单身人士。 其中一个通常被称为SysConfig,其中包含的内容可能最终会成为全局variables或其他腐败概念。 我没有第二个名字,因为到目前为止,我还没有达到我的极限。 🙂

静态成员variables有其用途,但我查看他们,因为我查看proctologists。 当你需要一个拯救者,但是赔率应该是“百万比一”(Seinfeld参考)时,你找不到解决问题的更好方法。

创build一个实现一个线程化侦听器的基本工具类。 派生类将具有特定于仪器的驱动程序等。为每个仪器实例化派生类,然后将该对象存储在某种容器中。 在清理时只需遍历容器。 每个仪器实例应该通过传递它的输出/状态/任何地方的注册信息来构造。 在这里使用你的想象力。 面向对象的东西变得相当强大。

我最近不得不解决一个类似的问题,而我所做的工作似乎对我很好,也许它会帮助你:

(1)将所有“全球”信息归为一类。 我们称之为Configuration

(2)对于所有使用这些静态对象的类,将它们改为(最终)从一个新的抽象基类inheritance,

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

(3)相应地更改派生自MyBaseClass所有类的构造函数。 然后在开始时创build一个Configuration实例,并在任何地方传递它。

缺点:

  • 你需要重构你的许多构造函数和它们被调用的每个地方
  • 如果您不从Object派生您的顶级类,这将无法正常工作。 那么,你可以将config字段添加到派生类,它只是不那么优雅。

优点

  • 没有很多的努力,只是改变inheritance和构造,而且 – 你可以切换所有Configuration.Instanceconfig
  • 你完全摆脱了静态variables; 所以现在没有问题,例如,如果你的应用程序突然变成了一个库,有人试图同时调用多个方法,或者别的什么。

伟大的问题。 从我几个简单的想法…

C#中的static 只能用于给定类的所有 实例 完全相同的数据。 既然你现在被困在辛格尔顿的地狱中,那么你只有一件事情,但是一旦你发现这是一般的规则(至less对我来说是这样)。 如果你开始对类进行线程化,那么你可能需要放弃静态的使用,因为那样你就有潜在的并发问题,但是这个问题可以在以后解决。

我不确定你的硬件是如何工作的,但是假设它们有一些基本的function是相同的(比如,你如何在原始数据级或类似的环境下与它们进行交互),那么这是创build类层次结构。 基类实现了低级别/类似的东西与虚拟方法后代类重写,以实际正确地解释数据/饲料的前进/无论什么。

祝你好运。