1

I have a type of "PC" (i.e. personal computer) that I need to model in C++ (can use language standards as new as C++11, but no newer than that). In short, we have a lot of granularity for components in the PC (in my actual tool, it would be about 10 times the number of components shown here), i.e:

Tower
    Motherboard
        CPU
            CORE1
            CORE2
            CORE3
            CORE4
        RAM_BANK
            DIMM1
            DIMM2
            DIMM3
            DIMM4
        SATA_Controller
            Drive1
            Drive2
            Drive3
        PCIE_BUS
            VideoCard1
                VGA_OUT
                HDMI_OUT
                DVI_OUT
            VideoCard2
                VGA_OUT
                HDMI_OUT
    Power_Button

I would like to setup a common base class for all objects in the tree above so that I can iterate through all objects that use this common base class. Ideally, I'd like to be able to print off the inheritance tree shown above, in as clean a manner as possible. This is because I might have to support thousands of different "PCs" with different types of mothers boards, disk controllers, CPU types; some models may have components others don't have, etc.

In addition to being able to generate the tree shown above, in a generalized manner, I have some component-detection logic that needs to be implemented. For example, the motherboard class, upon detecting a specific version of a CPU class, will use a specific interface to communicate with that instantiation of that particular version of the CPU class, e.g. "Mother board detected an AMD64 CPU, version/model XYZ, use this interface to talk to it". The end goal is a re-usable way to assemble virtual "PCs", and automatically assign the appropriate interface for a parent to communicate bi-directionally with a child object.

Which design pattern should I consider for such an implementation? I was thinking of a simple common base class (i.e. widget class) which all other classes inherit. Then, the widget class has an abstract data type (list) that contains a reference/pointer to all other members in that class that also inherit from the widget parent class. However, I can't come up with a method to auto-add all widget-type child classes to the list. For example:

class widget {
    private:
        std::list<widget> children;
    protected:
        char name[256];
    public:
        void announce()
        { printf("Component name:%s.", this->name);}
};

class MotherBoard: public widget {
    private:
        CPU cpu1;
        CPU cpu2;
        RAM_bank rambank1;
    public:
        MotherBoard() {
            // Add all classes that derive from widget to this list.
            // i.e. cpu1, cpu2, rambank1.
            // Also, construct `cpu1` with a generic interface that
            // accepts 2 parameters: this.name (for this particular 
            // MotherBoard instantiation) and this->cpu1.name;
        }
};

class CPU: public widget {
    public:
        CORE core1;
        CORE core2;
        CORE core3;
        CORE core4;
};
class RAM_BANK: public widget {
    public:
        DIMM dimm1;
        DIMM dimm2;
        DIMM dimm3;
        DIMM dimm4;
};

Finally, does C++ itself support introspection enough to be able to automatically detect all members that derive from a specific class and add them the the children list? This is so that if a new component that inherits from widget is added as a member to the class, people can't forget to have it included in the children list.

Thank you.

  • 3
    Might be worth at least considering following (a subset of) the Desktop Management Task Force Common Information Model DMTF CIM. – Jerry Coffin Dec 6 '17 at 4:29
2

Structure

The good

Your Widget is a tree like structure, thanks to the list of sub-widgets. The advantage is that you can always decompose a Widget in smaller parts. This is perfect to represent the structure of your PC and to navigate between the parts.

A variant could have been the composite design pattern, but this wouldn't add much benefits here, except that you would differentiate components that have sub-components and final components which haven't.

The bad

What is missing, in your model is the bidirectional interface. The parent knows about its children, but the children don't know about their parents. SO the child needs a reference or a pointer back to its parent. You have to adapt the "assembly" process to achieve this

Another problem is that you don't use the Widget potential to its full extent. For example, the memory bank shouldn't just have 4 sub-objects; it should use the component list as well (i.e. adding 4 DIMM to the list).

The ugly

automatically assign the appropriate interface for a parent to communicate bi-directionally with a child object

If "interface" is meant in its OO sense, you're ok. Except that the interface is only the Widget interface. Is it appropriate enough ? (By the way, you have no virtual function which would adapt their behavior depending on the object class.)

If not, or if "interface" if it's meant in the domain world meaning (i.e. SATA, PCI, etc...), then you don't comply:

  • If CPU sends something to the memory bank, the memory bank should dialogue with its children in a bus fashion (i.e. all the messages to the bank are sent to all its children).
  • the SATA controller of your model doesn't use SATA interface to dialogue with its children (and by the way, the S of SATA means Serial, so that one child is connected to the next and not to the SATA controller).

So after restructuring for the bidirectional and further componentization, you could further add some methods to emulate the specific PC-interfaces. But this puts the Widget approach in question, because each sub class of widget would have a different interface, and you would never be sure if you use the right one ! For this you would need to use polymorphism and dynamic_cast, which will not be a piece of cake...

Alternative structure

An alternative design is to remove the general Widget list out of the Widget class, and add a list only to the classes that need it. For example the memory bank would have a DIMM list and the SATA controller a DISK list. THen you'd allways use the right interface.

Navigation across your structure would then be less easy: it's still possible to foresee methods at the top that recursively dispatch to the children until the bottom. But you'd certainly need to implement the visitor pattern if you want to have a generalized exploration algorithm for your structure, once for printing it out, another time to search for a component, etc... (each with a specific "visitor")

Building the structure

The end goal is a re-usable way to assemble virtual "PCs",

For the moment, you rely on the constructors to assemble the PC. If tomorrow you want to have not 4 DIMM but 8 ? What it you want to add a co-processor ? How do you know how many disks to add when you create a controller ? This approach is not realistic and not reusable.

You need to look at the builder pattern to be able to construct the parts separately and assemble them. Perhap's you'll even need a separate builder for each configurable component.

To introspect or not to introspect ?

With the two approaches (improvement of your model or alternative), and the builder, it's not needed to do any introspection.

If you need it at runtime, you need to make your class polymorphic by having at least one virtual function (e.g. a virtual destructor in the base class). You can look for dynamic_cast, which is a try-error approach to introspection. Or at type_info, but which has only limited use as type names are not standardized.

At compile time, there is is_same and is_base_of, but this won't help you at all in your case.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.