2016-07-27 10 views
3

Jetzt entwickle ich eine Klasse für die Erkennung eines Objekts in einem Foto, und diese Klasse besteht aus mehreren Komponenten (Klassen). Zum Beispielist dies ein Beispiel, in dem abstrakte Fabrikmuster verwendet werden?

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; 

} 

In diesem Beispiel, wenn wir diese Klasse verwenden Erkennung auszuführen, rufen wir andere Klassen PreProcessing, FeatureExtractor und Classifier. Wie Sie sich vorstellen können, gibt es viele verschiedene Methoden, um jede Klasse zu implementieren. Für die Klasse Classifier können wir beispielsweise SVMClassfier oder NeuralNetworkClassifer verwenden, die eine abgeleitete Klasse der Basisklasse Classifier ist.

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

}; 

daher durch verschiedene Elemente innerhalb PhotoRecognizer Klasse verwenden, können wir verschiedene Arten von PhotoRecongnizer erstellen. Jetzt baue ich einen Benchmark, um zu wissen, wie man diese Elemente kombiniert, um ein optimales PhotoRecognizer zu schaffen. Eine Lösung, die ich denken kann, ist abstrakte Werk zu verwenden:

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; 


    } 

Also, wenn ich Method1 verwenden Foto Erkennung durchzuführen, kann ich einfach folgendes tun:

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

Weiter mehr, kann ich selbst machen die Klasse PhotoRecognizer kompaktere:

enum RecMethod 
{ 
    Method1, Method2 

}; 

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

    boost::shared_ptr<MethodFactory> pFactory; 

}; 

So, hier ist meine Frage: ist die abstrakte Fabrik-Entwurfsmuster gut begründet in der oben beschriebenen Situation? Gibt es alternative Lösungen? Vielen Dank.

+0

Dies ist nicht im Zusammenhang mit C++, also warum ist das Tag dort? – Ceros

+0

@Ceros Die Demo-Codes sind in C++ – feelfree

+0

Aber Ihre Frage ist über Code-Design nicht C++ – Ceros

Antwort

2

Wie so oft gibt es keine ultimative "richtige" Methode, es zu tun, und die Die Antwort hängt sehr davon ab, wie das Projekt genutzt wird. Also wenn es nur für schnelle Tests ist, einmal gemacht und nie zurückgeschaut wurde - weitermachen und Enums verwenden, wenn es dein Herz begehrt, sollte dich niemand aufhalten.

Wenn Sie jedoch planen, die möglichen Methoden im Laufe der Zeit zu erweitern, würde ich die Verwendung Ihrer zweiten Ansatz mit Enums entmutigen. Der Grund ist: Jedes Mal, wenn Sie eine neue Methode hinzufügen möchten, müssen Sie die Klasse PhotoRecognizer ändern, so dass Sie den Code lesen müssen, um sich daran zu erinnern, was er macht und ob jemand anders es tun sollte - es würde noch mehr Zeit brauchen.

Das Design mit Aufzählungen verletzt zwei erste Regeln von SOLID (https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)):

  1. Open Closed-Prinzip (OCP): PhotoRecognizer Klasse kann nicht verlängert werden (eine neue Methode hinzufügen) ohne Änderung des Codes.
  2. Single-Responsibility-Prinzip (SRP): PhotoRecognizer Klasse erkennt nicht nur das Foto, sondern dient auch als eine Fabrik für Methoden.

Ihre erste Ansatz ist besser, denn wenn Sie eine andere Method3 definieren würden Sie es in Ihrem PhotoRecognizer und es ohne Änderung des Codes der Klasse verwenden, setzen könnte:

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

Was Ich mag es nicht über Ihren Ansatz, ist das für jede mögliche Kombination, die Sie eine Klasse (MethodX) schreiben müssen, die viel freudlose Arbeit ergeben könnte. Ich würde folgendes tun:

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

Siehe Method als als eine Sammlung von Slots für verschiedene Algorithmen, es hier, weil es bequem ist Verarbeitung/Abzieh/Classifier auf diese Weise zu übergeben.

Und man könnte eine Fabrik-Funktion:

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 
    } 

Sie könnten fragen: „Aber wie OCP“ - und du hättest Recht! Man muss die createMethod ändern, um andere (neue) Klassen hinzuzufügen. Und es könnte Ihnen nicht viel Trost sein, dass Sie immer noch die Möglichkeit haben, ein Method -Objekt von Hand zu erstellen, die Felder mit den neuen Klassen zu initialisieren und an einen PhotoRecognizer-Konstruktor zu übergeben.

Aber mit C++, haben Sie ein mächtiges Werkzeug zur Verfügung - die Vorlagen:

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 
    } 

Und Sie sind frei, eine beliebige Kombination Sie wollen wählen, ohne den Code zu ändern:

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

Es ist noch ein weiteres Problem: Was passiert, wenn die Klasse PreProcessingA nicht mit der Klasse ClassifierB verwendet werden kann? Früher, wenn es keine Klasse MethodAB gab, konnte niemand es benutzen, aber jetzt ist dieser Fehler möglich.

folgende Vorteile Dieser Ansatz hat
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; 
     .... 
} 

Fazit

:

Um dieses Problem zu umgehen, können Züge verwendet werden

  1. SRP, PhotoRecognizer dh - erkennt nur, Method - bündelt nur die Teile des Algorithmus und createMethod - erstellt nur eine Methode.
  2. OCP, d. H. Wir können neue Algorithmen hinzufügen, ohne den Code anderer Klassen/Funktionen zu ändern
  3. Dank Traits können wir eine falsche Kombination von Teilalgorithmen zur Kompilierzeit erkennen.
  4. Kein Codetyp/keine Codedopplung.

PS:

Sie sagen können, warum nicht die ganze Method Klasse kratzen?Man könnte genauso gut verwenden:

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

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

Ja, es stimmt. Diese Alternative hat einige Vor- und Nachteile, man muss mehr über das Projekt wissen, um den richtigen Trade machen zu können. Aber als Standard würde ich mit dem SRP-prinzipkonformen Ansatz des Einkapselns der Teilalgorithmen in die Klasse Method gehen.

0

Sofern Sie MethodFactory wiederverwenden, würde ich empfehlen die folgenden:

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()); 
    } 
}; 

Verbrauch:

PhotoRecognizer(Method1()); 
1

Ich habe hier und da ein abstraktes Fabrikmuster implementiert. Ich bedauere die Entscheidung immer wieder, nachdem ich den Wartungscode erneut gelesen habe. Es gibt keinen Fall, an den ich denken könnte, wo eine oder mehrere Fabrikmethoden keine bessere Idee gewesen wären. Daher mag ich deinen zweiten Ansatz am besten. Ziehen Sie in Erwägung, die Methodenklasse, wie sie vorgeschlagen wurde, zu verwerfen. Sobald Ihre Tests abgeschlossen sind, verfügen Sie über eine oder mehrere Factory-Methoden, mit denen genau das erstellt werden kann, was Sie möchten. Am besten können Sie und andere den Code später verfolgen. Zum Beispiel:

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

    return result; 
} 

Ihre Factory-Methode in Code verwenden:

auto pPhotoRecognizer = CreateOptimizedPhotoRecognizer(); 

Erstellen Sie die Aufzählungen, wie Sie vorgeschlagen. Ich weiß, ich weiß, open/closed-Prinzip ... Wenn Sie diese Aufzählungen an einem Ort behalten, haben Sie kein Problem damit, sie mit Ihren Fabrikmethoden zu synchronisieren. Zuerst werden die Aufzählungen:

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

Hier ist ein Beispiel einer Komponente Factory-Methode:

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; 
} 

Um die besten Kombinationen von Algorithmen, um zu bestimmen Sie wahrscheinlich einige automatisierte Tests erstellen werden soll, die durch alle laufen die möglichen Permutationen von Komponenten. Eine Möglichkeit, dies zu tun, könnte so einfach sein wie:

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); 
      } 
     } 
    } 
}