现在我正在开发一个用于识别照片中对象的类,这个类由几个组件(类)组成。例如,
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;
}
在此示例中,当我们使用此类执行识别时,我们会调用其他类PreProcessing
,FeatureExtractor
和Classifier
。正如您可以想象的那样,实现每个类有许多不同的方法。例如,对于Classifier
类,我们可以使用SVMClassfier
或NeuralNetworkClassifer
,它是基本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;
};
所以这是我的问题:抽象工厂设计模式在上述情况下是否合理?有替代解决方案吗?感谢。
答案 0 :(得分:2)
由于经常没有终极&#34;权利&#34;这样做的方法,答案很大程度上取决于项目的使用方式。因此,如果它仅用于快速测试,只做一次而且从不回头 - 如果这是你心中的愿望,继续使用枚举,没有人应该阻止你。
但是,如果您计划在一段时间内扩展可能的方法,我会阻止您使用枚举来使用第二种方法。原因是:每次要添加新方法时,都必须更改PhotoRecognizer
类,因此必须阅读代码,记住它正在做什么,以及其他人是否应该这样做 - 它甚至需要更多时间。
使用枚举的设计违反了SOLID的两个首要规则(https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)):
PhotoRecognizer
类无法在不修改其代码的情况下进行扩展(添加新方法)。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
作为不同算法的插槽集合,这里是因为以这种方式传递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;
....
}
<强>结论强>
这种方法具有以下优点:
PhotoRecognizer
- 仅识别,Method
- 仅捆绑算法部分和createMethod
- 仅创建方法。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());