【问题标题】:Reducing duplicate code for function implementation减少功能实现的重复代码
【发布时间】:2022-01-19 12:13:26
【问题描述】:

不幸的是,我遇到了重复源代码的问题。 这是一个小例子来说明我的问题:

class cPlayer
{
public:

    struct Properties
    {
        std::vector<cStreet*>    Streets;
        std::vector<cHouse*>     Houses;
        std::vector<cComputer*>  Computers;
        std::vector<cBook*>      Book;
    };

    cPlayer(std::string name) : m_name{name}{};
    ~cPlayer(){};
    std::string         m_name{};
    Properties          m_Properties;
    
    // function overloaded
    void buy(cStreet& Street);
    void buy(cHouse& House);
    void buy(cComputer& Computer);
    void buy(cBook& Book);
};

void cPlayer::buy(cStreet& Street)
{
    std::cout << m_name.c_str() << " : Do you want buy this Street?" << std::endl;
    //Todo: Decision (here yes)
    m_Properties.Streets.push_back(&Street);
};

void cPlayer::buy(cHouse& House)
{
    std::cout << m_name.c_str() << " : Do you want buy this House?" << std::endl;
    //Todo: Decision (here yes)
    m_Properties.Houses.push_back(&House);
};

void cPlayer::buy(cComputer& PC)
{
    std::cout << m_name.c_str() << " : Do you want buy this PC?" << std::endl;
    //Todo: Decision (here yes)
    m_Properties.Computers.push_back(&PC);
};

void cPlayer::buy(cBook& Book)
{
    std::cout << m_name.c_str() << " : Do you want buy this Book?" << std::endl;
    //Todo: Decision (here yes)
    m_Properties.Book.push_back(&Book);
};

所以 4 个成员函数 buy() 实际上都有相同的逻辑。但是,会输出单个文本,并且始终使用单个 std::vector。当然,只实现一次功能会更加优雅。但是怎么做? 我只想到模板,但是如何切换正确的vector()来保存属性?

一个问题接一个问题。如果我能深思熟虑就好了,因为这样的“问题”经常出现在我的源代码中。

谢谢!

【问题讨论】:

标签: c++


【解决方案1】:

** A ** 这里的解决方案是使用继承。例如

#include <string>
#include <vector>
#include <iostream>

class Property
{
public:
    virtual char const* Name() const = 0;
};

class Street : public Property
{
private:
    static constexpr auto name {"street"};
public:
    char const* Name() const override
    {
        return name;
    }
};


//... etc

class Player
{
    std::vector<Property const*> properties;
public:
    void buy(Property const& property) {
        std::cout << /*...*/ " : Do you want to buy this "
            << property.Name()
            << "?\n";
        // if (yes)
        properties.push_back(&property);
    }
};

int main() {
    Player me {};
    const Street street {};

    me.buy(street);
}

【讨论】:

  • 感谢您的提示/想法。但这意味着我在单个 std::vector 中管理所有属性,对吗?换句话说,如果我只是想稍后出售书籍(可能通过 sell() 函数),我首先必须在这个 std::vector 中查找书籍类型的所有对象,对吗?实际上有没有办法在 C++ 中做到这一点,甚至不保存类 cBook、cHouse 等的类型,例如以枚举的形式?
  • @ThomasAlvaEdison 这就是为什么我说它是 A 解决方案。有许多。这完全取决于应用程序。顺便说一句,您接受的解决方案过于复杂。复杂的代码更难维护,也容易出错。通常越简单越好。
【解决方案2】:

一种可能的方法是使用模板std::map。特别是,通过将buy 成员函数设为成员函数模板,如下所示:

方法一

步骤 1

创建std::map

    std::map<std::type_index, void*> myMap{{typeid(cStreet*), &m_Properties.Streets}, 
                                           {typeid(cHouse*), &m_Properties.Houses}, 
                                           {typeid(cComputer*), &m_Properties.Computers}, 
                                           {typeid(cBook*), &m_Properties.Book}};

第 2 步

在类cPlayer内添加成员函数模板buy&lt;&gt;的声明

template<typename T> void buy(T& Arg);

步骤 3

实现buy&lt;&gt;

template<typename T> void cPlayer::buy(T& Arg) // - STEP 3
{
    std::cout << m_name.c_str() << " : Do you want buy this ?" <<typeid(Arg).name() << std::endl;
    //Todo: Decision (here yes)
    (*static_cast<std::vector<decltype(&Arg)>*>(myMap.at(typeid(&Arg)))).push_back(&Arg);
}

整体修改

所以modified code 将如下所示:

class cPlayer
{
public:

    struct Properties
    {
        std::vector<cStreet*>    Streets;
        std::vector<cHouse*>     Houses;
        std::vector<cComputer*>  Computers;
        std::vector<cBook*>      Book;
    };
    
    cPlayer(std::string name) : m_name{name}{};
    ~cPlayer(){};
    std::string         m_name{};
    Properties          m_Properties;
    
    //create std::map -                             STEP 1
    std::map<std::type_index, void*> myMap{{typeid(cStreet*), &m_Properties.Streets}, 
                                           {typeid(cHouse*), &m_Properties.Houses}, 
                                           {typeid(cComputer*), &m_Properties.Computers}, 
                                           {typeid(cBook*), &m_Properties.Book}};
    
    //create member function template -             STEP 2
    template<typename T> void buy(T& Arg);
    
};
template<typename T> void cPlayer::buy(T& Arg) // - STEP 3
{
    std::cout << m_name.c_str() << " : Do you want buy this ?" <<typeid(Arg).name() << std::endl;
    //Todo: Decision (here yes)
    (*static_cast<std::vector<decltype(&Arg)>*>(myMap.at(typeid(&Arg)))).push_back(&Arg);
}

方法二

This 使用std::any 作为mapped_typestd::map

class cPlayer
{
public:

    struct Properties
    {
        std::vector<cStreet*>    Streets;
        std::vector<cHouse*>     Houses;
        std::vector<cComputer*>  Computers;
        std::vector<cBook*>      Book;
    };
    
    cPlayer(std::string name) : m_name{name}{};
    ~cPlayer(){};
    std::string         m_name{};
    Properties          m_Properties;
    
   //create std::map -                             STEP 1 
   std::map<std::type_index, std::any> myMap{ {typeid(cStreet*), std::ref(m_Properties.Streets)}, 
                                              {typeid(cHouse*), std::ref(m_Properties.Houses)}, 
                                              {typeid(cComputer*), std::ref(m_Properties.Computers)}, 
                                              {typeid(cBook*), std::ref(m_Properties.Book)}
};   
    //create member function template -             STEP 2
    template<typename T> void buy(T& Arg);
    
};

    
template<typename T> void cPlayer::buy(T& Arg) {// - STEP 3
  
     std::cout << m_name.c_str() << " : Do you want buy this ?" <<typeid(Arg).name() << std::endl;
     std::any_cast<std::reference_wrapper<std::vector<T*>>>(myMap.at(typeid(T*))).get().push_back(&Arg);     
}

【讨论】:

    【解决方案3】:

    如果你喜欢你可以重构它

    void cPlayer::buy(cStreet& Street)
    {
         buy_impl(Street,m_Properties.Streets,"some text");
    }
    

    其他类似的,有

    template <typename T,typename U>
    void buy_impl(const T& t, U& prop,const std::string& message) {
        std::cout << message;
        U.push_back(&t);
    }
    

    不过,不确定增加的复杂性是否值得节省一点点。您的方法已经包含的只是它们之间的不同之处。

    【讨论】:

      【解决方案4】:

      这真的不是代码重复,因为每种属性的代码都略有不同。它相似,但不完全相同。

      可以将部分代码移至单独的辅助函数并将buy 代码移至

      void cPlayer::buy(cStreet& Street)
      {
          if (ConfirmPurchase("Street"))
             m_Properties.Streets.push_back(&Street);
      };
      

      但仅此而已,IMO。不要让代码过于复杂只是以减少重复。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-01-04
        • 2021-03-13
        • 1970-01-01
        • 2011-09-04
        • 1970-01-01
        • 1970-01-01
        • 2019-09-30
        相关资源
        最近更新 更多