鍍金池/ 問答/C++  Linux  HTML/ 如何傳入一個(gè)類需要使用其他類里面的同一個(gè)類型的值?

如何傳入一個(gè)類需要使用其他類里面的同一個(gè)類型的值?

有個(gè)User類,其方法calculate()的內(nèi)部需要使用到外部數(shù)據(jù)類的實(shí)例中的某一個(gè)成員值作為輸入源。在User構(gòu)造函數(shù)需要傳入不同數(shù)據(jù)實(shí)例作為參數(shù),根據(jù)傳入不同的數(shù)據(jù)實(shí)例分別創(chuàng)建不同的 User 類。

先來看下A和B兩個(gè)數(shù)據(jù)類,結(jié)構(gòu)和層次不同,但是都有 Rate 這個(gè)值

struct Rate{
    int value; // 被用于計(jì)算的值
};

namespace A{
struct Data{
    struct Info{
        int i1,i2;
        Rate rate = {1};
    };
    Data() { info = std::make_shared<Info>();}
    std::shared_ptr<Info> info;
    int aa, ab, ac; //不會被User 類用到的值
};}

namespace B{
struct Data{
    Rate rate = {2};
    int ba, bb, bc; //不會被User 類用到的值
};} 

下面是創(chuàng)建User類,傳入不同的 Data 類型構(gòu)建出不同的 User 實(shí)例

User類 example 1:

因?yàn)?User 每次只會接受一種Data類作為計(jì)算,所以可以在構(gòu)造函數(shù)中傳入 不同Data類的指針來判斷,使用指針不為空的類實(shí)例去做計(jì)算。在calculate()中,需要編寫不同的代碼來訪問 不同Data 類中的 同一個(gè)rate 值。
這個(gè)代碼看上去會非常惡心,因?yàn)楦鶕?jù)不同的類型需要不同的代碼,如果有3~4種不同的 Data 類,calculate()就需要不同的 if 分支。

class User1
{
public:
    User1(std::shared_ptr<A::Data> a, std::shared_ptr<B::Data> b){
        a_ = a; b_ = b;
    }

    int calculate(){
        int result = 0;
        if (a_) {
            result = a_->info->rate.value * 10;
        }else if(b_){
            result = b_->rate.value * 10;
        }
        return result;
    }

    std::shared_ptr<A::Data> a_;
    std::shared_ptr<B::Data> b_;
};

int main()
{
    User1 *u1a = new User1(std::make_shared<A::Data>(), nullptr);
    u1a->calculate();
    User1 *u1b = new User1(nullptr, std::make_shared<B::Data>());
    u1b->calculate();
}

User類 example 2:

能不能用C++模板的方式傳入不同的 Data 呢?
思路:不管哪一種Data類,都會有 rate 這邊成員變量,只是調(diào)用的層次不一樣,那可不可以把前面的類型作為模板參數(shù)呢?
嘗試了一下,代碼如下,但是感覺還是不夠好。

template<class T=B::Data>
class User2
{
public:
    User2(std::shared_ptr<T> data){
        data_ = data;
    }

    int calculate()
    {
      int result = data_->rate.value * 10;
      return result;
    }

    std::shared_ptr<T> data_;
};

int main()
{
    // DataA 就傳入 A::Data::Info作為模板參數(shù)
    auto u2a = new User2<A::Data::Info>(std::make_shared<A::Data::Info>());
    u2a->calculate();
    // DataB 就傳入 B::Data作為模板參數(shù)
    auto u2b = new User2<B::Data>(std::make_shared<B::Data>());
    u2b->calculate();
}

User類 example 3:
既然 User 類是需要一個(gè) rate 值,那可不可以在構(gòu)造函數(shù)的時(shí)候傳入一個(gè)函數(shù)對象呢,在calculate()中調(diào)用這個(gè)函數(shù)對象,獲取到不同 Data 實(shí)例的 rate 值。

//把user需要的輸入值用一個(gè)函數(shù)對象傳入,user就不需要關(guān)心這個(gè)值從哪里來的了
using getRateFunc = std::function<int()>; 
class User3
{
  public:
    User3(getRateFunc getRate)
    {
        getRate_ = getRate;
    }

    int calculate()
    {
        int result = getRate_() * 10;
        return result;
    }

    getRateFunc getRate_;
};

int main()
{
    //用匿名函數(shù)傳入U(xiǎn)ser類需要的值
    auto a = std::make_shared<A::Data>();
    auto u3a = new User3([a](){
        return a->info->rate.value;
    });
    u3a->calculate();

    auto b = std::make_shared<B::Data>();
    auto u3b = new User3([b]() {
        return b->rate.value;
    });
    u3b->calculate();
}

最后的問題

注意的是Data類因?yàn)槭羌償?shù)據(jù)類,是不可以繼承接口的。
有沒有比較好的編程范式去解決這種場景下的問題呢?
另外3個(gè)例子中哪個(gè)比較可取呢?

回答
編輯回答
逗婦惱

example 3 是擴(kuò)展性比較好的寫法。說擴(kuò)展性好是指在增加新的 Data 類型時(shí)不要改動任何代碼。第二種模板的方式要求 Data 類型必須具有一致的結(jié)構(gòu)才可以,太脆弱了。
example 3 的問題是對使用者不友好,每次調(diào)用都要寫 lambda, 如果有大量的調(diào)用就顯得代價(jià)過高了??梢酝ㄟ^提取輔助函數(shù)的方式解決(或者寫成 User 類的構(gòu)造函數(shù)),每增加一個(gè) Data 類型就對應(yīng)增加這樣一個(gè)輔助函數(shù)(或者構(gòu)造函數(shù))
當(dāng)然,以上只適合示例代碼中這種簡單邏輯。實(shí)際項(xiàng)目中可能復(fù)雜的多,比如要訪問十幾種 Data 類型中的十幾個(gè)成員,這時(shí)候就有必要加一個(gè)抽象層了,所有的 User 類從一個(gè)公共的接口繼承下來,每種或者每幾種 Data 類型對應(yīng)一種實(shí)現(xiàn)?;蛘甙殉橄蠼涌诜旁趦?nèi)部,User 類作為對該抽象接口調(diào)用的一個(gè)包裝類,這種方式的好處是抽象層在內(nèi)部可以隨時(shí)修改。
具體怎么做只能具體問題具體分析了。

2018年3月10日 00:08