簡體   English   中英

返回不同類型/類的方法的設計模式

[英]Design pattern for method returning different types/classes

這是對象設計模式專家的問題。

假設我有一個Parser類負責讀取/解析數據流(攜帶不同類型的信息 )。 這些數據包中的每一個都攜帶不同類型的信息,因此理想情況下,我會為每種類型的數據包( PacketTypeAPacketTypeB ,......每個都有自己的接口)提供一個類。

class Parser {

public:

    /* ctor */
    /* dtor */


   void read_packet(/* arguments */);

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

然后,方法Parser::read_packet將遍歷流並將類(或指針或對類的引用)返回到適當的數據包類型。

你會使用void指針嗎? 一個泛型類( PacketBasicInterface )如何提供一個公共(部分)接口來查詢數據包的類型(以便隨后可以在運行時做出任何決定)?

// 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(), ...
}

任何想法或反饋將非常感謝!

非常感謝!

這就是std :: variant的用途。

我將定義一個枚舉類,枚舉所有可能的數據包類型:

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

並讓read_packet返回read_packet和變量的元組:

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

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

不需要正式的枚舉,但它更容易弄清楚如何處理變體。

這種一般方法的一些變化包括:

  1. 使用不透明的std::string而不是固定的枚舉來指定數據包類型。

  2. 使用std::any而不是正式的std::variant

  3. 不使用簡單的枚舉或像std::string這樣的不透明標記,而是使用稍微不平凡的類來定義數據包類型,類的方法將變量元數據作為參數,並封裝可以執行的操作在包上。

當然,如引用鏈接中所述, std::variant需要C ++ 17。 對於您更新編譯器來說,這將是一個很好的理由:您可以通過一種簡單的方法來實現完全類型安全的方法。

你會使用void指針嗎?

沒有。

一個泛型類(PacketBasicInterface)如何提供一個公共(部分)接口來查詢數據包的類型(以便隨后可以在運行時做出任何決定)?

這對我來說最有意義。

讓我改進一下。 是的,擁有通用基類會很好。 但是,在解析流以構造基類的子類型時,不要依賴於if-else類型方法。 相反,使用工廠模式。 讓各個工廠基於一個密鑰構造正確的對象類型,我假設它可以從被解析的數據中獲得。

如果在數據中遇到字符串"PacketTypeA" ,您可能希望PacketTypeAFactory負責構造對象。

FWIW,這種方法可以擴展到基類的許多子類型。 我們在工作中使用這種方法,它已經為我們服務了二十多年。


這是我想到的代碼庫的骨架結構:


課程。

class PacketBasicInterface { };

class PacketTypeA : public PacketBasicInterface { };

class PacketTypeB : public PacketBasicInterface { };

工廠的界面。

// PacketFactory.h
class PacketFactory
{
   public:

      static PacketBasicInterface* makePacket(std::string const& packetData);

      static void registerFactory(std::string const& key, PacketFactory* factory);

      virtual PacketBasicInterface* make(std::string const& packetData) = 0;

      virtual ~PacketFactory() {}
};

實現使工廠工作的框架。

// PacketFactory.cpp

#include "PacketFactory.h"

namespace PacketBasicInterface_Impl
{
   using PacketFactoryMap = std::map<std::string, PacketFactory*>;

   PacketFactoryMap& getPacketFactoryMap()
   {
      static PacketFactoryMap theMap;
      return theMap;
   }
};

uisng namespace PacketBasicInterface_Impl;

PacketBasicInterface* PacketFactory::makePacket(std::string const& packetData)
{
   std::string key = extractKey(packetData);
   PacketFactoryMap& theMap = getPacketFactoryMap();
   PacketFactoryMap::iterator iter = theMap.find(key);
   if ( iter == theMap.end() )
   {
      return nullptr;
   }

   return iter->second->make(packetData);
}

void registerFactory(std::string const& key, PacketFactory* factory)
{
   getPacketFactoryMap()[key] = factory;
}

使用工廠模式制作PacketTypeA類型對象的代碼。

// PacketTypeAFactory.cpp

#include "PacketFactory.h"
#include "PacketTypeA.h"

class PacketTypeAFactory : public PacketFactory
{
   public:

      virtual PacketBasicInterface* make(std::string const& packetData)
      {
         PacketTypeA* packet = new PacketTypeA();

         // Flesh out packet with data pulled from packetData
         // ...
         //

         return packet;
      }

      struct Initializer
      {
         Initializer() { PacketFactory::registerFactory("PacketTypeA", new PacketTypeAFactory); }
      };
};


// Constructing this object at static initialization time makes sure
// that PacketTypeAFactory is registered with PacketFactory when the
// stream data need to be parsed.
static PacketTypeAFactory::Initializer initializer;

用於制作PacketTypeB類型的對象的代碼與使用工廠模式制作PacketTypeA類型的對象的代碼非常相似。

// PacketTypeBFactory.cpp


#include "PacketFactory.h"
#include "PacketTypeB.h"

class PacketTypeBFactory : public PacketFactory
{
   public:

      virtual PacketBasicInterface* make(std::string const& packetData)
      {
         PacketTypeA* packet = new PacketTypeA();

         // Flesh out packet with data pulled from packetData
         // ...
         //

         return packet;
      }

      struct Initializer
      {
         Initializer() { PacketFactory::registerFactory("PacketTypeB", new PacketTypeBFactory); }
      };
};


// Constructing this object at static initialization time makes sure
// that PacketTypeBFactory is registered with PacketFactory when the
// stream data need to be parsed.
static PacketTypeBFactory::Initializer initializer;

客戶代碼。

std::string packetData;
while ( getPacketData(packetData) )
{
   PacketBasicInterface* packet = PacketFactory::makePacket(packetData);
   if ( packet == nullptr )
   {
      // Deal with error.
   }
   else
   {
      // Use packet
   }
}

如果您正在尋找面向對象編程領域的設計模式,那么雙重調度可能是您的選擇。
它遵循一個最小的工作示例:

#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;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM