设计模式之创建型模式
2、 设计模式之创建型模式
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。这样可以降低系统的 耦合度,使用者不需要关注对象的创建细节,对象的创建由相关的工厂来完成。就像我们去商场购买商品时,不需 要知道商品是怎么生产出来一样,因为它们由专门的厂商生产。
创建型模式分为以下几种。
image.png2.1 单例模式
2.1.1 什么是单例模式?(概念的引入)
案例:我是皇帝,独此一家。
中国自从秦始皇确立了皇帝这个职位之后,同一个时期基本上上就只有一个人孤零零的坐在皇位上啦。这种情况的 好处就是大家好办事,大家讨论或者汇报大事的时候只要提及皇帝,每个人都知道指的是谁,不需要在皇帝面前加 上特定的称呼。这种过程反应到软件设计领域就是:一个类只能产生一个对象(皇帝),大家对他的依赖都是相同 的。我们把皇帝这种特殊的职位通过程序来实现。
皇帝类:
image.png
大臣类:
image.png
运行结果:
image.png
大臣每天上朝汇报国事的对象都是同一个皇帝,这就是单例模式!
2.1.2 单例模式的定义以及特点
定义:
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
Ensure a class has only one instance,and provide a global point of access to it.
特点:
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点。
2.1.3 单例模式的分类
上面的单例模式,在低并发的情况下可能不会出现问题,如果并发量增大,内存中就会出现多个实例,就不是真正 意义上的单例。为什么会出现这种情况呢?
解决线程不安全的方式有多种。我们先将上面代码的单例模式修改为线程安全的:
懒汉式单例:
image.png该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。
关键字 volatile 和 synchronized,能保证线程安全,但是每次访问时都要同步,会影响性能,且消耗更多的资 源,这是懒汉式单例的缺点。
饿汉式单例:
image.png该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了。而且该方式是线 程安全的。
2.1.4 单例模式的使用场景
- 某类只要求生成一个对象的时候,如一个航班的机长、每个人的身份证号等。
- 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问 速度。如 Web 中的配置对象、数据库的连接池等。
- 当某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
- 在计算机系统中, Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、 打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应 用程序中的对话框、系统中的缓存等常常被设计成单例。
2.1.5 单例模式的优缺点
优点:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
- 避免对资源的多重占用(比如写文件操作)。
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问。
缺点:
- 单例模式一般没有接口,扩展很困难。如果要扩展,只能修改代码。
- 与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
2.1.6 单例模式的扩展
单例模式可扩展为有限的多例(Multitcm)模式,这种模式可生成有限个实例并保存在 ArrayList 中,客户需要时 可随机获取。
image.png大臣类:
image.png运行结果:PS:每个人的运行结果都不一样,因为随机数的产生不一致
image.png2.2 工厂方法模式
2.2.1 什么是工厂模式?
引入案例:话说前一阵疫情期间,居家的时间有点长,于是我也跟广大网友一样,开始好好修炼自己的厨艺,有人 做凉皮,有人做锅巴。我也加入了霍霍面粉的大军--做面包。
于是和面、发面、捏成我想要的布朗熊的样子,开心放入烤箱 ,等待我的布朗熊面包出炉。然而,步骤没错,烤箱 没错,可能是大厨不对:
第一次:时间稍短了点,没太烤熟,于是布朗熊面包变成了北极熊面包;
再来一次:时间长点肯定能熟了,烤箱中多靠一会,出炉发现烤焦啦,与布朗熊面包变成了黑熊面包;
第三次:吸取教训,别跟时间死磕了,一直盯着烤箱中的面包好了,等到面包微微发焦,终于成功的做出了我想要 的布朗熊面包
好在家人给面子,每一种面包都有人吃掉哈。
在这个过程中,我的职业病就犯了,是不是可以通过软件开发来实现这个过程呢?
在面向对象的思想中,万物皆对象。是对象我们就可以通过软件设计来实现。来分析一下烤面包的过程,该过程涉 及三个对象:大厨(也就是我哈)、烤箱、三种不同成果的面包(我称他们为北极熊、黑熊、布朗熊)。
大厨可以使用场景类Client来表示,烤箱类似一个工厂,负责生产产品(即面包),三种不同成果的面包都是一个 接口下不同实现类,因为好不好吃好不好看也都是面包啊。
image.png image.png image.png image.png image.png2.2.2 工厂方法模式的定义
定义:
定义一个用户创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
工厂方法模式的主要角色如下:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对 应。
我们可以将工厂方法模式抽取出一个实用的通用代码:
image.png2.2.3 工厂方法模式的应用场景
工厂方法模式通常适用于以下场景。
- 客户只知道创建产品的工厂名,而不知道具体的产品名。如 TCL 电视工厂、海信电视工厂等。
- 创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
- 客户不关心创建产品的细节,只关心产品的品牌。
2.3 抽象工厂模式
2.3.1 引入案例:
上回书说到我的烤面包副业已经小有成就,做的多了发现我的面包似乎缺少了灵魂,面包怎么能没有馅儿呢?!于 是打算将自己最爱的水果菠萝和芒果放入面包中。
大厨烤面包之前也是做了很多的准备工作的,所以想在不浪费现有资源的情况下继续完成新产品的制作。之前做的 面包中虽然大厨自己觉得布朗熊面包才是最成功的的,但是每个人的口味不一样,有人偏爱烤的过火的,有的偏爱 稍欠火候的。所以我决定继续满足所有人的口味:即将做出三种火候的菠萝面包和芒果面包。
即将要做的产品分析完了,可是我的工厂(面包机)真真只有一个烤面包的功能,于是为了做出蛋糕,又专门买了 一个烘焙蛋糕的烤箱。于是乎,可以准备干活啦!
image.png image.png image.png image.png所有产品都出炉啦!
image.png工厂方法模式只考虑生产同等级的产品,抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于 不同等级的一组产品称为一个产品族。
2.3.2 模式的定义与特点
抽象工厂(AbstractFactory)模式的定义:为创建一组相关或者相互依赖的对象提供一个接口,而且无须指定他们 的具体类。
抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是 一种非常好的解决方案。工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
使用抽象工厂模式一般要满足以下条件。
- 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
- 系统一次只可能消费其中某一族产品,即同族的产品一起使用。
2.3.3 抽象工厂的通用代码
image.png image.png image.png2.3.4 抽象工厂模式的优缺点:
优点:
- 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
- 当增加一个新的产品族时不需要修改原代码,满足开闭原则。
其缺点是:当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
2.3.5 抽象工厂模式的应用场景
- 适合于产品之间相互关联、相互依赖且相互约束的地方
- 需要动态切换产品族的地方
2.4 建造者(Builder)模式
2.4.1 案例引入
老板:又签订了一个新合同,XX公司将宝马和奔驰两款车辆模型都交给我们公司制作了。不过这次有了新的需求: 汽车的启动、停止、鸣笛、引擎声音都由客户自己控制,他们想要什么顺序就什么顺序,OK吗?
我:OK!
来分析一下需求:宝马和奔驰两款车辆模型都是产品,他们有共有的属性,XX公司关心的是每个模型的运行过程, 期待奔驰A模型先有引擎声音再鸣笛,奔驰B模型是先启动再有引擎声音。老板的意思就是满足客户所有要求,要 什么顺序立刻就生成什么顺序的模型出来,而且能批量生成出来。
使用程序模拟实现这一需求:
image.png image.png image.png image.png image.png看到运行结果,满足了一个需求,如果还有更多的不同顺序的需求怎么办呢?不停的写场景类来满足吗?很显然这 是一个问题,所以我们就要想一种方案来解决这个问题。
我们为每种产品模型定义一个建造者,要创建什么顺序直接通知建造者,由建造者来建造。使用程序模拟一下:
image.png image.png image.png同样运行顺序的宝马车也出来了,而且代码比第一版直接访问产品类简单清晰。
我们在做项目的时候要知道:需求不可能一成不变的。我们案例中的4个过程(start stop alarm engineboom)按 照组合有很多种。客户可以随意组合,它是上帝,想要什么顺序我就要生成什么顺序的车模。怎么办呢?我们就需 要封装一下,找个导演来指挥各个事件的先后顺序,然后为每种顺序指定一个代码,你要什么我们立刻就可以提 供。
image.png image.png有了这样的导演类之后,我们的场景类就更容易处理了。而且代码变得简单清晰。
其实我们上面用的就是建造者模式!
其实生活中还有更多的这样的案例。例如,计算机是由 OPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、 鼠标等部件组装而成的,采购员不可能自己去组装计算机,而是将计算机的配置要求告诉计算机销售公司,计算机 销售公司安排技术人员去组装计算机,然后再交给要买计算机的采购员。
再例如游戏中的不同角色,其性别、个性、能力、脸型、体型、服装、发型等特性都有所差异;还有汽车中的方向 盘、发动机、车架、轮胎等部件也多种多样;每封电子邮件的发件人、收件人、主题、内容、附件等内容也各不相 同。
以上所有这些产品都是由多个部件构成的,各个部件可以灵活选择,但其创建步骤都大同小异。这类产品的创建无 法用前面介绍的工厂模式描述,只有建造者模式可以很好地描述该类产品的创建。
2.4.2 模式的定义与结构
2.4.2.1 建造者模式的定义:
指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者 模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组 成部分是不变的,但每一部分是可以灵活选择的。
2.4.2.2 模式的结构与实现
1. 模式的结构
建造者模式的主要角色如下。
- 产品(Product)类:它是包含多个组成部件的复杂对象,由具体建造者来创建其各个部件。
- 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产 品的方法 getResult()。
- 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
- 导演(Director)类:它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具 体产品的信息。
2. 模式的实现
2.4.3 建造者模式的优缺点
优点:
- 各个具体的建造者相互独立,有利于系统的扩展。
- 客户端不必知道产品内部组成的细节,便于控制细节风险。
缺点:
- 产品的组成部分必须相同,这限制了其使用范围。
- 如果产品的内部变化复杂,该模式会增加很多的建造者类。
建造者(Builder)模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零 部件的创建过程,但两者可以结合使用。
2.4.4 模式的应用场景
建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算 法却相对稳定,所以它通常在以下场合使用。
- 相同的方法,不同的执行顺序,产生不同的实践结果
- 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
- 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立 的。