当前位置: 首页 > 知识库问答 >
问题:

返回不同类型/类的方法的设计模式

傅志诚
2023-03-14

这是一个面向对象设计模式专家的问题。

假设我有一个解析器类,负责读取/解析数据流(携带不同类型的信息包)。每个数据包都携带不同类型的信息,因此理想情况下,我会为每种类型的数据包创建一个类(PacketTypeAPacketTypeB,…每个都有自己的接口)。

class Parser {

public:

    /* ctor */
    /* dtor */


   void read_packet(/* arguments */);

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

然后,方法Parser::read_packet遍历流,并将类(或指向类的指针或引用)返回到相应的包类型。

你会用空指针吗?一个泛型类(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(), ...
}

任何想法或反馈都将不胜感激!

非常感谢!

共有3个答案

上官思博
2023-03-14

你会用空指针吗?

没有。

一个泛型类(PacketBasicInterface)可以提供一个公共(部分)接口来查询数据包的类型(这样就可以在运行时做出任何决定),这个类怎么样?

<罢工> 这对我来说最有意义。

让我来改进一下。是的,有一个通用基类会很好。但是,在解析流以构造基类的子类型时,不要依赖if-elsetype方法。相反,使用工厂模式。让不同的工厂基于一个键构造正确的对象类型,我假设这个键可以从正在解析的数据中获得。

如果您在数据中遇到字符串“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类型对象的代码与使用factory模式制作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
   }
}
齐铭
2023-03-14

如果您正在从面向对象程序设计领域寻找设计模式,双重分派可能是一种方法。
它遵循一个最小的工作示例:

#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;
}
谷梁智
2023-03-14

这就是std::variant的用途。

我将定义一个枚举类,枚举所有可能的数据包类型:

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

并让read_packet返回packet_类型的元组和变量:

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

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

实际上不需要正式的枚举,但它可以更容易地找出如何处理变量。

这种一般方法的一些变化包括:

>

  • 使用不透明的std::string,而不是固定的枚举来指定数据包类型。

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

    与使用简单的枚举或不透明的标记(如std::string)不同,使用一个稍微简单的类来定义数据包类型,该类的方法将变量元数据作为参数,并封装可以对数据包执行的操作。

    当然,正如引用的链接所指出的,std::变体需要C 17。这将是您更新编译器的一个很好的论据:您获得了一种实现完全类型安全方法的简单方法。

  •  类似资料:
    • 我正在尝试实现和重写具有不同返回类型的方法,而不会被迫转换返回类型。 我的问题:是否可以在不强制强制转换的情况下返回不同的类型?解决这个问题的抽象方法看起来怎么样? 我认为必须有一个解决方案,因为编译器应该知道返回类型...

    • 问题内容: 。 将返回String和int值。因此,我从这些返回值中得出了以下解决方案。 建立课程通话 并进行如下更改。 现在我的问题是,有没有更简单的方法来实现这一目标? 问题答案: 最后,我认为我的方法会更好,因为当返回类型的数量增加时,这种实现会以最佳方式实现。

    • . 将返回字符串和int值。从中获取这些返回值,我想出了以下解决方案。 创建一个类调用返回值 并更改,如下所示。 现在我的问题是,有没有更简单的方法来实现这一点??

    • 我可以看到它不工作,因为我尝试了它。我只是无法解释为什么一定要这样。 第二个方法来自一个类,该类是实现第一个getValue()方法的类的子类。 为什么同名不足以覆盖该方法?我想到的唯一论点是,它将反对“是一个”关系,因为用A扩展的第二个方法的类必须具有与A相同的能力,如果你重写返回类型,你就打破了那个法律,对吧?

    • 本文向大家介绍JAVA利用泛型返回类型不同的对象方法,包括了JAVA利用泛型返回类型不同的对象方法的使用技巧和注意事项,需要的朋友参考一下 有时需要在方法末尾返回类型不同的对象,而return 语句只能返回一个或一组类型一样的对象。此时就需要用到泛型。 首先先解释个概念, 元组:它是将一组对象直接打包存储于其中的一个单一对象,这个容器对象允许读取其中元素,但不能修改。 利用泛型创建元组 测试 输出

    • 我想基于泛型值返回不同的类型。例如: 但我有一个错误: 类型字符串[]不能分配给类型T扩展Base?String[]:字符串