这是一个使用抽象工厂模式的例子吗?

时间:2016-07-27 19:12:44

标签: c++ design-patterns

现在我正在开发一个用于识别照片中对象的类,这个类由几个组件(类)组成。例如,

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执行照片识别时,我可以简单地执行以下操作:

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;

};

所以这是我的问题:抽象工厂设计模式在上述情况下是否合理?有替代解决方案吗?感谢。

3 个答案:

答案 0 :(得分:2)

由于经常没有终极&#34;权利&#34;这样做的方法,答案很大程度上取决于项目的使用方式。因此,如果它仅用于快速测试,只做一次而且从不回头 - 如果这是你心中的愿望,继续使用枚举,没有人应该阻止你。

但是,如果您计划在一段时间内扩展可能的方法,我会阻止您使用枚举来使用第二种方法。原因是:每次要添加新方法时,都必须更改PhotoRecognizer类,因此必须阅读代码,记住它正在做什么,以及其他人是否应该这样做 - 它甚至需要更多时间。

使用枚举的设计违反了SOLID的两个首要规则(https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)):

  1. 开放 - 封闭原则(OCP):PhotoRecognizer类无法在不修改其代码的情况下进行扩展(添加新方法)。
  2. 单一责任原则(SRP):PhotoRecognizer类不仅可以识别照片,还可以作为方法的工厂。
  3. 您的第一种方法更好,因为如果您要定义另一个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作为不同算法的插槽集合,这里是因为以这种方式传递Processing / Extractor / Classifier很方便。

    可以使用工厂功能:

       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
       }
    

    您可能会问:&#34;但是OCP怎么样?&#34; - 你会是对的!必须更改createMethod以添加其他(新)类。对你来说可能不太舒服,你仍然可以手工创建一个Method - 对象,用新类初始化字段并将其传递给PhotoRecognizer - 构造函数。 / p>

    但是使用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. 没有样板代码/没有代码重复。
    5. 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类中。

答案 1 :(得分: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);
            }
        }
    }
}

答案 2 :(得分: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());