likes
comments
collection
share

前端设计模式系列——工厂模式

作者站长头像
站长
· 阅读数 15

持续整理一些前端用到的设计模式,欢迎大家关注专栏:

  • 工厂模式
  • 单例模式
  • 代理模式
  • 策略模式
  • 建造者模式
  • 观察者模式
  • 适配器模式
  • 装饰器模式
  • 迭代器模式
  • 中间件模式
  • ……

工厂模式

基本概念

工厂模式的用法不止一种,用途非常广。用户只通过工厂函数来创建对象,这样写出来的代码更加健壮,也更容易理解。例如在 JavaScript 中,创建一个对象可以用 new 或者 Object.create 等,选择很多非常灵活,但是如果要设计一个 API 给用户用的话,建议采用下面的工厂函数的方式:

function createImage(name) {
  return new Image(name)
}

const image = createImage('photo.jpeg')

用户只需要记住 createImage 函数,并传递一个 name 参数即可拿到可用的 Image 对象。也许你可能会问:这样做不是多此一举吗?直接用下面的方式岂不更精简?

const image = new Image('photo.jpeg')

答案就是:工厂模式让我们能够把创建对象的流程,与该流程的实现方式解耦。工厂模式封装的就是创建新实例的逻辑,让开发者能够在该模式内部,灵活地调控实例的创建手法,例如后期可能会对不同类型的图片进行不同的处理,此时只需调整 createImage 工厂函数即可:

function createImage(name) {
  if (name.match(/.jpe?g$/)) {
    return new ImageJpeg(name)
  } else if (name.match(/.gif$/)) {
    return new ImageGif(name)
  } else if (name.match(/.png$/)) {
    return new ImagePng(name)
  } else {
    throw new Error('Unsupported Format')
  }
}

而此调整对使用者是无感知的,因为工厂函数不会向外透露具体的实现,它会将生产的逻辑隐藏起来,让外界无法修改这些类。

记住:所谓的工厂模式,就是在函数里返回类的实例。用户只需要传递参数给工厂函数,里面具体的过程不用关心,反正最后返回了实例对象。

典型示例

jQuery

jQuery 代码里面可以看到 jQuery 就是一个工厂函数,执行该函数的时候会返回一个 jQuery 的实例:

var jQuery = function( selector, context ) {
  // The jQuery object is actually just the init constructor 'enhanced'
  // Need init if jQuery is called (just allow error to be thrown if not included)
  return new jQuery.fn.init( selector, context );
};

真正的实例对象则是由下面函数来创建的:

jQuery.fn.init = function( selector, context ) {
  var match, elem;

  // HANDLE: $(""), $(null), $(undefined), $(false)
  if ( !selector ) return this;

  // HANDLE: $(DOMElement)
  if ( selector.nodeType ) {
    this[ 0 ] = selector;
    this.length = 1;
    return this;

  // HANDLE: $(function) Shortcut for document ready
  } else if ( typeof selector === "function" ) {
    return rootjQuery.ready !== undefined ?
      rootjQuery.ready( selector ) :
      selector( jQuery );

  } else {
  	// 省略...
  }
}

React

React 源码里面,新增了 FiberNode,结构比较复杂:

function FiberNode(tag, pendingProps, key, mode) {
  // Instance
  this.tag = tag;
  this.key = key;
  this.elementType = null;
  this.type = null;
  this.stateNode = null;

  // Fiber
  this.return = null;
  this.child = null;
  this.sibling = null;
  this.index = 0;

  this.ref = null;
  this.refCleanup = null;

  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;

  this.mode = mode;

  // Effects
  this.flags = NoFlags;
  this.subtreeFlags = NoFlags;
  this.deletions = null;

  this.lanes = NoLanes;
  this.childLanes = NoLanes;

  this.alternate = null;
}

然后关键的地方来了,React 并没有在创建 Fiber 节点的时候使用 new FiberNode,而是又添加了一个 createFiber 工厂函数来创建 Fiber 节点,并给出了非常详细的说明:

// This is a constructor function, rather than a POJO constructor, still
// please ensure we do the following:
// 1) Nobody should add any instance methods on this. Instance methods can be
//    more difficult to predict when they get optimized and they are almost
//    never inlined properly in static compilers.
// 2) Nobody should rely on `instanceof Fiber` for type testing. We should
//    always know when it is a fiber.
// 3) We might want to experiment with using numeric keys since they are easier
//    to optimize in a non-JIT environment.
// 4) We can easily go from a constructor to a createFiber object literal if that
//    is faster.
// 5) It should be easy to port this to a C struct and keep a C implementation
//    compatible.
const createFiber = function(tag, pendingProps, key, mode) {
  return new FiberNode(tag, pendingProps, key, mode);
}

Knex

Knex 是一个查询生成器,能够对各种数据库做 SQL 查询,它导出了一个工厂函数,在里面创建了 Dialect 对象,并将封装好的 Knex 对象返回给用户使用:

function knex(config) {
  const { resolvedConfig, Dialect } = resolveConfig(...arguments);

  const newKnex = makeKnex(new Dialect(resolvedConfig));
  if (resolvedConfig.userParams) {
    newKnex.userParams = resolvedConfig.userParams;
  }
  return newKnex;
}