Apollo中factory设计模式
创作笺言
只要你持续努力,老天不会亏付你,机会是留给有准备的人的。
背景前言
工厂设计模式已经有很多博主进行过很详细的介绍,这里不再赘述,基础的工厂设计方法理念,这篇文章写的个人感觉比较好,可以参考 这篇文章。本文重点以Apollo中的工厂类设计模板进行展开,深度刨析里面的设计技巧与方法。
不得不说Apollo代码里面包含有很多大厂的整体设计框架思路与设计方法,全局考虑,多次复用,减少耦合,模块化开发,有时间可以markApollo.
详细刨析过程
代码展示
代码存在于modules/common/util/factory
/******************************************************************************
* Copyright 2017 The Apollo Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*****************************************************************************/
/**
* @file
* @brief Defines the Factory class.
*/
#pragma once
#include <map>
#include <memory>
#include <utility>
#include "cyber/common/macros.h"
#include "cyber/common/log.h"
/**
* @namespace apollo::common::util
* @brief apollo::common::util
*/
namespace apollo {
namespace common {
namespace util {
/**
* @class Factory
* @brief Implements a Factory design pattern with Register and Create methods
*
* The objects created by this factory all implement the same interface
* (namely, AbstractProduct). This design pattern is useful in settings where
* multiple implementations of an interface are available, and one wishes to
* defer the choice of the implementation in use.
*
* @param IdentifierType Type used for identifying the registered classes,
* typically std::string.
* @param AbstractProduct The interface implemented by the registered classes
* @param ProductCreator Function returning a pointer to an instance of
* the registered class
* @param MapContainer Internal implementation of the function mapping
* IdentifierType to ProductCreator, by default std::unordered_map
*/
template <typename IdentifierType, class AbstractProduct,
class ProductCreator = AbstractProduct *(*)(),
class MapContainer = std::map<IdentifierType, ProductCreator>>
class Factory {
public:
/**
* @brief Registers the class given by the creator function, linking it to id.
* Registration must happen prior to calling CreateObject.
* @param id Identifier of the class being registered
* @param creator Function returning a pointer to an instance of
* the registered class
* @return True if the key id is still available
*/
bool Register(const IdentifierType &id, ProductCreator creator) {
return producers_.insert(std::make_pair(id, creator)).second;
}
bool Contains(const IdentifierType &id) {
return producers_.find(id) != producers_.end();
}
/**
* @brief Unregisters the class with the given identifier
* @param id The identifier of the class to be unregistered
*/
bool Unregister(const IdentifierType &id) {
return producers_.erase(id) == 1;
}
void Clear() { producers_.clear(); }
bool Empty() const { return producers_.empty(); }
/**
* @brief Creates and transfers membership of an object of type matching id.
* Need to register id before CreateObject is called. May return nullptr
* silently.
* @param id The identifier of the class we which to instantiate
* @param args the object construction arguments
*/
template <typename... Args>
std::unique_ptr<AbstractProduct> CreateObjectOrNull(const IdentifierType &id,
Args &&... args) {
auto id_iter = producers_.find(id);
if (id_iter != producers_.end()) {
return std::unique_ptr<AbstractProduct>(
(id_iter->second)(std::forward<Args>(args)...));
}
return nullptr;
}
/**
* @brief Creates and transfers membership of an object of type matching id.
* Need to register id before CreateObject is called.
* @param id The identifier of the class we which to instantiate
* @param args the object construction arguments
*/
template <typename... Args>
std::unique_ptr<AbstractProduct> CreateObject(const IdentifierType &id,
Args &&... args) {
auto result = CreateObjectOrNull(id, std::forward<Args>(args)...);
AERROR_IF(!result) << "Factory could not create Object of type : " << id;
return result;
}
private:
MapContainer producers_;
};
} // namespace util
} // namespace common
} // namespace apollo
有效代码区区不足30行,却将c++中各个知识点展现的淋漓尽致,看着舒服,极具简奢之风,让人赏心悦目
类模板解析
/**
* @class Factory
* @brief Implements a Factory design pattern with Register and Create methods
*
* The objects created by this factory all implement the same interface
* (namely, AbstractProduct). This design pattern is useful in settings where
* multiple implementations of an interface are available, and one wishes to
* defer the choice of the implementation in use.
*
* @param IdentifierType Type used for identifying the registered classes,
* typically std::string.
* @param AbstractProduct The interface implemented by the registered classes
* @param ProductCreator Function returning a pointer to an instance of
* the registered class
* @param MapContainer Internal implementation of the function mapping
* IdentifierType to ProductCreator, by default std::unordered_map
*/
template <typename IdentifierType, class AbstractProduct,
class ProductCreator = AbstractProduct *(*)(),
class MapContainer = std::map<IdentifierType, ProductCreator>>
class Factory {
public:
这里定义了一个类模板,类名叫Factory,用的是模块化的思想,方便构建出形式各异的工厂类,适配不同的需求.
先看结构,暂时先不看功能,很多人看开源项目会举步维艰,原因是想一步全部看完,既看结构,又看功能,然后就觉得看不懂,不知所云。一步一步来,这里的类模板有四个参数,其中前两个参数在类模板的实例话的时候必须指定,而后两个参数有默认的参数,这里之所以后面写成默认的形式,就是为了告诉使用者,后面两个参数不是简单的自定义的类型,而是具备一些特殊功能的“对象”。
- 第一个参数为一个类型
- 第二个参数也是一个类型
- 第三个是一个函数指针
- 第四个是一个std::map数据结构。
这几个参数中1、2、4比较好理解,第3个重点解释一下,
class ProductCreator = AbstractProduct *(*)()
这里的定义是第三个参数的名字叫ProductCreator,而它代表的是一个函数指针,这个函数指针指向的是一个函数,这个函数的参数是空,返回值是AbstractProduct *。这里使用的是C编码风格,扩展下C编码形式下的函数指针
#include <stdio.h>
int max(int x, int y)
{
return x > y ? x : y;
}
int main(void)
{
/* p 是函数指针 */
int (* p)(int, int) = & max; // &可以省略
//这里第一个int是函数的返回值,(*P)代表这是个函数指针,指针的名叫p,而最后的(int,int)说明函数参数为两个int
int a, b, c, d;
printf("请输入三个数字:");
scanf("%d %d %d", & a, & b, & c);
/* 与直接调用函数等价,d = max(max(a, b), c) */
d = p(p(a, b), c);
printf("最大的数字是: %d\n", d);
return 0;
}
如上示例所示,代码定义了一个函数指针,指针名字叫p,输入参数为两个int,函数返回值为int,对应到我们的代码里面,就是函数名叫ProductCreator,输入为空,函数返回是AbstractProduct *,所以亦可以写作:
AbstractProduct *(*ProductCreator)()
再看功能,优秀的开源项目里面拥有非常优秀的注释,此处就体现的淋漓尽致。这里再罗嗦一下
- 第一个参数IdentifierType:是唯一表示注册的对象的标识,可以用std::string,也可以用别的
- 第二个参数AbstractProduct:是所注册对象的类型,也就是此工厂要生产出来的物品类型,比如苹果、香蕉等等
- 第三个参数ProductCreator:是一个方法,这个方法需要能够创造出实例化对象的,并将实例化对象的指针返回回来
- 第四个参数MapContainer,使用来匹配管理对象标识与创造词对象方法的,将对象的标识与创造方法联系起来,这样就可以根据对象类型标识来找到创造此对象的方法。
Register方法
class Factory {
public:
/**
* @brief Registers the class given by the creator function, linking it to id.
* Registration must happen prior to calling CreateObject.
* @param id Identifier of the class being registered
* @param creator Function returning a pointer to an instance of
* the registered class
* @return True if the key id is still available
*/
bool Register(const IdentifierType &id, ProductCreator creator) {
return producers_.insert(std::make_pair(id, creator)).second;
}
这里是要注册一个创造对象的方法与对象的标识关联起来。比如一个说这是一个玩具工厂,注册的步骤就是将生产方法(第二个参数,比如生产水枪的车间)与生产这个类型(水枪)关联起来,并在次工厂落地,成为此工厂的一部分。
函数内的实现进行了缩略,可以拆开看,等同于
std::pair<std::map<IdentifierType, ProductCreator>>iterator, bool> insert_result = \
producers_.insert(std::make_pair(id, creator));
return insert_result.second;
从此函数可以看出,同样的类型只能注册一次,而且是第一次生效,后面的就失效了。并把插入成功和失败的状态返回。这里使用producers_来进行匹配管理。
Contains方法
bool Contains(const IdentifierType &id) {
return producers_.find(id) != producers_.end();
}
这个函数是判断此工会长是否具备生产某一个类型的方法。举例来说就是判断玩具工厂是否具备生产水枪玩具的车间。
Unregister方法
/**
* @brief Unregisters the class with the given identifier
* @param id The identifier of the class to be unregistered
*/
bool Unregister(const IdentifierType &id) {
return producers_.erase(id) == 1;
}
有注册就有注销,这里就是注销的方式,并将注销的是否成功状态返回,以备后续判断使用
Clear & Empty
void Clear() { producers_.clear(); }
bool Empty() const { return producers_.empty(); }
这两个函数比较简单,一个是清空工厂,一个是判断工厂是否为空。
CreateObject 方法
/**
* @brief Creates and transfers membership of an object of type matching id.
* Need to register id before CreateObject is called. May return nullptr
* silently.
* @param id The identifier of the class we which to instantiate
* @param args the object construction arguments
*/
template <typename... Args>
std::unique_ptr<AbstractProduct> CreateObjectOrNull(const IdentifierType &id,
Args &&... args) {
auto id_iter = producers_.find(id);
if (id_iter != producers_.end()) {
return std::unique_ptr<AbstractProduct>(
(id_iter->second)(std::forward<Args>(args)...));
}
return nullptr;
}
/**
* @brief Creates and transfers membership of an object of type matching id.
* Need to register id before CreateObject is called.
* @param id The identifier of the class we which to instantiate
* @param args the object construction arguments
*/
template <typename... Args>
std::unique_ptr<AbstractProduct> CreateObject(const IdentifierType &id,
Args &&... args) {
auto result = CreateObjectOrNull(id, std::forward<Args>(args)...);
AERROR_IF(!result) << "Factory could not create Object of type : " << id;
return result;
}
CreateObject与CreateObjectOrNull是相互嵌套使用的,所以放在一起来说。CreateObject是此工厂的重要过程->生产产品的过程。一般对外常用的就是CreateObject接口,里面调用了CreateObjectOrNull方法,并进行了一些断言,日志输出。重点看下CreateObjectOrNull函数。CreateObjectOrNull函数是一个参数大于等于1的函数,第一个参数为必填项,为产品的标识,后面的参数为不固定参数,这个需要根据AbstractProduct的构造函数来定。有的产品构造函数需要参数,有的产品构造函数不需要参数,这里均能适配。
这里使用了c++11的标准std::forward功能,完美转发,将函数的参数类型进行完美转发给自己的构造函数。如果在CreateObject的时候,之前没有进行注册,id_iter就会返回producers_.end(),此函数就会返回null,进行断言,帮助开发者进行代码调试。提醒开发者要事先进行注册。
使用样例
#include <string>
#include <iostream>
#include "factory"
using namespace apollo::common::util
class Base {
public:
virtual std::string Name() const { return "base"; }
};
class Derived : public Base {
public:
virtual std::string Name() const { return "derived"; }
};
class ArgConstructor {
public:
explicit ArgConstructor(const std::string& name) : name_(name) {}
ArgConstructor(const std::string& name, int value)
: name_(name), value_(value) {}
std::string Name() const { return name_; }
int Value() const { return value_; }
private:
std::string name_;
int value_ = 0;
};
int main(){
//测试使用默认参数的类模板
Factory<std::string, Base> factory;
//工厂产品注册
factory.Register("derived_class",
[]() -> Base* { return new Derived(); }));
auto derived_ptr = factory.CreateObject("derived_class");
std::cout<<"derived_ptr.name = "<<derived_ptr->Name();
auto non_exist_ptr = factory.CreateObject("non_exist_class");//non_exist_ptr为null
std::cout<<"non_exist_ptr = "<<non_exist_ptr;// non_exist_ptr = 0
//测试使用自定义参数的类模板,也就是类模板的第三第四位不使用默认的参数
Factory<std::string, ArgConstructor, ArgConstructor* (*)(const std::string&)> arg_factory;
arg_factory.Register(
"arg_1", [](const std::string& arg) { return new ArgConstructor(arg); });//返回值不为空
auto arg_ptr = factory.CreateObject("arg_1", "name_1");//arg_ptr不为空
auto non_exist_arg_prt = factory.CreateObject("arg_1", "name_1");//non_exist_arg_prt为空
std::cout<<"arg_ptr->Name() = "<<arg_ptr->Name();//输出为 arg_ptr->Name() = name_1
return 0;
}
总结
此工厂方法代码精简却不缺灵活性,近30行代码,包含的知识点可谓是丰富。我们不仅仅可以在Apollo里面使用,在日常工作中如果需要工厂模板,可以直接拿走使用。因为这些代码不与Apollo耦合。
总结一下知识点:
- 模板类
- std::map
- std::forward
- 函数指针
- 断言机制
感想
优秀的程序员写出的代码总是让人赏心悦目,心旷神怡~