7. 原型模式
7.1 原型模式(克隆羊)
- 原型模式是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。
- 原型模式是一种创建型设计模式,允许一个对象在创建另一个可定制的对象,无需知道如何创建的细节。
- 工作原理是:通过将一个原型对象传递给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝他们自己来实施创建,即 对象.clone()
7.2 原型模式(浅拷贝)
- 深拷贝解决的问题是在克隆对象的时候应该如何处理。
- 使用浅拷贝方法拷贝的对象,并不会新克隆出一个对象,克隆出来的对象和原始对象指向同一个地址,也就是说克隆出来的对象和被克隆的对象是同一个对象。
- 上边的克隆羊就是属于浅拷贝。(不会创建出新的对象)
7.3 原型模式(深拷贝)
- 深拷贝的核心就是 克隆出来的对象是一个全新的对象,有自己的内存空间。
7.4 JDK中源码分析
- Spring中原型bean的创建,就是使用原型模式的应用。
8.建造者模式
建造者模式介绍
- 建造者模式又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
- 建造者模式 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型个内容就就可以构建他们,用户不需要知道内部的具体构建细节。
盖房项目需求
- 需要建房子:这一过程为打桩、砌墙、封顶。
- 房子有各种各样的类型:普通房、别墅、高楼大厦,各种房子的建造过程虽然一样,但是要求不一样。
使用普通模式实现盖房子需求
我们可以采取继承的方式来构建不同的房子。
- 优点:比较好理解,简单易于操作。
- 缺点:这种程序结构,设计简单,没有设计缓存层对象,程序的扩展和维护不好,也就是说,这种设计方案,把产品(即:房子)和创建产品的过程(即:建房子流程)封装在一起,耦合度增强了。
- 解决方案:将产品与产品建造的过程解耦==》建造者模式。
使用构造者模式实现盖房子的需求
- 以下是盖房子时是用建造者模式的类图
建造者模式
- 客户端(使用程序)不必知道产品内部组件的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 每一个具体的建造者都相对独立,而与其他的具体建造者无关,因此可以很方方便地替换具体建造者或增加具体建造者,用户使用不同的具体建造者即可得到不同的产品对象
- 可以更加精细的控制产品的创建过程。 将复杂产品的创建步骤分解在不同方法中,使得创建过程更加清晰,也更加方便使用程序来控制床架过程。
- 增加新的具体的建造者无须修改原有类库的代码,指挥者类针对抽象建造者编程,系统扩展方便,符合 开闭原则
- 建造者模式所创建的产品一般具有较多的共性,其组成部分相似(比如盖房子),如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围有一定的限制 。
- 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,需考虑是否选择建造者模式。
抽象工厂模式VS建造者模式
- 抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品;具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。
8.1 JDK中源码分析
- Java.lang.StringBuilder中使用到建造者模式
9. 适配器模式
9.1 适配器介绍
- 适配器模式(包装器):将某个类的接口转换成客户端期望的另一个接口表示,主要目的是兼容性,让原本因接口因接口不匹配不能在一起工作的两个类可以协同工作。
- 适配器模式属于结构性模式。
- 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式。
9.2 工作原理
- 适配器模式:将一个类的接口转换成另一种接口,让 原本接口不兼容的类可以兼容。
- 从用户的角度看不到被适配者。
- 用户调用适配器转换出来的目标接口方法,适配器再调用被适配者的相关接口方法。
- 用户收到反馈结果,感觉只是和目标接口交互,如图:
9.3 类适配器
类适配器模式介绍
基本介绍:Adapter类,通过继承src类,实现dst类接口,完成src-->dst的适配。
类适配器模式应用案列
以生活中充电器的例子来讲解,充电器本身相当于Adapter,220v交流电相当于src(即被适配者)。我们的目的是dst(即:目标)是5v直流电。
这里VoltagAdapter是适配器类,这个类继承了220v的这个被适配的类,实现了5v的这个接口,Phone是手机类,这个类依赖5v的这个接口其实就是依赖的VoltageAdapter这个适配器类。Clinet是用户类,这个用户依赖手机类和这个适配器类。
- 以上代码实现方式见 adapter类 包
使用类适配器的缺点
- Java是单继承机制,所以类适配器需要继承src类这一点算是一个缺点,因为这要求dst必须是接口,有一定的局限性。
- src类的方法在Adapter中都会暴露出来,也增加了使用的成本。
使用类适配器的优点
- 由于其继承了src类,所以它可以根据需求重写src类的方法,是得Adapter的灵活性增强了。
9.4 对象适配器
对象适配器模式介绍
- 基本介绍:和类适配器模式差不错,只是将Adapter类作为修改,不是继承src类,而是持有src类的实例,以解决兼容性的问题。即:持有src类,实现dst类接口,完成src-->dst的适配。
- 根据”合成复用原则”,在系统中尽量使用关联关系来代替继承关系。
- 对象适配器模式是适配器模式中常用的一种
- 对象适配器和类适配器其实算是一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承src的局限性问题,也不再要求dst必须是接口。
- 使用成本更低,更灵活。
9.5 接口适配器模式
接口适配器模式介绍
- 一些书籍称为:适配者模式或者缺省适配器模式。
- 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中的每个方法提供一个默认实现(空方法),那么该抽象类的子类可以有选择的覆盖重写父类的某些方法来实现需求。
- 适用于一个接口不想使用其所有方法的情况。
- 一下代码是使用的匿名内部类来实现的。因为AbsAdapter 是一个抽象类,不可以直接new出来,需要实现具体的方法。
对适配器的总结
- 适配器的三种命名方式,是根据src(被适配者)是以怎样的形式给到Adapter(在Adapter里的形式,Adapter是适配器类)类命名的。
类适配器:以类给到,在Adapter里,就是将src当作类。(继承)。
对象适配器:以对象给到,在Adapter里,将src作为一个对象。(聚合、组合)。
接口适配器:以接口给到,在Adapter里,将src作为一个接口。(实现) - Adapter模式最大的作用还是将原本不兼容的接口结合在一起工作。
- 实际开发中,实现起来并不拘泥这三种经典的形式。
9.6 JDK中源码分析
- SpringMVC中的HandlerAdapter,就使用了适配器模式
10.桥接模式
10.1 使用普通模式实现问题
问题引入(手机操作问题)
使用传统的方式解决这个问题的类图
使用传统方式解决问题的缺点
- 扩展性问题(类爆炸),如果我们在增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也需要在各个手机样式类下增加。
- 违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本。
10.2 桥接模式介绍
- 桥接模式是指:将实现与抽象放在两个不同的类层次中,是两个层次可以独立改变。
- 桥接模式是一种结构型设计模式。
- 桥接模式基于 类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象与行为实现分离开来,从而可以保持各个部分的独立性以及应对他们的功能扩展。
- 以上 Implementor 是一个接口,Abstraction是一个抽象类,剩下的类都是具体的实现类。
10.3 使用桥接模式实现问题
桥接模式实习的类图
桥接模式的总结
- 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分分别独立出来,这有助于系统进行分层设计,从而产生更好的结构化系统。
- 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
- 桥接模式由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程。
- 桥接模式要求正确的识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性,即需要有这样的应用场景。
桥接模式的使用场景
10.4 JDK中源码分析
- JDBC的Driver接口中,使用到了桥接模式。
11. 装饰者模式
11.1 使用普通模式实现问题
问题引入(咖啡馆)
使用传统方式解决问题类图
- 问题在于这种写法会产生类爆炸。
11.2 装饰者模式介绍
- 装饰者模式:动态的将新的功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(OCP)
装饰者模式的原理
11.3 使用装饰者模式解决星巴克问题
使用装饰者模式解决上述咖啡问题的方案图
装饰者模式下的订单:2分巧克力+1份牛奶的LongBlack(单品咖啡) ,我们生活着的思想是将配料加到咖啡中,但是使用装饰者模式的思想是:将单品咖啡加入到配料中。
11.4 JDK中源码分析
- Java中的IO流中的 FilterInputStream就是一个装饰者
12. 组合模式
12.1 使用普通模式是实现问题
问题引入(学校院系展示需求)
使用传统解决问题的类图使用组合的方式,学校中包含学院,学院中包含系。
使用传统方案的问题分析
- 将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的。
- 实际上我们的要求是:在一个页面中展示出一个学校的院系组成,一个学校有多少个学院,一个学院有多少个系,因此这种解决方案不能很好实现管理的操作。比如对学院、系得添加、删除和遍历等。
- 解决方案:把学校、学院、系都看做是组织结构,他们之间没有继承得关系,而是一个树形结构,可以更好得实现管理操作 ==> 组合模式。
12.2 组合模式简介
- 组合模式,又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示”整体—部分”的层次关系。
- 组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
- 这种类型的设计模式属于结构型设计模式。
- 组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象。
组合模式原理类图
12.3 使用组合模式解决问题
使用组模式解决文艺的类图这里的 OrganizationComponent 可以是接口、抽象类、普通类。
- 简化客户端操作。
- 具有较强的扩展性。
- 方便创建出复杂的层次结构。
- 需要遍历组织机构,或者处理的对象具有树形结构时,非常适合使用组合组合模式。
- 需要较高的抽象性。如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样。不适合使用组合模式。
12.4 JDK中源码分析
- Java中的集合类 HashMap就是使用了组合模式
13.外观模式
13.1 使用普通模式实现问题
问题引入(影院项目管理)
传统方式解决问腿的原理图*
13.2 外观模式介绍
外观模式概念
- 外观模式,也叫过程模式:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加的容易使用。
- 外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,是得调用端只需跟这个接口发生调用,而无需关系这个子系统的内部细节。
外观模式类图
外观模式角色分析
- 外观类(Facade):为调用端提供统一的调用接口,外观类知道哪些子系统负责处理请求, 从而将调用端的请求代理给适当子系统对象。
- 调用者(Client):外观接口的调用者。
- 子系统的集合:指模块或者子系统,处理Facade对象指派的任务,他是功能的实际提供者。
外观模式的原理
12.3 外观模式实现影院管理
实现类图
12.4 外观模式注意事项
- 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性。
- 外观模式对客户端与子系统分耦合关系,让子系统内部的模块更容易的维护和扩展。
- 通过合理的使用外观模式,可以帮助我们更好的规划访问的层次。
- 当系统需要进行分层设计时,可以考虑使用Facade模式。
- 可以使用Facade来提高接口分复用性。
- 不能过多或者不合理的使用外观模式,判断是使用外观模式好,还是使用直接调用的模式好,要让系统有层次,利于维护的目的。
12.5 JDK中源码分析
- MyBatis中的Configuration 去创建MetaObject 对象使用到了外观模式
13.享元模式
13.1 使用传统的方式实现问题
问题引入(网站外包问题)
传统方式实现问题原理
传统方法问题分析
13.2 享元模式介绍
基本介绍
- 享元模式也叫蝇量模式 :运用共享技术有效地支持大量细粒度的对象。
- 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来使用,避免重新创建,如果没有我们需要的,则创建一个。
- 享元模式能够解决 重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率。
- 享元模式 经典的应用场景就是池技术了,Sting常量池、数据库连接池、缓冲池等都是享元模式的应用,享元模式是池技术的重要实现方式。
享元模式的原理类图
- Flyweight:是抽象的享元角色,他是产品的抽象类,同时定义出对象的外部状态和内部状态的接口或实现。
- ConcreteFlyweight:是具体的享元角色,具体的产品类,实现抽象角色定义相关业务。
- UnSharedConcreateFlyweight:是不可共享的角色,一般不会出现在享元工厂。
- FlyweightFactory:享元工厂类,用于构建一个处理器(集合),同时提供从池中获取对象方法
- Client:客户端。
13.3 内部状态和外部状态
- 内部状态:对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变。
- 外部状态:对象得以依赖的一个标记,是随环境的改变而改变的、不可共享的状态。
举个例子:
比如围棋、五子棋、跳棋。棋子的颜色就是内部状态。棋子的坐标就是外部状态。
比如代码内容相同的网页,展示出不同的形式(网页、博客、公众号) 。这里相同的代码就是内部状态,而展现代码的形式就是外部状态
13.4 使用享元模式实现问题
使用享元模式实现问题的类图
13.4享元模式在JDK中的应用
- 注意:在JDK1.8之后,将字符串常量池移到了堆内存中。
- 这里的Hello是共享的数据,即使new了一个String,但是共享的是同一个Hello。
- 在 java.lang.Integer中使用到了享元模式。使用Integer.valueOf(x),而不使用new 。此时只要x的范围在 -127~128 之间就是使用的享元模式。
- 在Integer中存在缓冲区,这个缓冲区的范围就是 -127~128,只要不是new出来的,在这个范围内的Integer对象的地址是一样的。如果超出这个范围会new一个新的对象。
13.5 享元模式的注意事项
- 在享元模式中,“享”代表共享,“元”代表对象。
- 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率。
- 使用享元模式提高了复杂度。需要分离出来内部状态与外部状态,比较繁琐。
- 使用享元模式时,注意划分内部状态与外部状态,并且需要有一个工厂类加以控制。
- 享元模式经典的应用场景就是:缓冲区。比如字符串常量池、数据库连接池。
14.代理模式
14.1 代理模式介绍
- 代理模式:为一个对象提供一个替身,以控制这个对象的访问。 即通过代理对象访问目标对象。这样做的好处:可以在目标对象实现的基础之上,增强额外的功能操作,即扩展目标对象的功能。
- 被代理的对象可以是:远程对象、创建开销大的对象或者需要安全控制的对象。
- 代理模式有不同的形式:静态代理、动态代理(JDK代理、接口代理)和Cglib代理(可以在内存动态的创建对象,而不需要实现接口,他是属于动态代理的范畴。)
代理模式的原理类图
14.2 静态代理
- 静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者继承相同父类。
- 调用的时候通过调用代理对象的方法来调用目标对象。代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法。
静态代理模式的原理类图
静态代理的优缺点
- 优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展。
- 缺点:因为代理对象需要与目标对象实现一样的接口, 所以会有很多代理类。
- 缺点:一旦接口中增加了方法,目标对象与代理对象都要维护(即实现这个方法)
14.3 动态代理
动态代理简介
- 代理对象不需要实现接口,但是目标对象要实现接口,否则不能动态代理。
- 代理对象的生成,是利用的JDK的API(使用的就是反射机制),动态的在内存中构建代理对象。
- 动态代理也叫做:JDK代理、接口代理。
JDK中生成代理对象的API
- 代理类所在包:java.lang.reflect.Proxy
- JDK实现动态代理只需要使用newProxyInstance方法。
动态代理的原理类图
14.4 Cglib代理
Cglib代理介绍
- 静态代理哥JDK动态代理模式都要要求目标对象实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对新子类来实现代理。
- Cglib代理也叫做 子类代理。他是在内存中构建出一个子类对象从而实现对目标对象功能扩展,有些书将Cglib代理归属到动态代理
- Cglib是一个强大的高性能的代码生成包,它可以在运行期间扩展Java类与实现Java接口。它广泛的被许多的AOP的框架使用,例如:Spring AOP,实现方法拦截。
- 在AOP编程中如何选择动态代理模式:
- 目标对象需要实现接口,用JDK代理。
- 目标对象不需要实现接口,用Cglib代理。
- Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类。
Cglib动态代理的实现步骤
注意:这个cglib包需要自己下载之后手动导入,JDK中不存在这个包
Cglib动态代理模式的原理类图
使用方法
以下是被代理类
以下是代理对象
14.5 其他代理模式
- 防火墙代理:内网通过代理穿透防火墙,实现对公网的访问。
- 缓存代理:比如当请求图片文件等资源时,先到缓存代理取,如果取到资源则ok,如果取不到资源,再到公网或者数据库取,然后缓存。
- 远程代理:远程对象的本地代表,通过它可以 把远程对象当本地对象来调用。远程代理通过网络和正真的远程对象沟通信息。
- 同步代理:主要使用在多线程编程中,完成多线程间同步工作。
15.模板方法模式
15.1 使用传统模式解决问题
问题引入
15.2 模板方法模式介绍
模板方法模式简介
- 模板方法模式又叫 模板模式,在一个抽象类公开定义了执行它的方法的模板。它的子类可以按照需要重写方法实现,但调用将以抽象类中定义的方法进行。
- 简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定的步骤。
- 这种类型的设计模式属于行为型模式。
模板方法的原理类图
模板方法模式的角色及职务的分析
- AbstractClass(抽象类):类中实现了模板方法(template),定义了算法的骨架,具体子类需要去实现其他的抽象方法operation2,3,4.
- ConcreteClass 实现抽象方法operation2,3,4,以完成算法中特定子类的步骤。
15.3 使用模板方法模式实现问题
使用模板实现豆浆问题的类图
核心代码
15.4 模板模式中的钩子方法
钩子方法简介
- 在模板方法模式的父类中,我们可以定义一个方法,这个方法默认不做任何的事情,子类可以根据情况看要不要覆盖它,该方法称为”钩子“。
- 比如我们此时需要纯豆浆,不添加任何的配料,使用钩子方法解决问题。
15.5 模板方法模式在JDK中的应用
- Spring IOC容器中初始化时运用到了模板方法模式。
15.6 模板方法模式注意和细节
- 基本思想:算法只存在于一个地方,也就是父类中,容易修改。 需要修改算法时,只要修改父类的模板方法或者已实现的某些步骤,子类会继承这些修改。
- 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
- 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由于类提供部分步骤的实现。
- 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
- 一般将模板方法加上final,防止子类重写模板方法。
15.7模板方法模式的使用场景
当要完成某个过程,该过程要执行一系列步骤,这一系列的步骤基本相同,但其个别步骤实现时可能不同,通常考虑使用模板方法模式来处理。