admin管理员组

文章数量:1292069

I want to create an interface class for a communication interface - see source code. However, different interfaces (USB, COM, etc.) have different configuration options, so the Open( ) function must/should reflect this. Thus I've created a template class:

#include <stdio.h>
#include <iostream>


template <typename T>
class Interface
{
    public:
        virtual int Open(const T & config) = 0;
        virtual void PrintConfig(void) = 0;
};


struct config_c1
{
    int a;
    int b;
};

class C1 : public Interface<struct config_c1>
{
    public:
        int Open(const struct config_c1 & config) override
        {
            config_ = config;
            return 0;
        }
        void PrintConfig(void) override
        {
            std::cout << config_.a << " " << config_.b << std::endl;
        }
    private:
        struct config_c1 config_;
};

struct config_c2
{
    int c;
    int d;
};

class C2 : public Interface<struct config_c2>
{
    public:
        int Open(const struct config_c2 & config) override
        {
            config_ = config;
            return 0;
        }
        void PrintConfig(void) override
        {
            std::cout << config_.c << " " << config_.d << std::endl;
        }
    private:
        struct config_c2 config_;
};


int main (void)
{
    config_c1 cfg1{0, 1};
    config_c2 cfg2{2, 3};

    C1 c1;
    c1.Open(cfg1);
    c1.PrintConfig();

    C2 c2;
    c2.Open(cfg2);
    c2.PrintConfig();

/* But I realized this won't work as "I" must be declared either as I<config_c1> or I<config_c2> */
    Interface * I = &c1;
    I->PrintConfig();

    I = &c2;
    I->PrintConfig();
}

However, using this construct I can't create a base pointer to access different interface configurations.

What is the way out??

I've been thinking to update the interface following way:

class Interface
{
    public:
        virtual int Open(const void * config) = 0;
        virtual void PrintConfig(void) = 0;
};

In such a case I would need to distinguish between different configurations in some way like:

struct config_c1
{
    int interface_id;
}

But that seems awkward in C++ and I would expect more elegant way?? Thx for any hints or directions.

EDIT: My mistake. I've provided an example to print stuff in a generic way, but the main target was to have a common interface for all the operations: Open, Close, Read, Write. So I can do:

Interface * I = <whichever_class_implementing_that_interface>;
I->Open(config);
I->Write(message);
I->Close();

If that's an incorrect approach / design, what are the ways these things are implemented?

I want to create an interface class for a communication interface - see source code. However, different interfaces (USB, COM, etc.) have different configuration options, so the Open( ) function must/should reflect this. Thus I've created a template class:

#include <stdio.h>
#include <iostream>


template <typename T>
class Interface
{
    public:
        virtual int Open(const T & config) = 0;
        virtual void PrintConfig(void) = 0;
};


struct config_c1
{
    int a;
    int b;
};

class C1 : public Interface<struct config_c1>
{
    public:
        int Open(const struct config_c1 & config) override
        {
            config_ = config;
            return 0;
        }
        void PrintConfig(void) override
        {
            std::cout << config_.a << " " << config_.b << std::endl;
        }
    private:
        struct config_c1 config_;
};

struct config_c2
{
    int c;
    int d;
};

class C2 : public Interface<struct config_c2>
{
    public:
        int Open(const struct config_c2 & config) override
        {
            config_ = config;
            return 0;
        }
        void PrintConfig(void) override
        {
            std::cout << config_.c << " " << config_.d << std::endl;
        }
    private:
        struct config_c2 config_;
};


int main (void)
{
    config_c1 cfg1{0, 1};
    config_c2 cfg2{2, 3};

    C1 c1;
    c1.Open(cfg1);
    c1.PrintConfig();

    C2 c2;
    c2.Open(cfg2);
    c2.PrintConfig();

/* But I realized this won't work as "I" must be declared either as I<config_c1> or I<config_c2> */
    Interface * I = &c1;
    I->PrintConfig();

    I = &c2;
    I->PrintConfig();
}

However, using this construct I can't create a base pointer to access different interface configurations.

What is the way out??

I've been thinking to update the interface following way:

class Interface
{
    public:
        virtual int Open(const void * config) = 0;
        virtual void PrintConfig(void) = 0;
};

In such a case I would need to distinguish between different configurations in some way like:

struct config_c1
{
    int interface_id;
}

But that seems awkward in C++ and I would expect more elegant way?? Thx for any hints or directions.

EDIT: My mistake. I've provided an example to print stuff in a generic way, but the main target was to have a common interface for all the operations: Open, Close, Read, Write. So I can do:

Interface * I = <whichever_class_implementing_that_interface>;
I->Open(config);
I->Write(message);
I->Close();

If that's an incorrect approach / design, what are the ways these things are implemented?

Share Improve this question edited Feb 13 at 11:34 ST Renegade asked Feb 13 at 10:28 ST RenegadeST Renegade 3113 silver badges16 bronze badges 17
  • What's the point of Open? Why can't this be handled by child class constructor? – Yksisarvinen Commented Feb 13 at 10:36
  • Open opens a connection based on users provided input. If such a connection exists, closes the connection first and opens a new one based on provided inputs. I could solve the problem moving the config into constructor, but how could I close the connection afterwards and open it again with different configuration?| Only by destroying the object and creating a new on? Is my assumption correct? – ST Renegade Commented Feb 13 at 10:42
  • "I realized this won't work" - why do you want to write that code, and not the other code that you already have? – Thomas Weller Commented Feb 13 at 10:44
  • Because: Interface * I = SerialImplementationFactory( "USB" ) (returns USBSerialImplementation or COMSerialImplementation) l->Open() l->Write(message) l->Read(message) Instead of: if (USB) USB logic if (COM) COM logic ??? And if I extend the set of serial implementations, I would have to extend this logic as well, which I don't want? Maybe extending the factory or so... but nothing else. – ST Renegade Commented Feb 13 at 10:57
  • You are asking for two mutually exclusive things at the same time. By requiring a common base class, you are in essence saying "I want to fet the difference between the different interfaces, and only use what they all have in common". By requiring Open to be available, you are saying "I want to know exactly what interface I'm dealing with". You cannot have both at the same time. – n. m. could be an AI Commented Feb 13 at 11:06
 |  Show 12 more comments

2 Answers 2

Reset to default 0

Ok, so I mixed up two approaches. Approach 1 (I know in advance which interface I'll use):

class Interface
{
    public:
        virtual int Write(std::string data) = 0;
        virutal int Read(std::string data) = 0;
};

class COM : public Interface
{
    public:
        int Open(com_config & ) { ...implementation... };
        int Close() { ...implementation... };        
        int Write(std::string data) { ...implementation... };
        int Read(std::string data) { ...implementation... };
}

class USB : public Interface
{
    public:
        int Open(usb_config & ) { ...implementation... };
        int Close() { ...implementation... };        
        int Write(std::string data) { ...implementation... };
        int Read(std::string data) { ...implementation... };
}

class ControlSomething
{
   public:
      ControlSomething(Interface * I);
      int RestartDevice() { I->Write( <message_to_restart_device>); };
      int DisableFeature() { I->Write( <message_to_disable_feature>); };
   private:
      Interface * I;
}

int main (void)
{
    /* I know in advance, which interface I'm going to use - USB */
    USB usb_connection;
    usb_config usb_cfg = {...};
    usb_connection.Open(usb_config);

    ControlSomething cs{usb_connection};
    cs.RestartDevice();
    cs.DisableFeature();

    usb_connection.Close();
}

Approach 2 (I don't know in advance which interface I'll use); the user provides the input from command line about the used interface:

/* Extend Interface class with Open/Close and use a generic type for config */
class Interface
{
    public:
        virtual int Open(std::string & config) = 0;
        /* Or use YAML / JSON?? */
        // virtual int Open(std::string & json_file_path) = 0;
        /* Or use void pointer?? not really safe, but doable */
        // virtual int Open(const void * config) = 0;
        virutal int Close() = 0;
        virtual int Write(std::string data) = 0;
        virutal int Read(std::string data) = 0;
};

/* COM & USB interface updates the Open */
...
int Open(std::string & config);
or
int Open(std::string & json_file_path);
or
int Open(const void * config);




  class ControlSomething
    {
       public:
          ControlSomething(Interface * I);
          int Connect(std::string & config) { I->Open(config);};
          or
          int Connect(std::string & json_file_path) { I->Open(json_file_path); };
          ...
          int Disconnect(void) { I->Close(); };
          int RestartDevice() { I->Write( <message_to_restart_device>); };
          int DisableFeature() { I->Write( <message_to_disable_feature>); };
       private:
          Interface * I;
    }
    

int main (char argc, char ** argv)
{
    /* pseudo code */
    args = parse_argv();
    /* args content - type of connection, configuration string / json etc. */    
    Interface * I = CreateInterface( args.type );
    
    ControlSomething cs{I};
    cs.Open(args.config);
    cs.RestartDevice();

    cs.DisableFeature();

    cs.Close();
}

Does the second solution makes sense? Or this approach is not used at all? (code not tested, not sure it will work)

As correctly pointed out in the comment, you are trying to mix a CRTP-like approach (it's not, actually, crtp, but similar. More info here CRTP) with virtual inheritance, which may work elegantly in some contexts but not here, while you are trying the type erasure with template class. The main goal is to move the Config type outside the Interface. One possible approach is to do it below. It may not cover all your needs, but it will give you some hint.


#include <iostream>

class Interface {
public:
    virtual void PrintConfig() const = 0;
    virtual ~Interface() = default;
};

struct config_c1 { int a; int b; };
struct config_c2 { int c; int d; };

template<typename> struct ConfigPrinter {};
template<> struct ConfigPrinter<config_c1> {
    void operator()(const config_c1& cnf) const noexcept {
        std::cout << cnf.a << " " << cnf.b << std::endl;
    }
};
template<> struct ConfigPrinter<config_c2> {
    void operator()(const config_c2& cnf) const noexcept {
        std::cout << cnf.c << " " << cnf.d << std::endl;
    }
};

template<typename TConf>
requires requires(const TConf& c) { ConfigPrinter<TConf>{}(c); }
class ConfigHolder {
public:
    int Open(const TConf& conf) {
        config_ = conf;
        return 0;
    }

protected:
    void Print() const { ConfigPrinter<TConf>{}(config_); }
private:
    TConf config_;
};

template<typename TConf>
class SomeC : public Interface, public ConfigHolder<TConf> {
public:
    void PrintConfig() const override { ConfigHolder<TConf>::Print(); }
};

int main ()
{
    config_c1 cfg1{0, 1};
    config_c2 cfg2{2, 3};

    SomeC<config_c1> c1;
    c1.Open(cfg1);
    c1.PrintConfig();

    SomeC<config_c2> c2;
    c2.Open(cfg2);
    c2.PrintConfig();

    /* But I realized this won't work as "I" must be declared either as I<config_c1> or I<config_c2> */
    Interface* I = &c1;
    I->PrintConfig();

    I = &c2;
    I->PrintConfig();
}

本文标签: design patternsInterface class with quotvariablequot config structure in function in CStack Overflow