177230_23种设计模式详解笔记Part1

零、讲在前面

1.学习基础要求:

(1)对JavaEE尤其面向对象思想有较为深刻的理解和使用经验;

(2)熟练掌握eclipse或idea使用;

(3)对Java有良好的编程习惯。

2.课程说明:

​ 本课程主要讲解设计模式思想,对于开发工具使用、语法、编程习惯、代码细节不做过多讲解。

​ 课程使用环境及工具:jdk1.8 + idea2018.2.4

3.课程介绍

一、设计模式概述

1.设计模式概念:

​ 设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。

​ 使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

2.设计模式简介:

​ 设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

​ 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。

3.六大设计原则:

为什么要提倡“Design Pattern”呢?根本原因是为了代码复用,增加可维护性。那么怎么才能实现代码复用呢?面向对象有几个原则:单一职责原则 (Single Responsiblity Principle SRP)开闭原则(Open Closed Principle,OCP)、里氏代换原则(Liskov Substitution Principle,LSP)、依赖倒转原则(Dependency Inversion Principle,DIP)、接口隔离原则(Interface Segregation Principle,ISP)、合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)、最小知识原则(Principle of Least Knowledge,PLK,也叫迪米特法则)。开闭原则具有理想主义的色彩,它是面向对象设计的终极目标。其他几条,则可以看做是开闭原则的实现方法。

(1)总原则:开闭原则(Open Close Principle)

​ 开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代 码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等,后面的具体设计中我们会提到这点。 单一职责原则不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。

(2)里氏替换原则(Liskov Substitution Principle)

​ 里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP 是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实 现抽象化的具体步骤的规范。里氏替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。

(3)依赖倒转原则(Dependence Inversion Principle)

​ 这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。

(4)接口隔离原则(Interface Segregation Principle)

​ 这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口 拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。

(5)迪米特法则(最少知道原则)(Demeter Principle)

​ 就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装方法的内部,通过 public 方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。 最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部 变量出现在类中。

(6)合成复用原则(Composite Reuse Principle)

​ 原则是尽量首先使用合成/聚合的方式,而不是使用继承。

4.设计模式的分类:

总体来说设计模式分为三大类:

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

其实还有两类:并发型模式和线程池模式,以图的形式表示:

1554254661859

二.创建型模式(5种)

0.简单工厂模式:

​ 简单工厂模式是属于创建型模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。简单工厂一般分为:普通简单工厂、多方法简单工厂、静态方法简单工厂。

0.1 普通简单工厂:

img

举例如下:(我们举一个发送邮件和短信的例子)

首先,创建二者的共同接口:

1
2
3
public interface Sender {
public void Send();
}

其次,创建实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MailSender implements Sender {
@Override
public void Send() {
System.out.println("this is mailsender!");
}
}

public class SmsSender implements Sender {

@Override
public void Send() {
System.out.println("this is sms sender!");
}
}

最后,建工厂类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SendFactory {

public Sender produce(String type) {
if ("mail".equals(type)) {
return new MailSender();
} else if ("sms".equals(type)) {
return new SmsSender();
} else {
System.out.println("请输入正确的类型!");
return null;
}
}
}

我们来测试下:

1
2
3
4
5
6
7
8
public class FactoryTest {

public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produce("sms");
sender.Send();
}
}

输出:this is sms sender!

0.2 多方法简单工厂:

img

将上面的代码做下修改,改动下SendFactory类就行,如下:

1
2
3
4
5
6
7
8
9
10
public class SendFactory {

public Sender produceMail(){
return new MailSender();
}

public Sender produceSms(){
return new SmsSender();
}
}

测试类如下:

1
2
3
4
5
6
7
8
public class FactoryTest {

public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produceMail();
sender.Send();
}
}

输出:this is mailsender!

0.3 静态方法简单工厂:

将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。

1
2
3
4
5
6
7
public class FactoryTest {
public static void main(String[] args) {
SendFactory factory = new SendFactory();
Sender sender = factory.produceMail();
sender.Send();
}
}
1
2
3
4
5
6
public class FactoryTest {
public static void main(String[] args) {
Sender sender = SendFactory.produceMail();
sender.Send();
}
}

​ 输出:this is mailsender!

0.4 简单工厂模式的优缺点:

优点:

  • 很明显,简单工厂的特点就是“简单粗暴”,通过一个含参的工厂方法,我们可以实例化任何产品类,上至飞机火箭,下至土豆面条,无所不能。所以简单工厂有一个别名:上帝类。

缺点:

  • 任何”东西“的子类都可以被生产,负担太重。当所要生产产品种类非常多时,工厂方法的代码量可能会很庞大
  • 在遵循开闭原则(对拓展开放,对修改关闭)的条件下,简单工厂对于增加新的产品,无能为力。因为增加新产品只能通过修改工厂方法来实现。

1.工厂方法模式:

1.1 理解:

​ 简单工厂模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了开闭原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到工厂方法模式,创建一个工厂接口和创建多个工厂实现类,这样一旦需要增加新的功能, 直接增加新的工厂类就可以了,不需要修改之前的代码。

1.2 代码案例:

img

1554366555020

Sender接口、SmsSender类、MailSender类同简单工厂,无需修改。

创建Provider接口,produce()抽象方法:

1
2
3
public interface Provider {
Sender produce();
}

创建Provider接口的实现类:

1
2
3
4
5
6
public class MailSenderFactory implements Provider{
@Override
public Sender produce() {
return new MailSender();
}
}
1
2
3
4
5
6
public class SmsSenderFactory implements Provider{
@Override
public Sender produce() {
return new SmsSender();
}
}

1.3 工厂方法模式的优缺点:

优点:

  • 工厂方法模式就很好的减轻了工厂类的负担,把某一类/某一种东西交由一个工厂生产;(对应简单工厂的缺点1)
  • 同时增加某一类”东西“并不需要修改工厂类,只需要添加生产这类”东西“的工厂即可,使得工厂类符合开闭原则。

缺点:

  • 相比简单工厂,实现略复杂。
  • 对于某些可以形成产品族的情况处理比较复杂(相对抽象工厂)。

2.抽象工厂模式:

2.1 理解:

​ 抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。根据里氏替换原则,任何接受父类型的地方,都应当能够接受子类型。因此,实际上系统所需要的,仅仅是类型与这些抽象产品角色相同的一些实例,而不是这些抽象产品的实例。换言之,也就是这些抽象产品的具体子类的实例。工厂类负责创建抽象产品的具体子类的实例。

2.2 代码案例:

1554366685054

Sender接口、SmsSender类、MailSender类同简单工厂,无需修改。

与Sender接口与实现类相似,创建Call接口及其实现类WXCall、QQCall,并创建call()方法,实现类对其进行重写:

1
2
3
public interface Call {
void call();
}
1
2
3
4
5
6
public class QQCall implements Call {
@Override
public void call() {
System.out.println("This is QQCall");
}
}
1
2
3
4
5
6
public class WXCall implements Call {
@Override
public void call() {
System.out.println("This is WXCall");
}
}

创建产品族工厂接口ProviderPlus,编写创建Call、Sender接口实现类对象的方法produceCall()、produceSender():

1
2
3
4
public interface ProviderPlus {
Call produceCall();
Sender produceSender();
}

创建ProviderPlus接口实现类,即产品族工厂:

1
2
3
4
5
6
7
8
9
10
11
public class Phone1Factory implements ProviderPlus{
@Override
public Call produceCall() {
return new QQCall();
}

@Override
public Sender produceSender() {
return new MailSender();
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Phone2Factory implements  ProviderPlus{
@Override
public Call produceCall() {
return new WXCall();
}

@Override
public Sender produceSender() {
return new SmsSender();
}
}

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {
public static void main(String[] args) {
Phone1Factory phone1Factory = new Phone1Factory();
Call call = phone1Factory.produceCall();
Sender sender = phone1Factory.produceSender();
call.call();
sender.send();

System.out.println("============================");

Phone2Factory phone2Factory = new Phone2Factory();
Call call1 = phone2Factory.produceCall();
Sender sender1 = phone2Factory.produceSender();
call1.call();
sender1.send();

}
}

2.3 抽象工厂模式的优缺点:

​ 优点:

  • 抽象工厂模式隔离了具体类的生产,使得客户并不需要知道什么被创建。
  • 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
  • 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。

​ 缺点:

  • 增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类。

3.单例模式:

3.1 理解:

​ 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

​ 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

3.2 代码案例:

(a)饿汉模式:

​ 这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。

这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Person1 {
// 1.将构造方法私有——防止外界创建对象
private Person1(){
super();
}
// 2.创建自己的对象
private static Person1 person1 = new Person1();

// 3.给外界访问方式
public static Person1 getInstance(){
return person1;
}
}

(b)懒汉模式:

​ 这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。

getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Person2 {
// 1.将构造方法私有——防止外界创建对象
private Person2(){
super();
}
// 2.创建自己的对象
private static Person2 person2;
/**
* 3.给外界访问方式
* (1)创建对象
* (2)之前已经被创建过,直接使用之前的对象
*/
public static Person2 getInstance(){
if(person2 == null){// 第一次创建时
person2 = new Person2();
}
// 如果之前已经被创建过,直接使用之前的对象
return person2;
}
}

(c)懒汉模式(线程安全):

​ 这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
​ 它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance ()方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Person4 {
// 1.将构造方法私有——防止外界创建对象
private Person4(){
super();
}
// 2.创建自己的对象
private static Person4 person2;

/**
* 3.给外界访问方式
* 方法添加线程锁synchronized保证线程安全
*/
public static synchronized Person4 getInstance(){
if(person2 == null){// 第一次创建时
person2 = new Person4();
}
// 如果之前已经被创建过,直接使用之前的对象
return person2;
}
}

(d)懒汉模式(双重锁):

​ 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Person5 {
// 1.将构造方法私有——防止外界创建对象
private Person5(){
super();
}
// 2.创建自己的对象
private static Person5 person2;
/**
* 3.给外界访问方式
* (1)创建对象
* (2)之前已经被创建过,直接使用之前的对象
*/
public static Person5 getInstance(){
if(person2 == null){
synchronized (Person5.class){
if(person2 == null){
person2 = new Person5();
}
}
}
// 如果之前已经被创建过,直接使用之前的对象
return person2;
}
}

(e)登记式/静态内部类:

​ 这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Person6 {
// 1.将构造方法私有——防止外界创建对象
private Person6(){
super();
}
// 2.创建自己的对象(内部类)
private static class SinglePerson{
private static final Person6 PERSON = new Person6();
}
/**
* 3.给外界访问方式
*/
public static Person6 getInstance(){
return SinglePerson.PERSON;
}
}

3.3 单例模式的优缺点:

​ 优点:

  • 1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
  • 2、省去了 new 操作符,降低了系统内存的使用频率,减轻 GC 压力。
  • 3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完 全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例 模式,才能保证核心交易服务器独立控制整个流程

​ 缺点:

  • 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

4.建造者模式:

4.1 建造者模式概念:

​ 建造者模式很容易让人想到建房子,不管建刚需房、改善房还是别墅,它们都离不开地基、柱子、层面和墙体这些组成部分,建筑工人就是把这些组成部分一个个建起来,最后连成一体建出一栋栋楼房。

​ 来看看建造者模式的定义,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建房子的过程都是相似的,但可以建出形形色色的房子。

builder

5.2 代码案例:

bike

bikebuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* @Auther: Xulu
* @ProjectName: Builder1Pattern
* @Date: 2019/5/17 17:18
* @Description:自行车类
*/
public class Bike {
// 车架
private String frame;
// gps
private String gps;
// 轮胎
private String tyre;


public String getFrame() {
return frame;
}

public void setFrame(String frame) {
this.frame = frame;
}

public String getGps() {
return gps;
}

public void setGps(String gps) {
this.gps = gps;
}

public String getTyre() {
return tyre;
}

public void setTyre(String tyre) {
this.tyre = tyre;
}

@Override
public String toString() {
return "Bike{" +
"frame='" + frame + '\'' +
", gps='" + gps + '\'' +
", tyre='" + tyre + '\'' +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @Auther: Xulu
* @ProjectName: Builder1Pattern
* @Date: 2019/5/17 17:25
* @Description:自行车生产线接口
*/
public interface BikeBuilder {

// 组装轮胎
public abstract void buildTyres();
// 组装车架
public abstract void buildFrame();
// 装gps
public abstract void buildGps();
// 成品输出
public abstract Bike getBike();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* @Auther: Xulu
* @ProjectName: Builder1Pattern
* @Date: 2019/5/17 17:29
* @Description:摩拜生产线
*/
public class MoBikeBuilder implements BikeBuilder {
private Bike bike = new Bike();
@Override
public void buildTyres() {
bike.setTyre("装非充气轮胎");
System.out.println("装非充气轮胎");
}

@Override
public void buildFrame() {
bike.setFrame("装橙色车架");
System.out.println("装橙色车架");
}

@Override
public void buildGps() {
bike.setGps("装MoBike的gps");
System.out.println("装MoBike的gps");
}

@Override
public Bike getBike() {
return bike;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* @Auther: Xulu
* @ProjectName: Builder1Pattern
* @Date: 2019/5/17 17:32
* @Description:ofo生产线
*/
public class OfoBuilder implements BikeBuilder {
private Bike bike = new Bike();
@Override
public void buildTyres() {
bike.setTyre("装充气轮胎");
System.out.println("装充气轮胎");
}

@Override
public void buildFrame() {
bike.setFrame("装明黄色车架");
System.out.println("装明黄色车架");
}

@Override
public void buildGps() {
bike.setGps("装Ofo的gps");
System.out.println("装Ofo的gps");
}

@Override
public Bike getBike() {
return bike;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @Auther: Xulu
* @ProjectName: Builder1Pattern
* @Date: 2019/5/17 17:24
* @Description:指挥者(工程部)
*/
public class EnginnerDepartment {
// 指定生产线
BikeBuilder bikeBuilder;

public EnginnerDepartment(BikeBuilder bikeBuilder){
this.bikeBuilder = bikeBuilder;
}

/**
* 指导组装单车
*/
public void construct(){
bikeBuilder.buildFrame();
bikeBuilder.buildTyres();
bikeBuilder.buildGps();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @Auther: Xulu
* @ProjectName: Builder1Pattern
* @Date: 2019/5/17 17:37
* @Description:
*/
public class Test {
public static void main(String[] args) {

// 生产ofo
OfoBuilder ofoBuilder = new OfoBuilder();
EnginnerDepartment enginnerDepartment = new EnginnerDepartment(ofoBuilder);
enginnerDepartment.construct();// 指导组装
Bike ofoBuilderBike = ofoBuilder.getBike();
System.out.println(ofoBuilderBike);

// 生产MoBike
MoBikeBuilder moBikeBuilder = new MoBikeBuilder();
EnginnerDepartment enginnerDepartment1 = new EnginnerDepartment(moBikeBuilder);
enginnerDepartment1.construct();
Bike moBikeBuilderBike = moBikeBuilder.getBike();
System.out.println(moBikeBuilderBike);

}
}

5.3 建造者模式优缺点:

1.优点

  (1)、产品的建造和表示分离,实现了解耦。

  (2)、隐藏了产品的建造细节,用户只需关心产品的表示,而不需要了解是如何创建产品的。

  (3)、体现了开闭原则,如上代码所示,如果需要再生产其他共享单车,只需要再开一条生产线即可,不影响其他生产线的作业。

 2.缺点

  (1)、当建造者过多时,会产生很多类,难以维护。

 建造者模式的使用场合是当创建复杂对象时,把创建对象成员和装配方法分离出来,放在建造者类中去实现,用户使用该复杂对象时,不用理会它的创建和装配过程,只关心它的表示形式。其实完全理解这个模式还是要一番思考的,难以搞懂的是指挥者似乎没什么存在的必要,在代码里也没体现它的作用,我们也可以把指挥者的方法放在建造者里面,但为什么没有这样做呢?我想这可能是考虑到单一责任原则,建造者只负责创建对象的各个部分,至于各个部分创建的顺序、装配方法它就不管了。还有就是当顺序要改变时,建造者可以不用改动,改动指挥者就好了,指挥者只有一个,建造者有很多,要改建造者就麻烦了。

5.原型模式:

5.1 原型模式概念:

​ 原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

​ 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

5.2 代码案例:

以批量发送邮件为例:

AdvTemplete广告:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class AdvTemplete {
// 广告信名称
private String advSubject = "广告信名称";
// 广告信内容
private String advContent = "广告信内容";

/**
* 获取广告信名称
* @return
*/
public String getAdvSubject() {
return advSubject;
}

/**
* 获取广告信内容
* @return
*/
public String getAdvContent() {
return advContent;
}

}

Mail类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public class Mail implements Cloneable{
// 售收件人
private String receiver;
// 邮件名称
private String subject;
// 称谓
private String appellation;
// 邮件内容
private String content;
// 邮件尾部
private String tail;

@Override
protected Mail clone() {
Mail mail = null;
try {
mail = (Mail) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return mail;
}

/**
* 构造方法
* @param advTemplete
*/
public Mail(AdvTemplete advTemplete) {
this.content = advTemplete.getAdvContent();
this.subject = advTemplete.getAdvSubject();
}


// ======================setter,getter=======================================

public String getReceiver() {
return receiver;
}

public void setReceiver(String receiver) {
this.receiver = receiver;
}

public String getSubject() {
return subject;
}

public void setSubject(String subject) {
this.subject = subject;
}

public String getAppellation() {
return appellation;
}

public void setAppellation(String appellation) {
this.appellation = appellation;
}

public String getContent() {
return content;
}

public void setContext(String content) {
this.content = content;
}

public String getTail() {
return tail;
}

public void setTail(String tail) {
this.tail = tail;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class Client {
// 设置邮件发送数
private static int MAX_COUNT = 6;

/**
* 模拟邮件发送
* @param args
*/
public static void main(String[] args) {
int i = 0;

// 邮件创建
Mail mail = new Mail(new AdvTemplete());
mail.setTail(" ");
while(i < MAX_COUNT){
// 克隆
Mail mailClon = mail.clone();

// 设置称谓
mailClon.setAppellation(getRandString(5) + "先生/女士");
// 设置接收信箱
mailClon.setReceiver(getRandString(5) + "@" + getRandString(3) + ".com");
// 设置邮件内容
mailClon.setContext(getRandString(20));

// 发送邮件
sendMail(mailClon);
i++;
}
}

/**
* 发送邮件
*/
public static void sendMail(Mail mail){
// 打印邮件相关信息
System.out.println("标题:" + mail.getSubject() + "\t收件人:"
+ mail.getReceiver() + "\t内容:" + mail.getContent()
+ "\t....发送成功!");
}

/**
* 模拟生成字符串
* @param maxLength
* @return
*/
public static String getRandString(int maxLength){
String source = "abcdefghijklmnopqrstuvwxyz";
// 定义字符串缓冲区
StringBuilder sb = new StringBuilder();
// 定义随机数生成器
Random random = new Random();
// 循环
for(int i =0; i < maxLength; i++){
sb.append(source.charAt(random.nextInt(source.length())));
}
return sb.toString();
}

}

5.3 原型模式的优缺点:

优点:

• 使用原型模式创建对象比直接new一个对象更有效

• 隐藏制造新实例的复杂性

• 重复地创建相似对象时可以考虑使用原型模式

缺点:

• 每一个类必须配备一个克隆方法

• 深拷贝比较复杂

5.5 适用场景:

• 复制对象的结构与数据;

• 希望对目标对象的修改不影响既有的原型对象;

• 创建对象成本较大的情况下。

5.4 注意事项:

  • 使用原型模式复制对象不会调用类的构造方法。所以,单例模式与原型模式是冲突的,在使用时要特别注意。

  • 在原型模式中,成员变量不能被final修饰

  • Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
public class Mail implements Cloneable{
// 售收件人
private String receiver;
// 邮件名称
private String subject;
// 称谓
private String appellation;
// 邮件内容
private String content;
// 邮件尾部
private String tail;

private int[] arr;


@Override
protected Mail clone() {
Mail mail = null;
try {
mail = (Mail) super.clone();
mail.arr = this.arr.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return mail;
}

/**
* 构造方法
* @param advTemplete
*/
public Mail(AdvTemplete advTemplete) {
this.content = advTemplete.getAdvContent();
this.subject = advTemplete.getAdvSubject();
}


// ======================setter,getter=======================================
public int[] getArr() {
return arr;
}

public void setArr(int[] arr) {
this.arr = arr;
}

public void setContent(String content) {
this.content = content;
}


public String getReceiver() {
return receiver;
}

public void setReceiver(String receiver) {
this.receiver = receiver;
}

public String getSubject() {
return subject;
}

public void setSubject(String subject) {
this.subject = subject;
}

public String getAppellation() {
return appellation;
}

public void setAppellation(String appellation) {
this.appellation = appellation;
}

public String getContent() {
return content;
}

public void setContext(String content) {
this.content = content;
}

public String getTail() {
return tail;
}

public void setTail(String tail) {
this.tail = tail;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public class Client {
// 设置邮件发送数
private static int MAX_COUNT = 6;

/**
* 模拟邮件发送
* @param args
*/
public static void main(String[] args) {
int i = 0;

// 邮件创建
Mail mail = new Mail(new AdvTemplete());
mail.setTail(" ");

int[] arr = new int[]{1,2,3};
mail.setArr(arr);

while(i < MAX_COUNT){
// 克隆
Mail mailClon = mail.clone();

// 设置称谓
mailClon.setAppellation(getRandString(5) + "先生/女士");
// 设置接收信箱
mailClon.setReceiver(getRandString(5) + "@" + getRandString(3) + ".com");
// 设置邮件内容
mailClon.setContext(getRandString(20));

if(i == 3){
arr[0] = 9;
arr[1] = 9;
arr[2] = 9;
}
// 发送邮件
sendMail(mailClon);
i++;
}
}

/**
* 发送邮件
*/
public static void sendMail(Mail mail){
// 打印邮件相关信息
System.out.println("标题:" + mail.getSubject() + "\t收件人:"
+ mail.getReceiver() + "\t内容:" + mail.getContent()
+ "\t....发送成功!");
System.out.println("arr: " + mail.getArr() + Arrays.toString(mail.getArr()));

}

/**
* 模拟生成字符串
* @param maxLength
* @return
*/
public static String getRandString(int maxLength){
String source = "abcdefghijklmnopqrstuvwxyz";
// 定义字符串缓冲区
StringBuilder sb = new StringBuilder();
// 定义随机数生成器
Random random = new Random();
// 循环
for(int i =0; i < maxLength; i++){
sb.append(source.charAt(random.nextInt(source.length())));
}
return sb.toString();
}

}