设计模式之工厂模式

开个新坑,复习基础知识,用typescript写写旧技术——设计模式。今天就介绍一下工厂模式,以及其他两个衍生模式工厂方法抽象工厂

简单工厂

工厂模式,又称简单工厂,顾名思义就是使用“工厂”(一个或一系列方法)去生产“产品”(一个或一系列的派生类实例)。UML如下所示:

Simple Factory

上图我定义产品接口名为Vegetable,它的两个派生类为Onion和Garlic:

// Vegetable.ts
interface Vegetable {
  fry(): void;
}

class Onion implements Vegetable {
  public fry(): void {
      console.log('Onion');
  }
}

class Garlic implements Vegetable {
  public fry(): void {
      console.log('Garlic');
  }
}

“蔬菜工厂”如下所示,通过调用不同的方法生产出不同的蔬菜实例。

p.s. 有些工厂模式的实现只有一个函数体,根据特定参数来“生产”特定的派生实例,这也是可行的。

// SimpleFactory.ts
import {Garlic, Onion, Vegetable} from './Vegetable';

class SimpleFactory {
  public createOnion(): Vegetable {
    return new Onion();
  }

  public createGarlic(): Vegetable {
    return new Garlic();
  }
}

最后看一下client调用:

// client.ts
import {SimpleFactory} from './SimpleFactory';
import {Vegetable} from './Vegetable';

const factory: SimpleFactory = new  SimpleFactory();

let onion: Vegetable = factory.createOnion();
let garlic: Vegetable = factory.createGarlic();

onion.fry(); // Onion
garlic.fry(); // Garlic

OK,问题来了,我们使用工厂模式的意义是什么?饶了这么一大圈不就是打印了个两个菜名吗?为什么不直接new呢?

import {Onion, Garlic} from './Vegetable';

let onion: Onion = new Onion();
let garlic: Garlic = new Garlic();

直接new出两个蔬菜实力确实特别简单,但是在大型软件开发中这很危险,用专业术语来说是违反了依赖倒置原则

高层模块不应该依赖于低层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象

有点绕,阳春白雪和者必寡,我们还是走下里巴人路线吧。通俗来讲,引入的依赖并不可靠,它一般是第三方库或是其他开发人员实现的代码;在不确定的某一天,里面的代码会被修改甚至是删除。这时候你的代码会变得很脆,不知不觉中就崩了。但现实中又不可能不引入其他依赖,所以大家就约定最小依赖引入:依赖提供者将隐藏对象的属性和实现细节,仅对外公开接口(抽象),这就是OOP三大特性之一的封装

再回看一下client实现,只import了一个公用接口Vegetable。运行时,我们利用多态——let onion: Vegetable = factory.createOnion()——就可以实现派生方法的动态绑定(onion.fry())。这样,“派生蔬菜”(Onion或Garlic)的修改就不会影响现有代码了。哪天VegetableFactory开发者觉得Onion子类实现太过丑陋或是性能太差,他只需在简单工厂里换一个新的“派生蔬菜”(OnionFromJapan)即可,client代码不需要做任何改动。

class VegetableFactory {
 public createOnion(): Vegetable {
   return new OnionFromJapan();
 }
}

工厂模式的最大优点就是屏蔽产品的具体实现,调用者只关心产品的接口。当然,它也有自己的问题,产品种类可能有成千上万,如果都是依靠同一个工厂生产,那么必然会使得工厂代码及其庞大。这就有了工厂方法的设计实现。

工厂方法

工厂方法就是针对每一种产品提供一个工厂类,通过不同派生工厂创建不同的产品。

Factory Method

实现很简单,为每种蔬菜提供对应的派生“蔬菜工厂”就行了。问题又来了,工厂与蔬菜一一对应有没有多此一举呢?要不直接new?嗯,这里不给出解答了,回去体会一下oop三大特性:封装、多态和继承

import {Garlic, Onion, Vegetable} from './Vegetable';

interface VegetableFactory {
  create(): Vegetable;
}

class OnionFactory implements VegetableFactory {
  public create(): Vegetable {
    return new Onion();
  }
}

class GarlicFactory implements VegetableFactory {
  public create(): Vegetable {
    return new Garlic();
  }
}

工厂方法减轻了工厂类的负担,新增一种“蔬菜”只需添加一个特定的“蔬菜工厂”即可,这就符合了开放闭合原则

对扩展是开放的;对修改是关闭的

这里提一下,开放闭合原则并不是说接口一成不变,它要求的是增量变化——只增加新方法,不改动旧方法。

抽象工厂

工厂方法自然比简单工厂更加灵活,但当业务有所变更时,比如需要添加“蔬菜”的周边产品——“酒”呢?(洋葱和红酒更配哦)这时候我们就需要一个更复杂的模式——抽象工厂了。

Abstract Factory

抽象工厂是一个产品簇的概念,一个工厂可以生产多种业务相关的产品。我们在工厂方法的基础上扩充一下代码:定义一个抽象工厂接口AbstractFactory,通过不同的方法生产出一个“抽象”产品簇(VegetableDrink)。回过头来再看工厂方法,事实上它就是抽象工厂最简单的一种场景设计——只生成一种产品。

interface AbstractFactory {
  create(): Vegetable;
  pick(): Drink;
}

class OnionRecipe implements AbstractFactory {
  public create(): Vegetable {
    return new Onion();
  }

  public pick(): Drink {
    return new Wine();
  }
}

抽象工厂的缺点很明显:成也产品簇败也产品簇,复杂度大,应用场景有限。

总结

  • 简单工厂:调用者只需使用单例工厂就可获取同一范畴的所有产品

  • 工厂方法:调用者并不知道它在运行时会获取何种产品,只知道某个特定的工厂能生成出满足需求的产品

  • 抽象工厂:调用者可以在运行时从特定的工厂中获得所有信息相关的产品簇

设计模式是上个世纪九十年代初从建筑领域引入到计算机软件开发领域的概念;是对软件设计中反复出现的各种问题所提出的一套解决方案。一般来说,设计模式的教学案例都是基于OOP语言java实现的,所以有时候会有一种错觉,以为设计模式只有java开发人员才该掌握的。但事实上设计模式更多的是一种思想,是在某种情景下解决特定问题的可靠途径,它不仅仅局限于组织编码,更可以应用于架构层面的思考。

思考题

如果我们的一个服务类中出现大量类似于if(useA)if(useB)这样的条件判断语句,你会怎么去重构它?


class Service {
  public methodA(): void {
    ...
    if( this.useA ){
        // omit
    }

    ...
  }

  public methodB(): void {
    ...
    if( this.useA ){
        // omit
    }
    ...

    if( this.useB ){
      // omit
    }
    ...
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容