2016-07-27 68 views
3

現在我正在開發一個用於識別照片中的對象的類,並且此類由多個組件(類)組成。例如,這是一個使用抽象工廠模式的例子嗎?

class PhotoRecognizer 
{ 
public: 
    int perform_recogniton() 
    { 
     pPreProcessing->do_preprocessing(); 
     pFeatureExtractor->do_feature_extraction(); 
     pClassifier->do_classification() 
    } 

    boost::shared_ptr<PreProcessing> pPreProcessing; 
    boost::shared_ptr<FeatureExtractor> pFeatureExtractor; 
    boost::shared_ptr<Classifier> pClassifier; 

} 

在這個例子中,當我們使用這個類來進行識別,我們調用其他類PreProcessingFeatureExtractorClassifier。正如你可以想象的那樣,有很多不同的方法來實現每個類。例如,對於Classifier類,我們可以使用SVMClassfierNeuralNetworkClassifer,它是基本類Classifier的派生類。

class SVMClassifier: public Classifier 
{ 
public: 
    void do_classification(); 

}; 

因此,通過使用PhotoRecognizer類中的不同元素,我們可以創建不同種PhotoRecongnizer。現在,我正在構建一個基準,以瞭解如何將這些元素結合在一起以創建最佳PhotoRecognizer。一個解決方案,我能想到的就是用抽象工廠:

class MethodFactory 
{ 
public: 
     MethodFactory(){}; 
     boost::shared_ptr<PreProcessing> pPreProcessing; 
     boost::shared_ptr<FeatureExtractor> pFeatureExtractor; 
     boost::shared_ptr<Classifier> pClassifier; 

}; 
class Method1:public MethodFactory 
{ 
    public: 
    Method1():MethodFactory() 
    { 
      pPreProcessing.reset(new GaussianFiltering); 
      pFeatureExtractor.reset(new FFTStatictis); 
      pClassifier.reset(new SVMClassifier); 

     } 

}; 

class Method2:public MethodFactory 
{ 
    public: 
    Method1():MethodFactory() 
    { 
      pPreProcessing.reset(new MedianFiltering); 
      pFeatureExtractor.reset(new WaveletStatictis); 
      pClassifier.reset(new NearestNeighborClassifier); 

     } 

}; 



class PhotoRecognizer 
    { 
    public: 
     PhotoRecognizer(MethodFactory *p):pFactory(p) 
     { 
     } 
     int perform_recogniton() 
     { 
      pFactory->pPreProcessing->do_preprocessing(); 
      pFactory->pFeatureExtractor->do_feature_extraction(); 
      pFactory->pClassifier->do_classification() 
     } 

     MethodFactory *pFactory; 


    } 

所以當我用方法一進行照片的認可,我可以簡單地做到以下幾點:

Method1 med; 
PhotoRecognizer recogMethod1(&med); 
med.perform_recognition() 

更進一步,我甚至可以使類PhotoRecognizer更緊湊:

enum RecMethod 
{ 
    Method1, Method2 

}; 

class PhotoRecognizer 
{ 
public: 
    PhotoRecognizer(RecMethod) 
    { 
     switch(RecMethod) 
     { 
      case Method1: 
      pFactory.reset(new Method1()); 
      break; 
      ... 
     } 
    } 

    boost::shared_ptr<MethodFactory> pFactory; 

}; 

因此,這裏是我的問題:是抽象工廠設計模式有充分理由在上述的情況呢?有其他解決方案嗎?謝謝。

+0

這與C++無關,爲什麼標記在那裏? – Ceros

+0

@Ceros演示代碼在C++ – feelfree

+0

但你的問題是關於代碼設計而不是C++ – Ceros

回答

2

由於經常沒有最終的「正確」的方法來做到這一點,和答案很大程度上取決於項目將如何使用。所以如果只是爲了快速測試,一次完成並且永不回頭 - 如果這是你內心的願望,那就繼續使用枚舉吧,沒有人會阻止你。但是,如果您打算隨着時間的推移擴展可能的方法,我會勸阻使用枚舉的第二種方法。原因是:每次你想添加一個新的方法時,你必須改變PhotoRecognizer類,所以你必須閱讀代碼,記住它正在做什麼,如果有人應該這樣做 - 這將需要更多的時間。

與枚舉的設計違反的固體(https://en.wikipedia.org/wiki/SOLID_(object-oriented_design))兩個第一規則:

  1. 開閉原理(OCP):PhotoRecognizer類不能擴展(添加的新方法),而不其代碼的修改。
  2. 單責任原則(SRP):PhotoRecognizer班級不僅承認照片,而且還充當方法的工廠。

你的第一種方法比較好,因爲如果你需要定義另一個Method3你可以把它變成你的PhotoRecognizer,並用它在不改變類的代碼:

//define Method3 somewhere 
Method3 med; 
PhotoRecognizer recogMethod3(&med); 
med.perform_recognition() 

我不喜歡關於你的方法,就是對於每一種可能的組合,你必須寫一個班級(MethodX),這可能會導致很多無聊的工作。我會做到以下幾點:

struct Method 
{ 
    boost::shared_ptr<PreProcessing> pPreProcessing; 
    boost::shared_ptr<FeatureExtractor> pFeatureExtractor; 
    boost::shared_ptr<Classifier> pClassifier; 
}; 

Method如爲不同的算法插槽的集合,它在這裏,因爲可以很方便地通過處理/提取/分級這樣。

人們可以使用一個工廠函數:

enum PreprocessingType {pType1, pType2, ...}; 
    enum FeatureExtractorType {feType1, feType2, ..}; 
    enum ClassifierType {cType1, cType2, ... }; 

    Method createMethod(PreprocessingType p, FeatureExtractionType fe, ClassifierType ct){ 
     Method result; 
     swith(p){ 
      pType1: result.pPreprocessing.reset(new Type1Preprocessing()); 
        break; 
      .... 
     } 
     //the same for the other two: fe and ct 
     .... 
     return result 
    } 

也許你會問:「可是怎麼樣OCP」 - 你會是對的!必須更改createMethod以添加其他(新)類。這對你來說可能並不太舒服,你仍然可以手動創建一個Method對象,用新類初始化這些字段並將它傳遞給一個PhotoRecognizer構造器。

但隨着C++,您在您的處置一個強大的工具 - 模板:

template < typename P, typename FE, typename C> 
    Method createMethod(){ 
     Method result; 
     result.pPrepricessing.reset(new P()); 
     result.pFeatureExtractor.reset(new FE()); 
     result.pClassifier.reset(new C()); 
     return result 
    } 

而且你可以自由選擇你想要的任何組合,而無需修改代碼:

//define P1, FE22, C2 somewhere 
Method medX=createMethod<P1, FE22, C2>(); 
PhotoRecognizer recogMethod3(&med); 
recogMethod3.perform_recognition() 

有是另一個問題:如果類PreProcessingA不能與類ClassifierB一起使用會怎麼樣?此前,如果沒有類MethodAB沒有人可以使用它,但現在這個錯誤是可能的。

爲了解決這個問題,特徵可以用:

template <class A, class B> 
struct Together{ 
    static const bool can_be_used=false; 

template <> 
struct Together<class PreprocessingA, class ClassifierA>{ 
    static const bool can_be_used=true; 
} 

template < typename P, typename FE, typename C> 
Method createMethod(){ 
    static_assert(Together<P,C>::can_be_used, "classes cannot be used together"); 
     Method result; 
     .... 
} 

結論

這種方法具有以下優點:

  1. SRP,即PhotoRecognizer - 只承認,Method - 僅捆綁算法部分和createMethod - 僅創建一個方法。
  2. OCP,即我們可以在不更改其他類/函數的代碼的情況下添加新算法
  3. 由於性狀,我們可以在編譯時檢測到部分算法的錯誤組合。
  4. 沒有樣板代碼/沒有代碼重複。

PS:

你可能會說,爲什麼不劃傷整個Method類?人們也可以使用:

template < typename P, typename FE, typename C> 
    PhotoRecognizer{ 
     P preprocessing; 
     FE featureExtractor; 
     C classifier; 
     ... 
    } 

    PhotoRecognizer<P1, FE22, C2> recog(); 
    recog.perform_recognition(); 

是的,這是真的。這種替代方案有一些優點和缺點,必須更多地瞭解項目才能做出正確的折衷。但默認情況下,我會採用更多SRP原則兼容方法將部分算法封裝到Method類中。

0

除非你是重用MethodFactory,我提出以下建議:

struct Method1 { 
    using PreProcessing_t = GaussianFiltering; 
    using FeatureExtractor_t = FFTStatictis; 
    using Classifier_t = SVMClassifier; 
}; 

class PhotoRecognizer 
{ 
public: 
    template<typename Method> 
    PhotoRecognizer(Method tag) { 
     pPreProcessing.reset(new typename Method::PreProcessing_t()); 
     pFeatureExtractor.reset(new typename Method::FeatureExtractor_t()); 
     pClassifier.reset(new typename Method::Classifier_t()); 
    } 
}; 

用法:

PhotoRecognizer(Method1()); 
1

我已經在這裏和那裏實現了抽象工廠模式。在重新審視維護代碼後,我一直對此決定表示遺憾。沒有我能想到的情況,一個或多個工廠方法不會是一個更好的主意。因此,我最喜歡你的第二種方法。考慮按照ead的建議拋棄方法類。一旦您的測試完成,您將擁有一個或多個工廠方法,可以精確構建您想要的內容,最重要的是,您和其他人將能夠在以後遵循代碼。例如:

std::shared_ptr<PhotoRecognizer> CreateOptimizedPhotoRecognizer() 
{ 
    auto result = std::make_shared<PhotoRecognizer>(
     CreatePreProcessing(PreProcessingMethod::MedianFiltering), 
     CreateFeatureExtractor(FeatureExtractionMethod::WaveletStatictis), 
     CreateClassifier(ClassificationMethod::NearestNeighborClassifier) 
     ); 

    return result; 
} 

用你的工廠方法在這樣的代碼:

auto pPhotoRecognizer = CreateOptimizedPhotoRecognizer(); 

創建枚舉如你所說。我知道,我知道,打開/關閉原則......如果將這些枚舉保留在一個點上,就不會有問題使它們與工廠方法保持同步。首先枚舉:

enum class PreProcessingMethod { MedianFiltering, FilteringTypeB }; 
enum class FeatureExtractionMethod { WaveletStatictis, FeatureExtractionTypeB }; 
enum class ClassificationMethod { NearestNeighborClassifier, SVMClassfier, NeuralNetworkClassifer }; 

這裏有一個組件工廠方法的一個例子:

std::shared_ptr<PreProcessing> CreatePreProcessing(PreProcessingMethod method) 
{ 
    std::shared_ptr<PreProcessing> result; 

    switch (method) 
    { 
     case PreProcessingMethod::MedianFiltering: 
      result = std::make_shared<MedianFiltering>(); 
      break; 

     case PreProcessingMethod::FilteringTypeB: 
      result = std::make_shared<FilteringTypeB>(); 
      break; 

     default: 
      break; 
    } 

    return result; 
} 

爲了確定您可能希望創建在所有運行一些自動化測試的算法的最佳組合組件的可能排列。做到這一點的一種方法可能是這樣簡單:

for (auto preProc = static_cast<PreProcessingMethod>(0); ; 
    preProc = static_cast<PreProcessingMethod>(static_cast<int>(preProc) + 1)) 
{ 
    auto pPreProcessing = CreatePreProcessing(preProc); 
    if (!pPreProcessing) 
     break; 

    for (auto feature = static_cast<FeatureExtractionMethod>(0); ; 
     feature = static_cast<FeatureExtractionMethod>(static_cast<int>(feature) + 1)) 
    { 
     auto pFeatureExtractor = CreateFeatureExtractor(feature); 
     if (!pFeatureExtractor) 
      break; 

     for (auto classifier = static_cast<ClassificationMethod>(0); ; 
      classifier = static_cast<ClassificationMethod>(static_cast<int>(classifier) + 1)) 
     { 
      auto pClassifier = CreateClassifier(classifier); 
      if (!pClassifier) 
       break; 

      { 
       auto pPhotoRecognizer = std::make_shared<PhotoRecognizer>(
        pPreProcessing, 
        pFeatureExtractor, 
        pClassifier 
        ); 

       auto testResults = TestRecognizer(pPhotoRecognizer); 
       PrintConfigurationAndResults(pPhotoRecognizer, testResults); 
      } 
     } 
    } 
}