Codec2之建造者模式
Codec2的组件在参数配置上,采用了建造者模式,以下我们简单分析该模式的运用过程。
组件在初始化时,通过addParameter接口配置默认参数。
class C2SoftAvcDec::IntfImpl : public SimpleInterface<void>::BaseParams {
public:
explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper)
: SimpleInterface<void>::BaseParams(
helper,
COMPONENT_NAME,
C2Component::KIND_DECODER,
C2Component::DOMAIN_VIDEO,
MEDIA_MIMETYPE_VIDEO_AVC) {
noPrivateBuffers(); // TODO: account for our buffers here
noInputReferences();
noOutputReferences();
noInputLatency();
noTimeStretch();
// TODO: Proper support for reorder depth.
addParameter(
DefineParam(mActualOutputDelay, C2_PARAMKEY_OUTPUT_DELAY)
.withDefault(new C2PortActualDelayTuning::output(kDefaultOutputDelay))
.withFields({C2F(mActualOutputDelay, value).inRange(0, kMaxOutputDelay)})
.withSetter(Setter<decltype(*mActualOutputDelay)>::StrictValueWithNoDeps)
.build());
........//省略其他addParameter
}
addParameter的参数是ParamHelper类型,这个参数的构建采用了建造者模式。
什么是建造者模式?这里如何运用了建造者模式?
首先,理解什么是建造者模式,不如直接阅读《秒懂设计模式之建造者模式》,这篇文章非常通俗易懂地讲解了建造者模式的应用场景与两种构建方法。在此简单总结一下这篇文件的要点。
使用场景
当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。
解决的问题
当一个类的构造函数参数超过4个,而且这些参数有些是可选的时,我们通常有两种办法来构建它的对象,
第一种,折叠构造函数模式(telescoping constructor pattern ),第二种,Javabean 模式。
第一种主要是使用及阅读不方便。第二种方式在构建过程中对象的状态容易发生变化,造成错误。因为类中的属性是分步设置的,所以就容易出错。为了解决这两个痛点,builder模式就横空出世了。如何实现(以Computer的构建为例)
- 在Computer 中创建一个静态内部类 Builder,然后将Computer 中的参数都复制到Builder类中。
- 在Computer中创建一个private的构造函数,参数为Builder类型
- 在Builder中创建一个public的构造函数,参数为Computer中必填的那些参数,cpu 和ram。
- 在Builder中创建设置函数,对Computer中那些可选参数进行赋值,返回值为Builder类型的实例
- 在Builder中创建一个build()方法,在其中构建Computer的实例并返回
如何使用
在客户端使用链式调用,一步一步的把对象构建出来。
其次,我们来看一下Codec2中,组件是如何运用建造者模式来配置参数的。
addParameter的参数是ParamHelper类型,它的定义为:
DefineParam(mActualOutputDelay, C2_PARAMKEY_OUTPUT_DELAY)
.withDefault(new C2PortActualDelayTuning::output(kDefaultOutputDelay))
.withFields({C2F(mActualOutputDelay, value).inRange(0, kMaxOutputDelay)})
.withSetter(Setter<decltype(*mActualOutputDelay)>::StrictValueWithNoDeps)
.build()
DefineParam方法是C2InterfaceHelper的静态方法。
class C2InterfaceHelper {
public:
template<typename T>
static ParamBuilder<T> DefineParam(std::shared_ptr<T> ¶m, C2StringLiteral name) {
return ParamBuilder<T>(param, name);
}
}
它返回ParamBuilder对象。根据mActualOutputDelay的定义:
template<>
class SimpleC2Interface<void> {
public:
std::shared_ptr<C2PortActualDelayTuning::output> mActualOutputDelay;
}
ParamBuilder对象扩展为ParamBuilder< C2PortActualDelayTuning::output >对象。
ParamBuilder类定义在C2InterfaceHelper.h中,它具有若干个方法,在addParameter的参数定义中链式调用。
/**
* Templated move builder class for a parameter helper.
*/
template<typename T>
class C2_HIDE ParamBuilder : private ParamHelper {
public:
/** Construct the parameter builder from minimal info required. */
ParamBuilder(std::shared_ptr<T> ¶m, C2StringLiteral name)
: ParamHelper(param, name, C2StructDescriptor((T*)nullptr)),
mTypedParam(¶m) {
attrib() = attrib_t::IS_PERSISTENT;
}
//......省略
/** Adds default value. Must be added exactly once. */
inline ParamBuilder &withDefault(std::shared_ptr<T> default_) {
// CHECK(!mDefaultValue);
// WARN_IF(!default_); // could be nullptr if OOM
// technically, this could be in the parent
*mTypedParam = std::shared_ptr<T>(T::From(C2Param::Copy(*default_).release()));
setDefaultValue(default_);
std::shared_ptr<T> *typedParam = mTypedParam;
setGetter([typedParam](bool) -> std::shared_ptr<C2Param> {
return std::static_pointer_cast<C2Param>(*typedParam);
});
return *this;
}
/** Adds default value. Must be added exactly once. */
inline ParamBuilder &withDefault(T *default_) {
return withDefault(std::shared_ptr<T>(default_));
}
/** Adds all fields to this parameter with their possible values. */
inline ParamBuilder &withFields(std::vector<C2ParamFieldValues> &&fields_) {
setFields(std::move(fields_));
return *this;
}
/**
* Adds a constant value (also as default). Must be added exactly once.
*
* Const parameters by definition have no dependencies.
*/
inline ParamBuilder &withConstValue(std::shared_ptr<T> default_) {
//...... 省略
return withDefault(default_);
}
/** Adds constant value (also as default). Must be added exactly once. */
inline ParamBuilder &withConstValue(T *default_) {
return withConstValue(std::shared_ptr<T>(default_));
}
template<typename ... Deps>
inline ParamBuilder &withSetter(
C2R (*fn)(bool, C2P<T> &, const C2P<Deps> &...), std::shared_ptr<Deps>& ... deps) {
//...... 省略
return *this;
}
//返回的是ParamHelper类的方法build()
inline std::shared_ptr<ParamHelper> build() {
return ParamHelper::build();
}
protected:
std::shared_ptr<T> *mTypedParam;
};
我们看一下ParamHelper类的方法build()的定义。
//C2InterfaceHelper.cpp
std::shared_ptr<C2InterfaceHelper::ParamHelper> C2InterfaceHelper::ParamHelper::build() {
mImpl->build();
return std::make_shared<C2InterfaceHelper::ParamHelper>(std::move(*this));
}
它返回的是C2InterfaceHelper::ParamHelper类型的对象。这个类型的对象也就是addParameter所需要的入参类型对象。
//C2InterfaceHelper.cpp
void C2InterfaceHelper::addParameter(std::shared_ptr<ParamHelper> param) {
std::lock_guard<std::mutex> lock(mMutex);
mReflector->addStructDescriptor(param->retrieveStructDescriptor());
c2_status_t err = param->validate(mReflector);
if (err != C2_CORRUPTED) {
_mFactory->addParam(param);
// run setter to ensure correct values
bool changed = false;
std::vector<std::unique_ptr<C2SettingResult>> failures;
(void)param->trySet(param->value().get(), C2_MAY_BLOCK, &changed, *_mFactory, &failures);
}
}
以上就是Codec2组件在参数配置上采用建造者模式的过程,想法很好,但是诸如C2PortActualDelayTuning::output之类的参数定义过于复杂,实在很不友好,不如OpenMAX的标准来得简单快乐。不信?你来看一下以下这个宏定义,感受一下谷歌工程师的炫技与对我们普通开发者的技术暴打。
//C2InterfaceHelper.h
/**
* Creates a C2ParamFieldValuesBuilder class for a field of a parameter
*
* \param spParam a configuration parameter in an interface class subclassed from C2InterfaceHelper.
* \param field a field of such parameter
*/
#define C2F(spParam, field) \
C2ParamFieldValuesBuilder< \
typename _c2_reduce_enum_to_underlying_type< \
typename std::remove_reference< \
typename std::remove_extent< \
decltype(spParam->field)>::type>::type>::type>( \
C2ParamField(spParam.get(), &spParam->field))