重构单身过度使用

时间:2010-05-27 22:25:00

标签: c# .net design-patterns oop

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

今天我意识到我需要3个新类,每个类都遵循相同的Singleton模式来匹配软件的其余部分。如果我一直在这个滑坡上翻滚,最终我应用程序中的每个类都将是Singleton,这与原始的静态方法组在逻辑上没有区别。

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

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

  2. 现在我有一个方法充当控制器。具体来说,我不断轮询几个外部仪器(通过硬件驱动程序)获取数据。这种类型的控制器应该是可行的方法,还是应该在程序启动时为每个仪器生成单独的线程?如果是后者,我该如何使这个面向对象?我应该创建名为InstrumentAListenerInstrumentBListener的类吗?或者有一些标准的方法来解决这个问题吗?

  3. 有更好的方法进行全局配置吗?现在我只是在整个代码中自由地散布Configuration.Instance.Foo。几乎每个班级都使用它,所以将它保持为Singleton是有道理的。有什么想法吗?

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

  5. 有关如何摆脱单身人士和其他模式过度使用的任何其他资源,书籍或评论都会有所帮助。

7 个答案:

答案 0 :(得分:13)

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

(1)静力学

你可能遇到的static问题是它在.NET中意味着不同的东西,比如C ++。静态基本上意味着它可以在类本身上访问。至于它的可接受性 id说它更像是你用来在类上进行非实例特定操作的东西。或者只是像Math.Abs(...)这样的一般事情。您应该用于全局配置的可能是用于保持当前/活动配置的静态访问属性。也可能是一些用于加载/保存设置配置的静态类,但是配置应该是 Object ,因此可以在操作时传递等等。     公共类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。这样可以确保在数据进入之前不会阻塞线程。它可以大大降低应用程序的复杂性。

答案 1 :(得分:4)

对于初学者来说,你可以通过“注册表”模式限制单例的使用,这实际上意味着你有一个单例,它允许你到达一堆其他预配置的对象。

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

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

但真正的问题似乎是你正在努力制作一组能很好地协同工作的对象。在OO中有两种步骤....配置对象,让对象做他们的事情。

所以或许看一下如何配置非单件对象协同工作,然后将其挂起注册表。

静态: -

这里的规则有很多例外,但总的来说,避免它,但它对于做单例,并创建在对象上下文之外进行“通用”计算的方法很有用。 (像Math.Min)

数据监控: -

通常你可以更好地做到这一点,创建一个带有一堆预配置对象的线程来进行监控。使用消息传递在线程之间进行通信(通过线程安全队列)以限制线程锁定问题。使用注册表模式访问硬件资源。

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

配置: -

获取您的配置信息并使用类似“构建器”模式的内容将您的配置转换为以特定方式设置的一组对象。即,不要让你的类知道配置,创建一个以特定方式配置对象的对象。

串行端口: -

我做了很多这些工作,我所拥有的是一个串行连接,它生成一个字符流,它作为一个事件发布。然后我有一些东西将协议流解释为有意义的命令。我的协议类使用一个通用的“IConnection”,其中一个SerialConnection继承.....我也有TcpConnections,MockConnections等,以便能够注入测试数据,或管道串口从一台计算机到另一台,等等。所以协议类只解释流,具有statemachine和dispatch命令。该协议预先配置了一个Connection,各种各样的东西都被协议注册,所以当它有有意义的数据时,它们将被触发并完成它们的工作。所有这些都是从一开始的配置构建的,或者如果发生变化,则可以即时重建。

答案 2 :(得分:1)

既然您了解依赖注入,您是否考虑过使用IoC容器来管理生命周期?有关静态类的问题,请参阅my answer

答案 3 :(得分:1)

答案 4 :(得分:0)

我在应用程序/流程中最多只限于两个单身人士。一个通常被称为SysConfig,并且包含可能最终成为全局变量或其他腐败概念的东西。我没有第二个名字,因为到目前为止,我从未达到过我的极限。 : - )

静态成员变量有它们的用途但我在观察直肠科医生时会查看它们。当你需要一个救生员,但赔率应该是“百万对一”(Seinfeld参考),你找不到更好的方法来解决问题。

创建一个实现线程监听器的基础工具类。派生类将具有仪器特定的驱动程序等。为每个仪器实例化派生类,然后将对象存储在某种容器中。在清理时,只需遍历容器即可。每个工具实例都应该通过向其传递一些注册信息来构建,其中包括发送其输出/状态/任何内容的位置。在这里运用你的想象力。 OO的东西变得非常强大。

答案 5 :(得分:0)

我最近不得不解决类似的问题,我所做的似乎对我有用,也许它会帮助你:

(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
  • 你完全摆脱了静态变量;所以现在没有问题,例如,如果你的应用程序突然变成一个库,有人试图同时调用多个方法或其他什么。

答案 6 :(得分:0)

好问题。我的一些快速想法......

static在C#中用于所有 实例的完全相同的数据

我不确定你的硬件是如何工作的,但是假设所有这些基本功能都是相同的(例如,如何在原始数据级别或类似地与它们进行交互)那么这是一个完美的实例创建一个类层次结构。基类使用虚拟方法实现低级/类似的东西,以便后代类覆盖以实际正确地解释数据/向前馈送/无论如何。

祝你好运。