Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Design pattern for method returning different types/classes

This is a question for the Object Design Pattern specialists.

Let's assume I have a Parser class that is in charge of reading/parsing a stream of data (that carry information packets of different types). Each of these packets carry a different type of information, so ideally I would have a class for each type of packet (PacketTypeA, PacketTypeB, ... each one with its own interface).

class Parser {

public:

    /* ctor */
    /* dtor */


   void read_packet(/* arguments */);

   // methods...
private:
   // more methods...
}

The method Parser::read_packet would then go through the stream and return a class (or pointer or reference to a class) to the appropriate packet type.

Would you use void pointers for this? How about a generic class (PacketBasicInterface) that would provide a common (partial) interface to query about the type of packet (so that any decision could then be made at runtime)?

// Pure virtual (abstract) class to provide a common (and partial) interface
class PacketBasicInterface {
public:

    std::string whoAmI() const = 0;
    bool amIofType(const std::string& type) const = 0;

}

// Class to access data of type A packet
class PacketTypeA : public PacketBasicInterface {

public:
    // methodA_1()
    // methodA_2(), ...
}

// Class to access data of type A packet
class PacketTypeB : public PacketBasicInterface {

public:
    // methodB_1()
    // methodB_2(), ...
}

Any thought or feedback would be very much appreciated!

Many thanks!

like image 405
mgfernan Avatar asked Mar 16 '17 17:03

mgfernan


People also ask

Can a project have multiple design patterns?

You do not have to use one pattern for every piece of functionality. In fact, many pieces of functionality don't use patterns at all, and some pieces will use multiple patterns.

Can Strategy pattern have multiple methods?

No you can have more than one method on your strategy interface. However, in order for your strategy object to actually use the Strategy pattern, at least one of the method implementations should differ between the different strategies.


2 Answers

This is what std::variant is for.

I would define an enumeration class, that enumerates all possible packet types:

enum class packet_type {initialization_packet, confirmation_type, ... };

And have read_packet return a tuple of packet_type and a variant:

typedef std::variant< ... > packet_info;

std::tuple<packet_type, packet_info> read_packet();

Don't really need a formal enumeration, but it makes it easier to figure out what to do with the variant.

A few variations on this general approach include:

  1. Using an opaque std::string, rather than a fixed enumeration, to specify the packet type.

  2. Using std::any instead of a formal std::variant.

  3. Instead of using a simple enumeration, or an opaque token like a std::string, use a slightly non-trivial class to define the packet type, with the class's methods taking the variant metadata as parameters, and encapsulating the operations that can be done on the packet.

Of course, as noted in the cited link, std::variant requires C++17. Which would be a good argument for you to update your compiler: you get a simple way to implement a completely type-safe approach.

like image 54
Sam Varshavchik Avatar answered Oct 27 '22 01:10

Sam Varshavchik


Double dispatching can be the way to go if you are looking for a design pattern from the realm of object oriented programming.
It follows a minimal, working example:

#include<iostream>

struct Visitor;

struct PacketBasicInterface {
    virtual void accept(Visitor &) = 0;
};

struct PacketTypeA: PacketBasicInterface {
    void accept(Visitor &) override;
};

struct PacketTypeB: PacketBasicInterface {
    void accept(Visitor &) override;
};

struct Visitor {
    void visit(PacketTypeA) {
        std::cout << "PacketTypeA" << std::endl;
    }

    void visit(PacketTypeB) {
        std::cout << "PacketTypeB" << std::endl;
    }
};

void PacketTypeA::accept(Visitor &visitor) {
    visitor.visit(*this);
}

void PacketTypeB::accept(Visitor &visitor) {
    visitor.visit(*this);
}

struct Parser {
   PacketBasicInterface * read_packet() {
       return new PacketTypeB{};
   }
};

int main() {
    Visitor visitor;
    auto *packet = Parser{}.read_packet();
    packet->accept(visitor);
    delete packet;
}
like image 21
skypjack Avatar answered Oct 27 '22 01:10

skypjack