2011-12-21 36 views
6

鑑於以下預先存在的框架,我需要找到好的設計模式來創建派生類的不同實例。是模式創建包含許多字段本身和基類的派生類

我有主要的挑戰如下:

挑戰-1>每個類都有超過10個字段,以及如何通過這些字段派生類,然後對基類有效。

針對這個問題,我可以找出四個解決方案,但他們都沒有吸引我。

方法1>傳遞的所有參數在簡單格式

classA::classA(int field1, float field2, ..., double field29) 

=>缺點:它不產生具有的功能的好主意超過6〜7通入參數

方法2>傳遞的所有參數作爲結構

struct DataClassA 
{ 
int field1; 
float field2; 
... 
double field29; 
}; 

struct DataClassBA : DataClassA 
{ 
int m_iField30; 
// ... 
double m_iField40; 
}; 

因此,首先我通過DataClassBAclassBA,然後又通過classBA通過DataClassAclassA。 =>缺點:類型DataClassBAclassBA是類似的類型,除了一個包含操作而另一個不包含操作。另外,將結構傳遞給構造函數時,會有副本和副本的處罰。想象一下,對於每個不同的類,我們必須定義一個類似的結構以保存所有的初始化數據。類和它的相應結構之間的關鍵不同是類包含一些方法,而結構純粹用於傳輸數據。實例的每次創建會引發許多函數的調用和非常昂貴:

方法3>通過設置功能

classA 
{ 
public: 
    int Field1() const { return m_iField1; } 
    classA& Field1(int field1) 
    { 
     m_iField1 = field1; 
     return *this; 
    } 
    ... 
} 

classBA : public classA 
{ 
public: 
    int Field30() const { return m_iField30; } 
    classBA& Field30(int field30) 
    { 
     m_iField30 = field30; 
     return *this; 
    } 
    ... 
} 

=>利弊設置的所有領域。

方法4>將地圖傳遞給基類和派生類的所有構造函數。

=>缺點:我認爲這是個壞主意,儘管它使數據傳遞變得簡單。

challenge-2>基類的默認值由其不同的派生類決定。 例如,基於不同的派生類,默認值classA::m_iField2是不同的。

針對這個問題,我可以找出兩個解決方案,但沒有一個對我有吸引力。

方法1> 將默認邏輯添加到派生類本身。

方法2> 添加默認邏輯來工廠類本身。

我列出我所能想到的所有方法。但是,我還是找了清潔和專業的解決這個問題。這將是最好的,如果有,我可以用作爲參考來解決這個問題,類似一個精心編寫的API庫。 任何意見是值得歡迎的。

謝謝

/////////////////////// framework //////////////////////////////////////// 
// Note: 
// <1> the class hierarchy has to kept as this 
// <2> getter and setter functions in each class have to kept as this 
// <3> add new functions(i.e constructors) are allowed 
// <4> add new classes or structures are allowed 
///////////////////////////////////////////////////////////////////////// 
#include "stdafx.h" 
#include <map> 
#include <string> 
#include <iostream> 
using namespace std; 

/************************************************************************/ 
/* Class Name: classA (an abstract base class) 
* default value of m_iField2 is determined by its derived class 
/************************************************************************/ 
class classA 
{ 
public: 
    virtual ~classA() = 0 {} 
    // ... 
private: // 
    int m_iField1; 
    float m_iField2; // one of the potential field that has to get the default value 
    // ... 
    double m_iField29; 
}; 
/************************************************************************/ 
/* Class Name: classBA 
* If the pass-in parameters do NOT include value for the field classA::m_iField2 
* then assign its value as 200.0f 
/************************************************************************/ 
class classBA : public classA 
{ 
    // ... 
private: 
    int m_iField30; 
    // ... 
    double m_iField40; 
}; 

/************************************************************************/ 
/* Class Name: classCA 
* If the pass-in parameters do NOT include value for the field classA::m_iField2 
* then assign its value as 300.0f 
/************************************************************************/ 
class classCA : public classA 
{ 
    // ... 
private: 
    int m_iField50; 
    // ... 
    int m_iField60; 
}; 

int main(int argc, char* argv[]) 
{ 
    map<string, string> mapStrsBA; 
    mapStrsBA["name"] = "classBA"; 
    mapStrsBA["field1"] = "5"; 
    // ... 
    mapStrsBA["field40"] = "1.89"; 
    // pass mapStrsBA to a factory class with function to create a new instance of class classBA 

    map<string, string> mapStrsCA; 
    mapStrsBA["name"] = "classCA"; 
    mapStrsBA["field1"] = "6"; 
    // ... 
    mapStrsBA["field60"] = "19"; 
    // pass mapStrsCA to a factory class with function to create a new instance of class classCA 
    return 0; 
} 
+1

你對#2的否定不一定是正確的。考慮使用'DataForA','DataForB'和'DataForAAndB:DataForA,DataForB'類。然後,'A'可以有一個DataForA類型的數據成員,'B'可以有一個'DataForB'類型的數據成員。沒有重複,你得到'命名參數'。 – 2011-12-21 04:47:00

回答

2

對於挑戰1,我認爲方法2是更好的。我不知道你的缺點是什麼意思。即使您通過參數值,你仍然需要複製值 階級成員。 Struct只會讓你的構造器變得簡單。我認爲你不需要繼承。如何:

struct DataClassA 
{ 
    int field1; 
    float field2; 
    ... 
    double field29; 
}; 

struct DataClassBA 
{ 
    DataClassA a; 
    int m_iField30; 
    // ... 
    double m_iField40; 

}; 

對於挑戰2,我認爲你可以在數據結構中設置默認值。如果您不需要默認值,則更改該值。例如:

DataClassA::DataClassA() 
{ 
    field1 = 1; 
} 

DataClassBA::DataClassBA() 
{ 
    a.filed1 = 2; 
} 
+0

我已經更新了我的重複意義。 – q0987 2011-12-21 05:52:17

1

對於第一個挑戰,我會推薦方法3,聲明你一套方法爲內聯(編譯器可以反正做到這一點,但它絕不會傷害給它一個提示)。此外,它似乎在這樣的情況下,無需返回值,我會寫:

inline void Field30(int field30) 
{ 
    m_iField30 = field30; 
} 

這是在其上構建其他方法一個合理的起點,因爲他們是在一個簡單的獲取所有在很大程度上擴展/設置模式。您所列出的騙子在我看來,過早的優化。

挑戰2很容易通過聲明在接受默認值作爲參數的基類公共或受保護的構造解決,並且調用該構造在派生類的初始化列表中,像這樣:

class Base 
{ 
private: 

    int Value; 

protected: 

    Base(int value) : 
     Value(value) 
    { 
     // Do nothing. 
    } 
}; 

class Derived : public Base 
{ 
public: 

    // A default constructor, that sets the base class default value. 
    Derived() : 
     Base(5) 
    { 
     // Do nothing. 
    } 
}; 

通過聲明構造函數爲protected並且不提供默認值,還可以防止未由派生類繼承的基類的實例被創建。

7

讓我添加一個方法5到你的挑戰1,這並不總是,但經常適用,尤其如果你碰巧有許多領域:查找其嚴格屬於同一成員字段,並把它們收集到邏輯對象。例如,假設您有一個Shape類,如下所示:

class Shape 
{ 
public: 
    Shape(pass initial values for all member variables); 
    // ... 
private: 
    // bounding box coordinates 
    int xmin, xmax; 
    int ymin, ymax; 
    // color 
    int red, green, blue; 
    int alpha; 
    // center point (for rotations) 
    int cx, cy; 
}; 

這是10個變量。但是,這些並不是毫無關聯的。他們大多數是x/y對,然後有一組指定顏色。因此,你可以按照如下改寫這個:

struct Point 
{ 
    int x, y; 
    Point(int ax, int ay): x(ax), y(ax) {} 
}; 

struct Color 
{ 
    int red, green, blue, alpha; 
    Color(int r, int g, int b, int a): red(r), green(g), blue(b), alpha(a) {} 
}; 

class Shape 
{ 
public: 
    // ... 
private: 
    // bounding box 
    Point lower_left, upper_right; 
    Color color; 
    Point center; 
}; 

現在突然間,你只需要四個變量以繞過。您甚至可以考慮製作一個由兩個角點組成的矩形類型,並將其用於邊界框,從而將變量數量進一步減少到3.

請注意,不僅要通過的參數數量減少,增加清晰度。

現在既然你沒有給出關於你的類的任何細節,我也不能說這樣的邏輯分組對於你的類是否可能,但是鑑於你提到的大量參數,如果不是,我會感到驚訝。


至於挑戰2,我會考慮它更好地默認的邏輯添加到派生類。

+0

對於如何縮小傳入參數,您已經給出了一個體面的想法,並且該示例非常清晰。然而,正如你所表明的,我的班級成員不容易分爲一兩個大組。如果我採納你的想法,我必須介紹太多不同的結構,每個結構都只限於有限的成員。在這種情況下,我寧願將所有成員分成一個結構。關於我的課程結構如何,我們可以假設成員變量之間沒有直接的關係。謝謝 – q0987 2012-01-11 18:15:33

+0

+1:@ q0987:所有這10個字段不可能是相等的獨立。這意味着非常複雜的邏輯,即使通過人工分組,也需要進行一些簡化。這樣的分組可以幫助接收更簡單,更可維護的實現。 – 2012-01-12 16:50:32

0

挑戰1: 我會去的方法2,你應該把所有的領域爲一體的結構,並初始化僅是相關的特定構造領域。

挑戰2:

它在派生類中實現。

下面的例子演示了上述決定:

#include <string> 
#include <iostream> 
using namespace std; 

struct InitializationData 
{ 
    // for the base class 
    int m_iField1; 
    //float m_iField2; // one of the potential field that has to get the default value 
    // ... 
    double m_iField29; 

    // for the classBA 
    int m_iField30; 
    // ... 
    double m_iField40; 

    // for the classCA 
    int m_iField50; 
    // ... 
    int m_iField60; 
}; 

class classA 
{ 
public: 
    classA(const InitializationData &data, 
      const float m_iField2_): 
     m_iField1(data.m_iField1), 
     m_iField2(m_iField2_), 
     m_iField29(data.m_iField29) 
    { 
    } 
    virtual ~classA() {} 
    // ... 
private: // 
    int m_iField1; 
    float m_iField2; // one of the potential field that has to get the default value 
    // ... 
    double m_iField29; 
}; 

class classBA : public classA 
{ 
public: 
    classBA(const InitializationData &data): 
     classA(data, 0.1), 
     m_iField30(data.m_iField30), 
     m_iField40(data.m_iField40) 
    { 
    } 
    virtual ~classBA() {} 
private: 
    int m_iField30; 
    // ... 
    double m_iField40; 
}; 

class classCA : public classA 
{ 
public: 
    classCA(const InitializationData &data): 
     classA(data, 0.3), 
     m_iField50(data.m_iField50), 
     m_iField60(data.m_iField60) 
    { 
    } 
    virtual ~classCA() {} 
private: 
    int m_iField50; 
    // ... 
    int m_iField60; 
}; 

classA* Create(const InitializationData &data, const int type) 
{ 
    switch (type) 
    { 
     case 0 : 
      return new classBA(data); 
      break; 
     case 1 : 
      return new classCA(data); 
      break; 
     default : 
      ; 
    } 

    return nullptr; 
} 

int main(int argc, char* argv[]) 
{ 
    InitializationData data; 

    // initialize data 
    data.m_iField1 = 1; 
    data.m_iField29 = 1; 

    data.m_iField30 = 30; 
    data.m_iField40 = 40.0; 

    data.m_iField50 = 50; 
    data.m_iField60 = 60; 

    // create the object of specific type 
    auto obj = Create(data, 1); 
    // use obj 
} 
0

去想想這個問題並不比結構和類更抽象的術語。你在這裏做的是將配置(或者如果你喜歡的初始化參數)傳遞給對象。您可以將它們作爲單獨的參數(方法1),單個大型配置對象(方法2)或多個專用配置集(celtschk解決方案)傳遞。您可以使用Builder模式來幫助您創建配置對象。

如果可能的話,最好是通過配置在構造,因爲它允許將部件限定爲const,從而限制了可能的狀態的對象可以是在(因此避免方法3)。總是明確定義參數,代碼更容易理解和重構(所以避免方法4)。如果你有一個選擇,那麼支持組合而不是繼承(就像Shawnone也建議的那樣)。

1

我想用「編譯防火牆」,使對象字段從業務對象。

struct classAObject 
{ 
    int field1; 
    //... 
    int field20; 
}; 
struct classBObject : public classAObject 
{ 
    int field30; 
    //... 
    int field50; 
}; 

class classA 
{ 
    public: 
    classA(classAObject* impl) {pImplA = impl;} 
    private: 
    classAObject* pImplA; 
}; 
class classB : public classA 
{ 
    public: 
    classB(classBObject* impl):classA(impl) {pImplB = impl;} 
    private: 
    classBObject* pImplB; 
}; 

那麼你可以使用它。

1

我先回答容易。 ;)對於挑戰#2,您應該在每個派生類中實現默認邏輯。這樣做的主要原因是您稍後可以添加具有不同邏輯的新派生類,而不必觸摸除派生類本身之外的任何代碼。它封裝了你的邏輯。

對於挑戰#1 ...如果是我的話,我會創建一個包含基類中的值的結構。然後,我爲派生類創建一個結構,其中只包含特定於派生類的字段以及父結構類型的一個元素。

struct BaseStruct { 
    int BaseValue; 
} 
struct DerivedA { 
    BaseStruct BaseData; 
    int DerivedField; 
} 

您可以像繼承層次結構一樣深入地繼續嵌套。在構造函數中,將結構的BaseData傳遞給基類,然後處理結構的類特定元素。同樣,這會對代碼進行分區,以便在派生類中只存在特定於派生類的邏輯,派生類包含特定於該派生類的所有邏輯。