Frage

I'm looking for a good design pattern for this scenario:

I have a bunch of hardware devices, each one represented by a class in a C++ library (let's call that class Device). The computer using that library opens a UDP socket with each device (they're identified by a IP in my network). The communication bewteen library and devices is via JSON messages. So, the library sends and receives JSON objects via sockets.

First thing the library does is to discover the number of devices, asking for a JSON object that contains that information (the IP of each device, basically). After that, builds a vector with all the Device objects.

This could be seen as a pool of objects, because, at the end, I have a set of Device objects, each one encapsulating a network connection. But they're not interchangeable. In a pool of database connections, I can pick one object and use it to connect with my database. But if I need to send a message to a specific device, I need that Device object. Not another one.

The original creator of the class modeled it just as a Singleton containing a vector. Because we just want one entry point to access the vector of Device objects. But I think this can cause problems.

Are there better ways to model this class?

War es hilfreich?

Lösung 2

I wouldn't call this collection a pool. The point of having a pool of objects is to reduce instantiation costs of multiple instances of the same type, allowing the client to "return" the resource back to the pool after using it. Your devices cannot be interchanged transparently, and the point of the collection is not related to resource usage at all.

There are two approaches that come to my mind:

  1. You can use a map to quickly fetch a certain Device using its unique key (e.g. std::map<std::string, Device>, but you should probably have a better idea of what to use as the key instead of a plain string), similar to:

    // find the right device instance
    string deviceInfo = "some-unique-key";
    Device device = devicesMap.find(deviceInfo)->second;
    
    // send the data
    string json = serialize(dataObject);
    device.send(json);
    

    On the other hand, you might want to delegate the actual (de)serialization to the Device itself, in case that the protocol might differ for some devices:

    // find the right device instance
    string deviceInfo = "some-unique-key";
    Device device = devicesMap.find(deviceInfo)->second;
    
    // send the object, Device class will know how to serialize it
    device.send(dataObject);
    
  2. Another interesting alternative is to send the messages to all Device instances, and let them decide if the message is relevant for them (based on whatever you think it's appropriate). This approach allows scenarios where multiple devices can dispatch the same message, if they feel like it, or simply be notified that something happened without even sending anything:

    // notify all devices that we have a message
    for (auto &device : devicesVector) {
    
        // you are only notifying the device.
        // each instance should decide whether or not to
        // pass the message forward (based on its contents),
        // and how to serialize it
    
        device.notify(dataObject);
    }
    

The fact that your current implementation is a Singleton is a separate issue. Singleton is a creational pattern, related to the object's lifetime, and is usually used only to avoid passing the dependency around. It simplifies ctor signatures a bit, but there might be some laziness involved also, especially considering the fact that you make unit-testing a bit harder.

Proper dependency injection patterns would require you to pass this service explicitly to all consumers (usually thorough constructor injection), which would result in less coupling and in turn allow easier unit testing. This doesn't mean you need to refactor your entire code to use a DI container, but simply pass the "thingy" which handles communication to its callers through their constructors.

Andere Tipps

Seems to be that what you need is an Abstract Factory Pattern

The idea is that you ask a factory for an object that ties together all related components so that you can't get them wrong. This is classically used in UI design (to make sure, for example, that you can't accidentally mix Windows buttons with Mac Style combo boxes) but directly applies to you.

So, in Java syntax (since I haven't written C++ in years)

// this is where you do the discovery you mentioned.
DeviceManager manager = DeviceManager.getInstance(); 

Device d = manager.getDevice("some-id");
// device c, d will be from the same set here because a reference to them 
// can only come through this manager
Device c = manager.getDevice("c-id");

The Composite Pattern is also very handy for hiding cascading messaging functionality if that's what you need.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top