问题描述
今天我有一个顿悟,那是我做错了一切.一些历史记录:我继承了一个C#应用程序,这实际上只是静态方法的集合,这是C#代码的完全过程混乱.我当时知道了这一点,这是我所知道的最好的,引入了许多大学后的OOP知识.简而言之,代码中的许多实体都被证明是单身人士.
今天,我意识到我需要3个新类,每个类都遵循相同的单例模式以匹配其余软件.如果我不断跌倒这个滑坡,最终我的应用程序中的每个班级都将是单身人士,这在逻辑上与原始静态方法没有什么不同.
我需要重新思考的帮助.我知道依赖注入,这通常是打破单身诅咒的策略.但是,我有一些与此重构有关的具体问题,以及这样做的最佳实践.
-
使用静态变量封装配置信息的使用如何接受?我对使用静态有一个大脑障碍,我认为这是由于上大学的早期OO课程所致,教授说静态是不好的.但是,我是否应该每次访问课程重新配置课程?访问硬件时,可以将静态指针留给所需的地址和变量,还是我应该不断执行Open()和Close()操作?
-
现在,我有一种充当控制器的方法.具体来说,我不断对数据进行调查(通过硬件驱动程序)以获取数据.这种类型的控制器应该是要走的方式,还是我应该在程序的启动中为每个仪器分开线程?如果是后者,我该如何使这个对象为导向?我应该创建名为InstrumentAListener和InstrumentBListener的类吗?还是有一些标准方法可以接近此?
-
是否有更好的方法来进行全局配置?现在,我只是在整个代码中自由地撒上Configuration.Instance.Foo.几乎每个班级都使用它,因此也许将其保留为单身人士是有道理的.有任何想法吗?
-
我的很多课程都是SerialPortWriter或DataFileWriter,必须坐在周围等待这些数据流入.由于它们一直处于活动状态,因此我应该如何安排这些数据以便聆听数据进来时生成的事件?
有关如何摆脱单身人士和其他模式过度使用的任何其他资源,书籍或评论将有所帮助.
推荐答案
好吧,这是我在攻击这个问题时最好的镜头:
(1)静态
static您可能遇到的问题是,它在.NET和说,C ++中意味着不同的事情.静态基本上意味着它可以在类本身上访问.至于可接受性 id说,它更多的是您在课堂上进行非常规操作.或者只是Math.Abs(...)之类的一般性.您应该用于全局配置的内容可能是用于持有当前/活动配置的静态访问的属性.也许还有一些用于加载/保存设置配置的静态类,但是配置应为 object ,以便可以通过操作等等. 公共类MyConcuration { 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<>,它是反应性框架(rx).
另一种方法是使用ThreadPool安排您的轮询任务,因为如果您有很多硬件可以池,则可能会获得大量线程.请确保在使用任何类型的线程之前了解很多内容.犯错很容易,您甚至可能没有意识到. 这本书是一个出色的来源,会教您很多.
无论哪种方式,您都应该构建服务(实际上只是一个名称),以管理负责收集有关服务信息的硬件(本质上是模型图案).从那里,您的中央控制器可以使用它们来访问将程序逻辑保存在控制器中的数据以及服务中的硬件逻辑.
(3)全局配置
我可能已经在第1点触摸了这个主题,但是通常这是我们去的地方,如果您发现自己打字太多,您可以随时将其从那里拉出,假设.Instance是一个对象.
MyConfiguration cfg = MyConfiguration.Current cfg.Foo // etc...
(4)聆听数据
再次反应性框架可以帮助您,或者您可以建立事件驱动的模型使用触发器进行传入数据.这将确保您在数据进来之前不会阻止线程.它可以大大降低应用程序的复杂性.
其他推荐答案
对于初学者,您可以通过"注册表"模式限制使用单例,这有效地意味着您有一个Singleton,可以使您进入一堆其他预配置对象.
这不是"修复",而是一种改进,它使许多单元的对象更为正常和可测试.例如...(完全人为的示例)
HardwareRegistry.SerialPorts.Serial1.Send("blah");
但真正的问题似乎是您正在努力制作一组可以很好地工作的对象. OO中有两个步骤....配置对象,然后让对象做它们的事情.
因此,也许您可以看一下如何配置非单身对象一起工作,然后将其悬挂在注册表中.
静态: -
这里规则的许多例外,但是通常,避免使用它,但是它对于执行单例和创建可以在对象上下文之外进行"常规"计算的方法很有用. (例如Math.min)
数据监视: -
通常最好在暗示时做,创建一个带有一堆预配置对象的线程,可以进行监视.使用消息传递在线程之间进行通信(通过线程安全队列)限制线程锁定问题.使用注册表模式访问硬件资源.
您想要像仪器列表的东西,该仪表式列表使用了仪器协议(您为每个协议子类别)到logdata.命令模式可能在这里使用.
配置: -
拥有您的配置信息,并使用诸如"构建器"模式之类的内容将您的配置转换为以特定方式设置的一组对象.即,不要让您的课程意识到配置,制作以特定方式配置对象的对象.
串行端口: -
我对这些工作进行了很多工作,我拥有的是串行连接,它会生成一系列字符,并将其发布为事件.然后,我有一些将协议流解释为有意义的命令的东西.我的协议课程可与串行连接继承的通用" iConnection"一起工作.....我还具有TCPConnections,oberconnections等,以便能够注入测试数据或将管道串行端口从一台计算机到另一台计算机等等.协议类仅解释流,具有statemachine和dispatch命令.该协议通过连接进行预配置,各种事物会在协议上注册,因此,当它具有有意义的数据时,它们将被触发并做事.所有这些都是从一开始就通过配置构建的,或者如果某些东西发生了变化,则可以随时重建.
其他推荐答案
由于您知道依赖型注入,您是否考虑过使用IOC容器来管理生命值?请参阅我的答案
问题描述
Today I had an epiphany, and it was that I was doing everything wrong. Some history: I inherited a C# application, which was really just a collection of static methods, a completely procedural mess of C# code. I refactored this the best I knew at the time, bringing in lots of post-college OOP knowledge. To make a long story short, many of the entities in code have turned out to be Singletons.
Today I realized I needed 3 new classes, which would each follow the same Singleton pattern to match the rest of the software. If I keep tumbling down this slippery slope, eventually every class in my application will be Singleton, which will really be no logically different from the original group of static methods.
I need help on rethinking this. I know about Dependency Injection, and that would generally be the strategy to use in breaking the Singleton curse. However, I have a few specific questions related to this refactoring, and all about best practices for doing so.
How acceptable is the use of static variables to encapsulate configuration information? I have a brain block on using static, and I think it is due to an early OO class in college where the professor said static was bad. But, should I have to reconfigure the class every time I access it? When accessing hardware, is it ok to leave a static pointer to the addresses and variables needed, or should I continually perform Open() and Close() operations?
Right now I have a single method acting as the controller. Specifically, I continually poll several external instruments (via hardware drivers) for data. Should this type of controller be the way to go, or should I spawn separate threads for each instrument at the program's startup? If the latter, how do I make this object oriented? Should I create classes called InstrumentAListener and InstrumentBListener? Or is there some standard way to approach this?
Is there a better way to do global configuration? Right now I simply have Configuration.Instance.Foo sprinkled liberally throughout the code. Almost every class uses it, so perhaps keeping it as a Singleton makes sense. Any thoughts?
A lot of my classes are things like SerialPortWriter or DataFileWriter, which must sit around waiting for this data to stream in. Since they are active the entire time, how should I arrange these in order to listen for the events generated when data comes in?
Any other resources, books, or comments about how to get away from Singletons and other pattern overuse would be helpful.
推荐答案
Alright, here's my best shot at attacking this question:
(1) Statics
The Problem with static that you may be having is that it means different things in .NET and say, C++. Static basically means it's accessible on the class itself. As for it's acceptability id say it's more of something you'd use to do non-instance specific operations on a class. Or just general things like Math.Abs(...). What you should use for a global config is probably a statically accessed property for holding the current/active configuration. Also maybe some static classes for loading/saving setting the config, however the config should be an Object so it can be passed around manipulated, etc. public class 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) Controller/Hardware
You should probably look into a reactive approach, IObserver<> or IObservable<>, it's part of the Reactive Framework (Rx).
Another approach is using a ThreadPool to schedule your polling tasks, as you may get a large number of threads if you have a lot of hardware to pool. Please make sure before using any kind of Threading to learn a lot about it. It's very easy to make mistakes you may not even realize. This Book is an excelent source and will teach you lots.
Either way you should probably build services (just a name really) for managing your hardware which are responsible for collecting information about a service (essentially a model-pattern). From there your central controller can use them to access the data keeping the program logic in the controller, and the hardware logic in the service.
(3) Global Configuration
I may have touched this subject in point #1 but generally that's where we go, if you find yourself typing too much you can always pull it out of there assuming the .Instance is an object.
MyConfiguration cfg = MyConfiguration.Current cfg.Foo // etc...
(4) Listening For data
Again the reactive framework could help you out, or you could build up an event-driven model that uses triggers for incoming data. This will make sure you're not blocking on a thread till data comes in. It can reduce the complexity of your application greatly.
其他推荐答案
for starters, you can limit use of singleton through the "Registry" pattern, which effectively means you have one singleton which allows you to get to a bunch of other preconfigured objects.
This is not a "fix" but an improvement, it makes the many objects that are singletons a little more normal and testable. eg... (totally contrived example)
HardwareRegistry.SerialPorts.Serial1.Send("blah");
but the real problem seems to be you are struggling with making a set of objects that work nicely together. There's two kind of steps in OO.... configuring objects, and letting objects do their thing.
so perhaps look at how you can configure non singleton objects to work together, and then hang that off a registry.
Static :-
Plenty of exceptions to the rules here, but in general, avoid it, but it is useful for doing singletons, and creating methods that do "general" computing outside the context of an object. ( like Math.Min )
Data Monitoring :-
its often better to do as you hint at, create a thread with a bunch of preconfigured objects that will do your monitoring. Use message passing to communicate between threads (through a thread safe queue) to limit thread locking problems. Use the registry pattern to access hardware resources.
you want something like a InstrumentListner that uses an InstrumentProtocol (which you subclass for each protocol) to I dunno, LogData. The command pattern may be of use here.
Configuration:-
have your configuration information and use something like the "builder" pattern to translate your configuration into a set of objects set up in a particular way. ie, don't make your classes aware of configuation, make a object that configures objects in a particular way.
Serial Ports :-
I do a bunch of work with these, what I have is a serial connection, which generates a stream of characters which it posts as an event. Then I have something that interprets the protocol stream into meaningful commands. My protocol classes work with a generic "IConnection" of which a SerialConnection inherits..... I also have TcpConnections, MockConnections, etc, to be able to inject test data, or pipe serial ports from one computer to another, etc. So Protocol classes just interpret a stream, have a statemachine, and dispatch commands. The protocol is preconfigured with a Connection, Various things get registered with the protocol, so when it has meaningful data they will be triggered and do their thing. All this is built from a configuration at the beginning, or rebuilt on the fly if something changes.
其他推荐答案
Since you know about Dependency Injection, have you considered using an IoC container to manage lifetimes? See my answer to a question on static classes.