今天我有顿悟,而且我做错了。一些历史:我继承了一个C#应用程序,它实际上只是一个静态方法的集合,一个完全程序化的C#代码。我重构了这个当时最了解的,带来了大量的大学后OOP知识。长话短说,代码中的许多实体都变成了单身人士。
今天我意识到我需要3个新类,每个类都遵循相同的Singleton模式来匹配软件的其余部分。如果我一直在这个滑坡上翻滚,最终我应用程序中的每个类都将是Singleton,这与原始的静态方法组在逻辑上没有区别。
我需要帮助重新考虑这一点。我知道依赖注入,这通常是用来打破Singleton诅咒的策略。但是,我有一些与此重构相关的具体问题,以及所有关于这样做的最佳实践。
使用静态变量封装配置信息的可接受程度如何?我有一个使用静态的大脑块,我认为这是由于大学早期的OO课,教授说静态是坏的。但是,每次访问它时,我是否应该重新配置该类?访问硬件时,是否可以保留指向所需地址和变量的静态指针,还是应该继续执行Open()
和Close()
操作?
现在我有一个方法充当控制器。具体来说,我不断轮询几个外部仪器(通过硬件驱动程序)获取数据。这种类型的控制器应该是可行的方法,还是应该在程序启动时为每个仪器生成单独的线程?如果是后者,我该如何使这个面向对象?我应该创建名为InstrumentAListener
和InstrumentBListener
的类吗?或者有一些标准的方法来解决这个问题吗?
有更好的方法进行全局配置吗?现在我只是在整个代码中自由地散布Configuration.Instance.Foo
。几乎每个班级都使用它,所以将它保持为Singleton是有道理的。有什么想法吗?
我的很多课程都是SerialPortWriter
或DataFileWriter
,它们必须等待这些数据流入。由于它们一直处于活动状态,我应该如何安排这些是为了监听数据进入时生成的事件吗?
有关如何摆脱单身人士和其他模式过度使用的任何其他资源,书籍或评论都会有所帮助。
答案 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
实例,并在任何地方传递它。
缺点:
config
字段添加到派生类中,它不那么优雅。赞成
Configuration.Instance
切换所有config
。答案 6 :(得分:0)
好问题。我的一些快速想法......
static
在C#中仅用于所有 实例的完全相同的数据特定班级的强者。因为你现在被困在Singleton hell你只有一个实例,但是一旦你突破了这是一般规则(至少,它适合我)。如果你开始线程你的类,你可能想要退回静态使用,因为那时你有潜在的并发问题,但这是可以在以后解决的问题。
我不确定你的硬件是如何工作的,但是假设所有这些基本功能都是相同的(例如,如何在原始数据级别或类似地与它们进行交互)那么这是一个完美的实例创建一个类层次结构。基类使用虚拟方法实现低级/类似的东西,以便后代类覆盖以实际正确地解释数据/向前馈送/无论如何。
祝你好运。