【提升篇】工厂设计模式-Apollo工厂类模板解析

创作笺言

只要你持续努力,老天不会亏付你,机会是留给有准备的人的。

背景前言

工厂设计模式已经有很多博主进行过很详细的介绍,这里不再赘述,基础的工厂设计方法理念,这篇文章写的个人感觉比较好,可以参考 这篇文章。本文重点以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
  • 函数指针
  • 断言机制

感想

  优秀的程序员写出的代码总是让人赏心悦目,心旷神怡~

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值