代码之家  ›  专栏  ›  技术社区  ›  drharris

重构单例过度使用

  •  45
  • drharris  · 技术社区  · 14 年前

    今天我顿悟了,我做错了一切。一些历史:我继承了一个C应用程序,它实际上只是静态方法的集合,完全是C代码的过程混乱。我尽我当时所知重构了它,带来了很多大学毕业后的OOP知识。长话短说,代码中的许多实体都是单件的。

    今天我意识到我需要3个新的类,每个类都遵循相同的单例模式来匹配软件的其余部分。如果我继续沿着这条滑坡下滑,最终我的应用程序中的每个类都将是单例的,这在逻辑上与最初的静态方法组没有什么不同。

    我需要帮助重新思考这个问题。我知道依赖注入,这通常是用来打破单例诅咒的策略。但是,我有一些与重构相关的具体问题,以及所有与此相关的最佳实践。

    1. 如何接受使用静态变量来封装配置信息?我有一个关于使用静电的大脑障碍,我认为这是由于大学早期的OO课程,教授说静电很糟糕。但是,我是否应该在每次访问该类时重新配置它?在访问硬件时,是否可以保留一个指向所需地址和变量的静态指针,或者我应该继续执行 Open() Close() 操作?

    2. 现在我有一个方法作为控制器。具体来说,我会不断地(通过硬件驱动程序)对一些外部仪器进行数据轮询。这种类型的控制器应该是前进的道路,还是应该在程序启动时为每个仪器生成单独的线程?如果是后者,如何使这个面向对象?我应该创建名为 InstrumentAListener InstrumentBListener ?或者有什么标准的方法来处理这个问题?

    3. 有没有更好的全局配置方法?现在我只是 Configuration.Instance.Foo 在整个代码中大量散布。几乎每个类都使用它,所以将它作为一个单例保存可能是有意义的。有什么想法吗?

    4. 我的很多课程都是 SerialPortWriter DataFileWriter ,它必须在周围等待这些数据流入。由于它们一直处于活动状态,我应该如何安排它们来监听数据进入时生成的事件?

    任何其他的资源,书籍,或评论如何摆脱单件和其他模式的过度使用将是有益的。

    7 回复  |  直到 14 年前
        1
  •  13
  •   Aren    14 年前

    好吧,这是我攻击这个问题的最佳机会:

    (1)静力学

    问题在于 static 你可能拥有的是,在.NET中表示不同的东西,比如说C++。静态基本上意味着它可以在类本身上访问。至于它 可接受性 我说它更多的是用来对类执行非实例特定操作的东西。或者只是一般的事情 Math.Abs(...) . 对于全局配置,您应该使用的可能是用于保存当前/活动配置的静态访问属性。也可能是一些用于加载/保存设置配置的静态类,但是配置应该是 对象 所以它可以通过操纵等方式传递。 公共类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来安排轮询任务,因为如果有很多硬件要汇集,可能会获得大量线程。请在使用任何种类的 螺纹加工 了解很多。犯错误是很容易的,你甚至可能意识不到。 This Book 是一个优秀的来源,会教你很多。

    无论采用哪种方式,您都应该构建用于管理硬件的服务(实际上只是一个名称),这些硬件负责收集有关服务(本质上是一个模型模式)的信息。从那里,您的中央控制器可以使用它们访问保存在控制器中的程序逻辑和服务中的硬件逻辑的数据。

    (3)全局配置

    我可能在第1点中提到过这个主题,但一般来说,这就是我们要做的,如果你发现自己打字太多,你总是可以把它从那里拉出来,假设 .Instance 是一个对象。

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

    (4)监听数据

    同样,反应式框架可以帮助您,或者您可以构建一个 event-driven model 它使用触发器来输入数据。这将确保在数据进入之前不会阻塞线程。它可以大大降低应用程序的复杂性。

        2
  •  4
  •   Keith Nicholas    14 年前

    对于初学者,您可以通过“注册表”模式限制使用singleton,这实际上意味着您有一个singleton,它允许您访问其他一些预配置的对象。

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

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

    但真正的问题似乎是你正在努力制作一组能够很好地协同工作的对象。在OO中有两种步骤……配置对象,并让对象完成它们的工作。

    所以,也许可以看看如何配置非单例对象以一起工作,然后将其挂起在注册表上。

    静态:

    这里的规则有很多例外,但是一般来说,要避免它,但是它对于做单例和创建在对象上下文之外进行“一般”计算的方法很有用。(就像math.min)

    数据监控:

    最好按照您的提示来做,创建一个带有一组预配置对象的线程,这些对象将执行您的监控。使用消息传递在线程之间进行通信(通过线程安全队列),以限制线程锁定问题。使用注册表模式访问硬件资源。

    您需要像InstrumentListner这样的东西,它使用InstrumentProtocol(每个协议的子类)来识别日志数据。这里可以使用命令模式。

    配置:

    拥有您的配置信息,并使用类似于“构建器”的模式将您的配置转换为以特定方式设置的一组对象。也就是说,不要让你的类知道配置,做一个以特定方式配置对象的对象。

    串行端口:

    我用这些做了很多工作,我有一个串行连接,它生成一个字符流,作为一个事件发布。然后我有一些东西可以将协议流解释成有意义的命令。我的协议类使用一个常规的“IConnection”,其中一个serialConnection继承了它…..我还拥有tcpconnections、mockconnections等,能够注入测试数据,或者将串行端口从一台计算机传输到另一台计算机,等等。所以协议类只解释流、拥有状态机和调度命令。协议预先配置了一个连接,在协议中注册了各种各样的东西,所以当它拥有有意义的数据时,它们将被触发并做它们的事情。所有这些都是从一开始的配置中构建的,或者如果有什么变化,可以在运行中重建。

        3
  •  1
  •   Community CDub    7 年前

    既然您知道依赖注入,您是否考虑过使用IOC容器来管理生命周期?见 my answer 关于静态类的问题。

        4
  •  1
  •   TerryP    14 年前
    1. 你(OP)似乎全神贯注于OO设计,好吧,我在考虑静态变量的时候会这样说。核心概念是封装和重用;有些东西您可能不太关心重用,但您几乎总是想要封装。如果它是一个静态变量,它不是真正封装的,是吗?想想谁需要访问它,为什么,以及您可以将它隐藏在客户机代码之外多远。好的设计通常可以在不破坏客户的情况下改变其内部结构,这就是您想要的 认为 关于。我同意Scott Meyers(有效C++)关于很多事情。OOP远远超出了class关键字。如果您从未听说过它,请查看属性:是的,它们可以是静态的,C有一个非常好的使用它们的方法。而不是 字面 使用静态 变量 . 就像我在列表开头暗示的那样: 想想以后怎么不开枪打自己的脚 随着时间的推移,类会发生变化,这是许多程序员在设计类时无法做到的。

    2. 看看有人提到的RX框架。对于您描述的这种情况,要使用的线程模型如果没有关于用例imho的更多细节,是不容易确定的。确保你知道你在用线程做什么。很多人无法找到挽救自己生命的线索;这并不难,当(重新)使用代码时,确保安全是可以做到的。记住,控制器应该经常与它们控制的对象分开(例如,不是同一个类);如果你不知道,可以查阅一本关于MVC的书,然后购买“四人帮”。

    3. 取决于你需要什么。对于许多应用程序来说,一个几乎完全由静态数据填充的类已经足够好了;就像一个免费的单例类。它可以做得非常好。有时,您更希望有多个实例或玩注入,这使得它更加复杂。

    4. 我建议线程和事件。使代码事件驱动变得容易实际上是C IMHO的一个优点。

    嗯,杀死单身汉…

    以我的经验,更多 常见的 使用那些年轻的程序员把单件放到,只不过是浪费类关键字而已。也就是说,它们作为一个有状态的模块,在 highlander 类;还有一些不好的单例实现要匹配。是否因为他们没有 学习 他们在做什么,或者在大学里只有JAVA,我不知道。回到C语言中,它被称为在文件范围内使用数据并公开API。在C语言(和Java)中,你有点像它是一个比很多语言都多的类。哎呀!=class关键字;学习好lhs。

    一个编写得体的类可以使用静态数据来有效地实现一个单例,并使编译器完成保持一个单例的工作,或者像您将要得到任何东西一样。做 不是 除非你真的知道你在干什么,否则就用继承来取代单身。对这些东西的继承做得不好,会导致更脆弱的代码,对waaaay了解得更多。类应该是哑的,数据是智能的。这听起来很愚蠢,除非你看看声明 深深地 . 将继承imho用于这种情况通常是一件坏事(tm),因为某种原因,语言具有模块/包的概念。

    如果你愿意的话,嘿,你很久以前就把它变成了单件的,对吗? 坐下来 想一想:我如何才能最好地构造这个应用程序,以使它以xxx的方式工作,然后想一下以xxx的方式工作会对事情产生怎样的影响,例如,这样做会成为线程间争用的一个来源吗?你可以在一个小时内完成很多事情。当你长大后,你会学到更好的技巧。

    下面是一个关于xxx方法的建议,从:(可视化)编写(^hing)一个复合控制器类开始,它作为一个管理器处理它引用的对象。这些对象是您的单例对象,而不是控制器持有它们,它们只是这些类的实例。这不是许多应用程序的最佳设计(特别是在重线程的IMHO中可能是一个问题),但它通常可以解决导致大多数年轻人只接触到一个单例的问题,并且它可以适用于大量的程序。就像设计图案CS102。忘了你在CS 607中学到的单身汉吧。

    这个控制类,也许“应用程序”更合适;,基本上解决了您对单例和存储配置的需求。如何以一种崇高的OO方式(假设你 了解OOP),不要开枪(再次),这是你自己教育的一个练习。

    如果这表明,我不喜欢所谓的单例模式,尤其是它经常被误用。将代码库移出它,通常取决于您准备使用多少重构。单子就像是全局变量:方便,但不是黄油。嗯,我想我会把它放在我的报价文件里,有一个很好的短语…

    老实说,您比这里的任何人都了解更多有关代码库和应用程序的信息。所以没人能真正为你设计它,建议比行动更能说明问题,至少我来自哪里。

        5
  •  0
  •   Amardeep AC9MF    14 年前

    在一个应用程序/流程中,我限制自己最多只能有两个单件。其中一个通常被称为sysconfig,它包含一些可能以全局变量或其他损坏概念结束的内容。我没有第二个名字,因为到目前为止,我还没有真正达到我的极限。-)

    静态成员变量有它们的用途,但我把它们看作是直肠病学家。当你需要一个救命恩人,但几率应该是“百万比一”(塞恩菲尔德参考),你找不到更好的方法来解决这个问题。

    创建一个实现线程侦听器的基本工具类。它的派生类将具有特定于工具的驱动程序等。为每个工具实例化派生类,然后将对象存储在某种类型的容器中。在清理时,只需遍历容器。每个仪器实例都应该通过向它传递一些注册信息来构造,这些信息关于将输出/状态/任何内容发送到哪里。发挥你的想象力。OO的功能变得相当强大。

        6
  •  0
  •   Oak    14 年前

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

    (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 因此。然后只创建一个 配置 一开始就把它传给任何地方。

    欺骗:

    • 您需要重构许多构造函数以及调用它们的每个地方。
    • 如果您不从对象派生顶级类,这将无法很好地工作。好吧,你可以加上 config 字段到派生类,只是不那么优雅。

    赞成的意见

    • 不需要太多的努力来改变继承和构造函数,以及 发巨响 -你可以全部切换 Configuration.Instance 具有 配置 .
    • 您完全摆脱了静态变量;因此,如果您的应用程序突然变成了一个库,并且有人试图同时调用多个方法或其他方法,那么现在就没有问题了。
        7
  •  0
  •   Donnie    14 年前

    好问题。我的一些想法…

    static 在C应该 只有 用于以下数据: 确切地 同样的 全部的 实例 属于某一类。既然你现在被困在单身的地狱里,不管怎么说,你只有一个实例,但是一旦你打破了这一点,这就是一般规则(至少对我来说是这样)。如果您开始对类进行线程处理,那么您可能希望在静态使用上退后,因为这样会产生潜在的并发问题,但这是以后可以解决的问题。

    我不确定您的硬件实际上是如何工作的,但是假设有一些基本功能在所有这些功能中都是相同的(例如,您如何在原始数据级别或类似级别与它们进行交互),那么这是创建类层次结构的完美实例。基类使用虚拟方法实现低级的/类似的东西,以便子类重写以实际正确地解释数据/向前提供数据/做任何事情。

    祝你好运。