前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >new也可以创建对象,为什么需要工厂模式?

new也可以创建对象,为什么需要工厂模式?

作者头像
用户6557940
发布2022-07-24 16:48:21
8340
发布2022-07-24 16:48:21
举报
文章被收录于专栏:Jungle笔记Jungle笔记

设计模式里,工厂模式是一类创建型的设计模式。为遵循软件设计和开发的开闭原则,先后衍生出了简单工厂模式,工厂方法模式和抽象工厂模式。作为一种创建型的设计模式,工厂模式是用来创建新对象的。那么问题就来了,以C++为例,C++的类明明构造函数也可以创建新的对象啊,为什么非得引入工厂模式呢

封装创建对象时的初始化工作

如果使用C语言,分配并初始化的工作包括:

  • malloc申请内存(但申请完了对象并没有初始化,只是有了一片内存空间),并强制类型转换
  • 初始化这块内存
  • Do other works

好像有点麻烦,分配内存、类型转换、初始化。如果是C++,new的动作包括分配内存和调用构造函数两个步骤,比较简化了。这是对一般的初始化过程比较简单的对象。那如果初始化过程比较复杂呢?什么叫比较复杂的初始化过程呢?就是说创建对象不仅是分配内存空间,还要做一些其他初始化工作,甚至是与外部变量或者资源相关的工作

下面的代码是NVDLA的compiler的源码中的一部分:

代码语言:javascript
复制
SDPScaleOpNode* NodeFactory::newSDPScaleOpNode
(
    ScaleNode* origCanNode,
    Graph* engGraph
)
{
    B b;
    DD dd;
    NvU16 numBatches = engGraph->profile()->multiBatchSize();
 
    b = dd = new SDPScaleOpNode(origCanNode, numBatches);
    dd->setName(std::string("sdp-scale-") + toString(s_sdp_scale_priv.size()));
    dd->setId(engGraph->nextNodeId());
    dd->setGraph(engGraph);
    engGraph->insertNode(b);
 
    s_sdp_scale_priv.insert(std::pair<B, DD>(b, dd));
    return dd;
}

我删去了部分代码以便于观察。该接口是要创建一个SDPScaleOpNode,但封装在NodeFactory的newSDPScaleOpNode()接口里。该接口里除了

代码语言:javascript
复制
new SDPScaleOpNode(origCanNode, numBatches);

以外,还有其他setter和insert工作。如果不用工厂模式封装,则每创建一个node,都要在创建node的地方写上其他setter和insert的代码,不便于阅读,而且造成代码冗余。

下面代码是tensorflow源码中的一个片段。可以看到,创建device的初始化过程更加复杂,甚至还可以处理一些异常

代码语言:javascript
复制
std::unique_ptr<Device> DeviceFactory::NewDevice(const string& type,
                                                 const SessionOptions& options,
                                                 const string& name_prefix) {
  auto device_factory = GetFactory(type);
  if (!device_factory) {
    return nullptr;
  }
  SessionOptions opt = options;
  (*opt.config.mutable_device_count())[type] = 1;
  std::vector<std::unique_ptr<Device>> devices;
  TF_CHECK_OK(device_factory->CreateDevices(opt, name_prefix, &devices));
  int expected_num_devices = 1;
  auto iter = options.config.device_count().find(type);
  if (iter != options.config.device_count().end()) {
    expected_num_devices = iter->second;
  }
  DCHECK_EQ(devices.size(), static_cast<size_t>(expected_num_devices));
  return std::move(devices[0]);
}

统一创建对象的接口命名

有了上面的例子,也就比较好理解“统一接口命名”了。

  • 如果是class Football,那么创建是要new Football;
  • 如果创建Basketball,则要new Basketball;
  • 如果是Volleyball,则new VolleyBall;
  • 如果是AbcdEfgHijkOpq1234567,则new AbcdEfgHijkOpq1234567(类的名字很长)。

如果有工厂模式做封装,那么就成了

代码语言:javascript
复制
createFootball();
createBasketball();
createVolleyball();
createAbcdEfgHijkOpq1234567();

接口命名很清晰,并且能够通过函数名大概知道它的作用。

对象是否真的需要“创建”?

每次new,都会去分配内存(不谈placement new)。但是有的场景下,我们真的需要每次都分配内存吗?要从线程池里获取一个线程,要从内存池里获取一片内存,要从某个资源池里获取一个资源,这些资源本身就有,不需要重新分配,除非池里的资源也用完了。所以工厂模式的另一个作用是,掌控某些资源分配的时机,当真正需要分配内存的时候,才去分配

结合多态,便于扩展

工厂模式结合多态,定义一个用于创建对象的接口,但是让子类决定将哪一个类实例化,增加代码的灵活性。比如下列代码,通过一个统一的接口getSportProduct(),运行时可创建出不同的产品。

代码语言:javascript
复制
int test()
{
  AbstractFactory *fac = NULL;
  AbstractSportProduct *product = NULL;
 
  fac = new BasketballFactory();
  product = fac->getSportProduct();
 
  fac = new FootballFactory();
  product = fac->getSportProduct();
 
  fac = new VolleyballFactory();
  product = fac->getSportProduct();  
 
  // other work
 
    return 0;
}
本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-10-19,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 Jungle笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 封装创建对象时的初始化工作
  • 结合多态,便于扩展
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com