Refactoring Singleton Overuse

后端 未结 7 1958
一个人的身影
一个人的身影 2020-12-14 00:06

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 co

相关标签:
7条回答
  • 2020-12-14 00:35

    Great question. A few quick thoughts from me...

    static in C# should only be used for data that is exactly the same for all instances of a given class. Since you're currently stuck in Singleton hell you only have one instance of everything anyways, but once you break out of that this is the general rule (at least, it is for me). If you start threading your classes you may want to back off on static usage because then you have potential concurrency issues, but that's something that can be tackled later.

    I'm not sure how your hardware actually works, but assuming that there's some basic functionality that's the same across all of them (like, how you interface with them at a raw data level or similar) then this is a perfect instance to create a class hierarchy. The base class implements the low level / similar stuff with virtual methods for descendant classes to override to actually properly interpret the data / feed it onward / whatever.

    Good luck.

    0 讨论(0)
  • 2020-12-14 00:41

    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.

    0 讨论(0)
  • 2020-12-14 00:46

    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.

    0 讨论(0)
  • 2020-12-14 00:50
    1. You (the OP) seem preoccupied with OO design, well, I'll put it this way when thinking about the static variables things. The core concept is encapsulation and reuse; somethings you could care less about reusing but you almost always want the encapsulation. If it's a static variable, it's not really encapsulated, is it? Think about who needs to access it, why, and how far you can HIDE it from client code. Good designs often can change their internals without much breakage to clients, that is what you want to think about. I agree with Scott Meyers (Effective C++) about many things. OOP goes way beyond the class keyword. If you've never heard of it it, look up properties: yes they can be static, and C# has a very good way of using them. As opposed to literally using a static variable. Like I hinted at the start of this list item: think about how not to shoot yourself in the foot later as the class changes with time, that's something many programmers fail to do when designing classes.

    2. Take a look at that Rx framework someone mentioned. The threading model to use, for such a situation as you described, is not readily decidable without more specifics about the use case IMHO. Be sure you know what you're doing with threads. A lot of people can't figure out threads to save their lives; it's no that hard, being tread safe can be when (re)using code. Remember controllers should often be separate from the objects they are controlling (E.g. not the same class); if you don't know it, look up a book on MVC and buy gang of four.

    3. Depends on what you need. For many applications a class that is almost entirely filled with static data, is good enough; like a singleton for free. It can be done very OO. Sometimes you would rather have multiple instances or play with injection, that makes it more complex.

    4. I suggest threads and events. The ease of making code event driven is actually one of the nicer things about C# IMHO.

    Hmm, killing off singletons...

    In my experience, a lot of the more common uses that young programmers put singletons to, are little more than a waste of the class keyword. Namely something they meant as a stateful module being rolled into a highlander class; and there are some bad singleton implementations out there to match. Whether this is because they failed to learn what they're doing, or only had Java in college, I dunno. Back in C land, it's called a using data at file scope and exposing an API. In C# (and Java) you're kind of bound to it being a class more than many languages. OOP != class keyword; learn the lhs well.

    A decently written class can use static data to effectively implement a singleton, and make the compiler do the leg work of keeping it one, or as one as you are ever going to get of anything. Do NOT replace singletons with inheritance unless you seriously know what the heck you are doing. Poorly done inheritance of such things, leads to more brittle code that knows waaaay to much. Classes should be dumb, data is smart. That sounds stupid unless you look at the statement deeply. Using inheritance IMHO for such a thing, is generally a bad thing(tm), languages have the concept of modules/packages for a reason.

    If you are up for it, hey you did convert it to singletons ages ago right? Sit down and think a bit: how can I best structure this app, in order to make it work XXX way, then think how doing it XXX way impacts things, for example is doing this one way going to be a source of contention among threads? You can go over a lot of things in an hour like that. When you get older, you'll learn better techniques.

    Here is one suggestion for an XXX way to start with: (visualize) write(^Hing) a composite controller class, that works as a manager over the objects it references. Those objects were your singletons, not the the controller holds them, and they are but instances of those classes. This isn't the best design for a lot of applications (particularly can be an issue in heavily threaded ones IMHO), but it will generally solve what causes most younglings to reach for a singleton, and it will perform suitably for a vast array of programs. It's uh, like design pattern CS 102. Forget the singleton you learned in CS 607.

    That controlling class, perhaps "Application' would be a more apt ;), basically solves your need for singletons and for storing configuration. How to do it in a sublimely OO way (assuming you do understand OOP) and not shoot yourself in the foot (again), is an exercise for your own education.

    If it shows, I am not a fan of the so called singleton pattern, particularly how it is often misused. Moving a code base away from it, often depends on how much refactoring you are prepared to use. Singletons are like global variables: convenient but not butter. Hmm, I think I'll put that in my quotations file, has a nice phrase to it...

    Honestly, you know more about the code base and the application in question then anyone here. So no one can really design it for you, and advice speaks less then action, at least where I come from.

    0 讨论(0)
  • 2020-12-14 00:55

    I limit myself to at most two singletons in an application / process. One is usually called SysConfig and houses things that might otherwise end up as global variables or other corrupt concepts. I don't have a name for the second one since, so far, I've never actually reached my limit. :-)

    Static member variables have their uses but I view them as I view proctologists. A lifesaver when you need one but the odds should be a "million to one" (Seinfeld reference) that you can't find a better way to solve the problem.

    Create a base instrument class that implements a threaded listener. Derived classes of that would have instrument specific drivers, etc. Instantiate a derived class for each instrument then store the object in a container of some sort. At cleanup time just iterate through the container. Each instrument instance should be constructed by passing it some registration information on where to send its output/status/whatever. Use your imagination here. The OO stuff gets quite powerful.

    0 讨论(0)
  • 2020-12-14 01:00

    I recently had to tackle a similar problem, and what I did seemed to work well for me, maybe it will help you:

    (1) Group all "global" information into a single class. Let's call it Configuration.

    (2) For all classes which used to use these static objects, change them to (ultimately) inherit from a new abstract base class which looks something like

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

    (3) Change all constructors of classes deriving from MyBaseClass accordingly. Then just create one instance of Configuration at start and pass it on everywhere.

    Cons:

    • You need to refactor many of your constructors and every place in which they are called
    • This won't work well if you do not derive your top-level classes from Object. Well, you can add the config field to the derived class, it's just less elegant.

    Pros

    • Not a lot of effort to just change inheritance and constructors, and bang - you can switch all Configuration.Instance with config.
    • You get rid of static variables completely; so no problems now if, for example, your application suddenly turns into a library and someone is trying to invoke multiple methods concurrently or whatever.
    0 讨论(0)
提交回复
热议问题