1.1 适配器模式
1.1.1 适配器模式基本介绍
- 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
- 适配器模式属于结构型模式
-
1.1.2 适配器模式工作原理
适配器模式:将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼容
- 从用户的角度看不到被适配者,是解耦的
- 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
- 用户收到反馈结果,感觉只是和目标接口交互,如图
1.1.3 类适配器模式
1.1.3.1 类适配器模式介绍
Adapter类,通过继承 src类,实现 dst 类接口,完成src->dst的适配。
1.1.3.2 类适配器模式应用实例
- 应用实例说明
以生活中充电器的例子来讲解适配器,充电器本身相当于Adapter,220V交流电相当于src (即被适配者),我们的目dst(即 目标)是5V直流电。
- 思路分析(类图)
- 代码实现
被适配的类
package com.java.adapter.classadapter;
// 被适配的类
public class Voltage220V {
// 输出220v电压
public int output220V() {
int src = 220;
System.out.println("电压=" + src + "伏");
return src;
}
}
适配的接口
package com.java.adapter.classadapter;
// 适配的接口
public interface IVoltage5V {
public int output5V();
}
适配器类
package com.java.adapter.classadapter;
// 适配器类
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
// 获得220v电压
int srcV = output220V();
int dstV = srcV / 44 ;
return dstV;
}
}
手机
package com.java.adapter.classadapter;
public class Phone {
public void charging(IVoltage5V iVoltage5V) {
if(iVoltage5V.output5V() == 5) {
System.out.println("电压为5V, 可以充电~~");
} else if (iVoltage5V.output5V() > 5) {
System.out.println("电压大于5V, 无法充电~~");
}
}
}
客户端
package com.java.adapter.classadapter;
public class Client {
public static void main(String[] args) {
Phone phone = new Phone();
phone.charging(new VoltageAdapter());
}
}
1.1.3.3 类适配器模式注意事项和细节
- Java是单继承机制,所以类适配器需要继承src类这一点算是一个缺点, 因为这要求dst必须是接口,有一定局限性;
- src类的方法在Adapter中都会暴露出来,也增加了使用的成本。
由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了。
1.1.4 对象适配器模式
1.1.4.1 对象适配器模式介绍
基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而是持有src类的实例,以解决兼容性的问题。 即:持有 src类,实现 dst 类接口,完成src->dst的适配
- 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系。
-
1.1.4.2 对象适配器模式应用实例
应用实例说明
以生活中充电器的例子来讲解适配器,充电器本身相当于Adapter,220V交流电相当于src (即被适配者),我们的目dst(即目标)是5V直流电,使用对象适配器模式完成。
- 思路分析(类图):只需修改适配器即可, 如下:
- 代码实现
被适配的类
package com.java.adapter.objectadapter;
// 被适配的类
public class Voltage220V {
// 输出220v电压
public int output220V() {
int src = 220;
System.out.println("电压=" + src + "伏");
return src;
}
}
适配的接口
package com.java.adapter.objectadapter;
// 适配的接口
public interface IVoltage5V {
public int output5V();
}
适配器类
package com.java.adapter.objectadapter;
// 适配器类
public class VoltageAdapter implements IVoltage5V {
private Voltage220V voltage220V; // 聚合关系
public VoltageAdapter(Voltage220V voltage220v) {
this.voltage220V = voltage220v;
}
@Override
public int output5V() {
int dst = 0;
if(null != voltage220V) {
int src = voltage220V.output220V();//
System.out.println("使用对象适配器,进行适配~~");
dst = src / 44;
System.out.println("适配完成,输出的电压为=" + dst);
}
return dst;
}
}
手机
package com.java.adapter.objectadapter;
public class Phone {
public void charging(IVoltage5V iVoltage5V) {
if(iVoltage5V.output5V() == 5) {
System.out.println("电压为5V, 可以充电~~");
} else if (iVoltage5V.output5V() > 5) {
System.out.println("电压大于5V, 无法充电~~");
}
}
}
客户端
package com.java.adapter.objectadapter;
public class Client {
public static void main(String[] args) {
Phone phone = new Phone( );
phone.charging(new VoltageAdapter(new Voltage220V()));
}
}
1.1.4.3 对象适配器模式注意事项和细节
- 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承src的局限性问题,也不再要求dst必须是接口。
-
1.1.5 接口适配器模式
1.1.5.1 接口适配器模式介绍
一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。
- 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
-
1.1.5.2 接口适配器模式应用实例
思路分析(类图)
- 代码实现
接口
package com.java.adapter.interfaceadapter;
public interface Interface4 {
public void m1();
public void m2();
public void m3();
public void m4();
}
抽象类
package com.java.adapter.interfaceadapter;
// 在AbsAdapter将Interface4的方法进行默认实现
public abstract class AbsAdapter implements Interface4 {
public void m1() {
}
public void m2() {
}
public void m3() {
}
public void m4() {
}
}
客户端
package com.java.adapter.interfaceadapter;
public class Client {
public static void main(String[] args) {
AbsAdapter absAdapter = new AbsAdapter() {
@Override
public void m1() {
System.out.println("使用了m1方法");
}
};
absAdapter.m1();
}
}
1.1.6 适配器模式的注意事项和细节
- 三种命名方式,是根据 src是以怎样的形式给到Adapter(在Adapter里的形式)来命名的。
- 类适配器:以类给到,在Adapter里,就是将src当做类,继承对象适配器:以对象给到,在Adapter里,将src作为一个对象,持有接口适配器:以接口给到,在Adapter里,将src作为一个接口,实现
Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作。
1.2 桥接模式
1.2.1 手机问题
现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等)
传统方案解决手机使用问题(类图):
传统方案解决手机操作问题分析扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。
- 违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本.
-
1.2.2 桥接模式(Bridge)-基本介绍
桥接模式(Bridge模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
- 是一种结构型设计模式
Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展
1.2.3 桥接模式(Bridge)-原理类图
原理类图说明:Client类:桥接模式的调用者
- 抽象类(Abstraction):维护了Implementer/即它的实现类ConcreteImplementorA,二者是聚合关系,Abstract充当桥接类
- RefindAbstraction:是Abstraction抽象类的子类
- Implementor:行为实现类的接口
- ConcreteImplementorA/B:行为的具体实现类
从UML类图:这里的抽象类和接口是聚合的关系,其实是调用和被调用关系。
1.2.4 桥接模式解决手机操作问题
思路分析图解-类图
- 代码实现
品牌:
package com.java.birdge;
// 接口
public interface Brand {
void open();
void close();
void call();
}
package com.java.birdge;
public class Vivo implements Brand {
@Override
public void open() {
System.out.println("Vivo手机开机");
}
@Override
public void close() {
System.out.println("Vivo手机关机");
}
@Override
public void call() {
System.out.println("Vivo手机打电话");
}
}
package com.java.birdge;
public class XiaoMi implements Brand {
@Override
public void open() {
System.out.println("小米手机开机");
}
@Override
public void close() {
System.out.println("小米手机关机");
}
@Override
public void call() {
System.out.println("小米手机打电话");
}
}
样式:
package com.java.birdge;
public abstract class Phone {
// 聚合品牌
private Brand brand;
public Phone(Brand brand) {
super();
this.brand = brand;
}
protected void open() {
brand.open();
}
protected void close() {
brand.close();
}
protected void call() {
brand.call();
}
}
package com.java.birdge;
public class FoldedPhone extends Phone {
public FoldedPhone(Brand brand) {
super(brand);
}
public void open() {
super.open();
System.out.println("折叠手机");
}
public void close() {
super.close();
System.out.println("折叠手机");
}
public void call() {
super.call();
System.out.println("折叠手机");
}
}
package com.java.birdge;
public class UpRightPhone extends Phone {
public UpRightPhone(Brand brand) {
super(brand);
}
public void open() {
super.open();
System.out.println("直立手机");
}
public void close() {
super.close();
System.out.println("直立手机");
}
public void call() {
super.call();
System.out.println("直立手机");
}
}
客户端:
package com.java.birdge;
public class Client {
public static void main(String[] args) {
// 获取折叠手机(样式+品牌)
Phone phone1 = new FoldedPhone(new XiaoMi());
phone1.open();
phone1.call();
phone1.close();
System.out.println("=======================");
Phone phone2 = new FoldedPhone(new Vivo());
phone2.open();
phone2.call();
phone2.close();
System.out.println("==============");
UpRightPhone phone3 = new UpRightPhone(new XiaoMi());
phone3.open();
phone3.call();
phone3.close();
System.out.println("==============");
UpRightPhone phone4 = new UpRightPhone(new Vivo());
phone4.open();
phone4.call();
phone4.close();
}
}
1.2.5 桥接模式的注意事项和细节
- 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
- 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成。
- 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
- 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性,即需要有这样的应用场景
1.2.6 桥接模式其它应用场景
对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用.
- 常见的应用场景:
- JDBC驱动程序
- 银行转账系统
- 转账分类: 网上转账,柜台转账,AMT转账
- 转账用户类型:普通用户,银卡用户,金卡用户..
- 消息管理
- 消息类型:即时消息,延时消息
- 消息分类:手机短信,邮件消息,QQ消息…
1.3 装饰者设计模式
1.3.1 星巴克咖啡订单项目
星巴克咖啡订单项目(咖啡馆):
- 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
- 调料:Milk、Soy(豆浆)、Chocolate
- 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
- 使用OO的来计算不同种类咖啡的费用: 客户可以点单品咖啡,也可以单品咖啡+调料组合。
方案1-解决星巴克咖啡订单项目
方案1-解决星巴克咖啡订单问题分析
- Drink 是一个抽象类,表示饮料
- des就是对咖啡的描述, 比如咖啡的名字
- cost() 方法就是计算费用,Drink 类中做成一个抽象方法.
- Decaf 就是单品咖啡, 继承Drink, 并实现cost
- Espress && Milk 就是单品咖啡+调料, 这个组合很多
- 问题:这样设计,会有很多类,当我们增加一个单品咖啡,或者一个新的调料,类的数量就会倍增,就会出现类爆炸
方案2-解决星巴克咖啡订单(好点)
前面分析到方案1因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到Drink类,这样就不会造成类数量过多。从而提高项目的维护性(如图)
方案2-的问题分析
- 方案2可以控制类的数量,不至于造成很多的类
- 在增加或者删除调料种类时,代码的维护量很大
- 考虑到用户可以添加多份set() 调料时,可以将hasMilk 返回一个对应int
-
1.3.2 装饰者模式-基本介绍
装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
1.3.3 装饰者模式-原理类图
装饰者模式就像打包一个快递
- 主体:比如:陶瓷、衣服 (Component) // 被装饰者
- 包装:比如:报纸填充、塑料泡沫、纸板、木板(Decorator)
- Component
主体:比如类似前面的Drink
- ConcreteComponent和Decorator
ConcreteComponent:具体的主体,比如前面的各个单品咖啡 Decorator: 装饰者,比如各调料.
- 在如图的Component与ConcreteComponent之间,如果ConcreteComponent类很多,还可以设计一个缓冲层,将共有的部分提取出来,抽象层一个类。
1.3.4 装饰者模式解决星巴克咖啡订单
说明
- Drink 类就是前面说的抽象类, Component
- ShortBlack 就单品咖啡
- Decorator 是一个装饰类,含有一个被装饰的对象(Drink obj)
- Decorator 的cost 方法 进行一个费用的叠加计算,递归的计算价格
装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack
说明
- Milk包含了LongBlack
- 一份Chocolate包含了(Milk+LongBlack)
- 一份Chocolate包含了(Chocolate+Milk+LongBlack)
- 这样不管是什么形式的单品咖啡+调料组合,通过递归方式可以方便的组合和维护。 ```java package com.java.decorator;
import lombok.Data;
@Data public abstract class Drink {
public String des; // 描述
private float price = 0.0f;
/**
* 计算费用的抽象方法,子类来实现
* @return
*/
public abstract float cost();
}
被装饰者类:
```java
package com.java.decorator;
public class Coffee extends Drink {
@Override
public float cost() {
return super.getPrice();
}
}
package com.java.decorator;
public class LongBlack extends Coffee {
public LongBlack() {
setDes(" longBlack ");
setPrice(5.0f);
}
}
package com.java.decorator;
public class ShortBlack extends Coffee{
public ShortBlack() {
setDes(" shortBlack ");
setPrice(4.0f);
}
}
package com.java.decorator;
public class Espresso extends Coffee {
public Espresso() {
setDes("意大利咖啡");
setPrice(6.0f);
}
}
package com.java.decorator;
public class DeCaf extends Coffee {
public DeCaf() {
setDes(" 无因咖啡 ");
setPrice(1.0f);
}
}
装饰者类:
package com.java.decorator;
public class Decorator extends Drink {
private Drink obj;
public Decorator(Drink obj) { // 组合
this.obj = obj;
}
@Override
public float cost() {
// getPrice 自己的价格
return super.getPrice() + obj.cost();
}
@Override
public String getDes() {
// obj.getDes() 被装饰者描述信息
return des + " " + getPrice() + " && " + obj.getDes();
}
}
package com.java.decorator;
// 具体的Decorator
public class Chocolate extends Decorator {
public Chocolate(Drink obj) {
super(obj);
setDes(" 巧克力 ");
setPrice(3.0f); // 调味品的价格
}
}
package com.java.decorator;
public class Milk extends Decorator {
public Milk(Drink obj) {
super(obj);
setDes(" 牛奶 ");
setPrice(2.0f);
}
}
package com.java.decorator;
public class Soy extends Decorator{
public Soy(Drink obj) {
super(obj);
setDes(" 豆浆 ");
setPrice(1.5f);
}
}
客户端:
package com.java.decorator;
public class CoffeeBar {
public static void main(String[] args) {
// 装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack
// 点一份 LongBlack
Drink order = new LongBlack();
System.out.println("费用1=" + order.cost());
System.out.println("描述=" + order.getDes());
// 2. order 加一份牛奶
order = new Milk(order);
System.out.println("order 加一份牛奶 费用 =" + order.cost());
System.out.println("order 加一份牛奶 描述 = " + order.getDes());
// 3. order 加一份巧克力
order = new Chocolate(order);
System.out.println("order 加一份巧克力 费用 =" + order.cost());
System.out.println("order 加一份巧克力 费用 = " + order.getDes());
// 3. order 加一份巧克力
order = new Chocolate(order);
System.out.println("order 加一份巧克力 费用 =" + order.cost());
System.out.println("order 加一份巧克力 费用 = " + order.getDes());
System.out.println("===========================");
Drink order2 = new DeCaf();
System.out.println("order2 无因咖啡 费用 =" + order2.cost());
System.out.println("order2 无因咖啡 描述 = " + order2.getDes());
order2 = new Milk(order2);
System.out.println("order2 加一份牛奶 费用 =" + order2.cost());
System.out.println("order2 加一份牛奶 费用 = " + order2.getDes());
}
}
1.3.5 装饰者模式在JDK应用的源码分析
package com.java.jdk;
import java.io.DataInputStream;
import java.io.FileInputStream;
public class Decorator {
public static void main(String[] args) throws Exception{
// 说明
//1. InputStream 是抽象类 类似 Drink
//2. FileInputStream 是 InputStream 子类,类似 DeCaf, LongBlack
//3. FilterInputStream 是 InputStream 子类 类似 Decorator 修饰者
//4. DataInputStream 是 FilterInputStream 子类 具体的修饰者 Milk, Soy 等
//5. FilterInputStream 类 有 protected volatile InputStream in; 即含被装饰者
//6. jdk的io体系中, 就是使用的装饰者模式
DataInputStream dis = new DataInputStream(new FileInputStream("d:\\abc.txt"));
System.out.println(dis.read());
dis.close();
}
}
1.4 组合模式
1.4.1 学校院系展示需求
编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。如图:
传统方案解决学校院系展示
传统方案解决学校院系展示存在的问题分析
- 将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的
- 实际上我们的要求是 :在一个页面中展示出学校的院系组成,一个学校有多个 学院,一个学院有多个系, 因此这种方案,不能很好实现的管理的操作,比如对学院、系的添加,删除,遍历等
解决方案:把学校、院、系都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实现管理操作。 => 组合模式
1.4.2 组合模式基本介绍
组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系。
- 组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
- 这种类型的设计模式属于结构型模式。
组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象
1.4.3 组合模式原理类图
对原理结构图的说明-即(组合模式的角色及职责)Component:这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为。用于访问和管理Component 子部件,Component 可以是抽象类或者接口。
- Leaf:在组合中表示叶子节点,没有子节点。
- Composite:非叶子节点,用于存储子部件,在Component 接口中实现子部件的相关操作,比如增加(add),删除(deleted)。
组合模式解决的问题
- 组合模式解决这样的问题,当我们的要处理的对象可以生成一颗树形结构,而我们要对树上的节点和叶子进行操作时,它能够提供一致的方式,而不用考虑它是节点还是叶子
- 对应的示意图
1.4.4 组合模式解决学校院系展示
代码实现:
OrganizationComponent:
package com.java.composite;
import lombok.Data;
@Data
public abstract class OrganizationComponent {
private String name; // 名字
private String des; // 说明
protected void add(OrganizationComponent organizationComponent) {
// 默认实现
throw new UnsupportedOperationException();
}
protected void remove(OrganizationComponent organizationComponent) {
// 默认实现
throw new UnsupportedOperationException();
}
public OrganizationComponent(String name, String des) {
super();
this.name = name;
this.des = des;
}
// 子类都需要实现
protected abstract void print();
}
University:
package com.java.composite;
import java.util.ArrayList;
import java.util.List;
//University 就是 Composite , 可以管理College
public class University extends OrganizationComponent {
List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();
// 构造器
public University(String name, String des) {
super(name, des);
}
// 重写add
@Override
protected void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}
// 重写remove
@Override
protected void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
// print方法就是输出University 包含的学院
@Override
protected void print() {
System.out.println("--------------" + getName() + "--------------");
// 遍历 organizationComponents
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}
}
College:
package com.java.composite;
import java.util.ArrayList;
import java.util.List;
public class College extends OrganizationComponent {
//List 中存放的是Department
List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();
// 构造器
public College(String name, String des) {
super(name, des);
}
// 重写add
@Override
protected void add(OrganizationComponent organizationComponent) {
// 实际业务中,College 的add 和 University的add不一定完全一样
organizationComponents.add(organizationComponent);
}
// 重写remove
@Override
protected void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
@Override
protected void print() {
System.out.println("--------------" + getName() + "--------------");
for (OrganizationComponent organizationComponent : organizationComponents) {
organizationComponent.print();
}
}
}
Department:叶子节点
package com.java.composite;
public class Department extends OrganizationComponent {
public Department(String name, String des) {
super(name, des);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDes() {
return super.getDes();
}
@Override
protected void print() {
System.out.println(getName());
}
}
客户端:
package com.java.composite;
public class Client {
public static void main(String[] args) {
// 从大到小创建对象
OrganizationComponent university = new University("清华大学", "自强不息, 厚德载物");
// 学院
OrganizationComponent computerCollege = new College("计算机学院", " 计算机学院 ");
OrganizationComponent infoEngineercollege = new College("信息工程学院", "信息工程学院");
// 创建各个学院下面的系(专业)
computerCollege.add(new Department("软件工程", " 软件工程 "));
computerCollege.add(new Department("网络工程", " 网络工程 "));
computerCollege.add(new Department("计算机科学与技术", " 计算机科学与技术 "));
//
infoEngineercollege.add(new Department("通信工程", " 通信工程 "));
infoEngineercollege.add(new Department("信息工程", " 信息工程 "));
// 将学院加到学校
university.add(computerCollege);
university.add(infoEngineercollege);
university.print();
// infoEngineercollege.print();
}
}
1.4.5 组合模式的注意事项和细节
- 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
- 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动.
- 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构
- 需要遍历组织机构,或者处理的对象具有树形结构时, 非常适合使用组合模式.
要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式
1.5 外观模式
1.5.1 影院管理项目
组建一个家庭影院:
DVD播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的
功能,其过程为:
• 直接用遥控器:统筹各设备开关
• 开爆米花机
• 放下屏幕
• 开投影仪
• 开音响
• 开DVD,选dvd
• 去拿爆米花
• 调暗灯光
• 播放
• 观影结束后,关闭各种设备
传统方式解决影院管理
传统方式解决影院管理问题分析在ClientTest 的main方法中,创建各个子系统的对象,并直接去调用子系统(对象)相关方法,会造成调用过程混乱,没有清晰的过程
- 不利于在ClientTest 中,去维护对子系统的操作
- 解决思路:定义一个高层接口,给子系统中的一组接口提供一个一致的界面(比如在高层接口提供四个方法 ready, play, pause, end ),用来访问子系统中的一群接口
也就是说 就是通过定义一个一致的接口(界面类),用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节 => 外观模式
1.5.2 外观模式基本介绍
外观模式(Facade),也叫“过程模式:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节
1.5.3 外观模式原理类图
原理类图的说明(外观模式的角色)外观类(Facade): 为调用端提供统一的调用接口, 外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当子系统对象
- 调用者(Client): 外观接口的调用者
子系统的集合:指模块或者子系统,处理Facade 对象指派的任务,他是功能的实际提供者
1.5.4 外观模式解决影院管理
外观模式可以理解为转换一群接口,客户只要调用一个接口,而不用调用多个接口才能达到目的。比如:在pc上安装软件的时候经常有一键安装选项(省去选择安装目录、安装的组件等等),还有就是手机的重启功能(把关机和启动合为一个操作)。
- 外观模式就是解决多个复杂接口带来的使用困难,起到简化用户操作的作用
- 示意图说明
client:
package com.java.facade;
public class Client {
public static void main(String[] args) {
HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade();
homeTheaterFacade.ready();
homeTheaterFacade.play();
homeTheaterFacade.end();
}
}
facade:
package com.java.facade;
public class HomeTheaterFacade {
private TheaterLight theaterLight;
private Popcorn popcorn;
private Stereo stereo;
private Projector projector;
private Screen screen;
private DVDPlayer dVDPlayer;
public HomeTheaterFacade() {
super();
this.theaterLight = TheaterLight.getInstance();
this.popcorn = Popcorn.getInstance();
this.stereo = Stereo.getInstance();
this.projector = Projector.getInstance();
this.screen = Screen.getInstance();
this.dVDPlayer = DVDPlayer.getInstanc();
}
public void ready() {
popcorn.on();
popcorn.pop();
screen.down();
projector.on();
stereo.on();
dVDPlayer.on();
theaterLight.dim();
}
public void play() {
dVDPlayer.play();
}
public void pause() {
dVDPlayer.pause();
}
public void end() {
popcorn.off();
theaterLight.bright();
screen.up();
projector.off();
stereo.off();
dVDPlayer.off();
}
}
DVDPlayer:
package com.java.facade;
public class DVDPlayer {
private DVDPlayer() {
}
// 使用单例模式,饿汉式
private static DVDPlayer instance = new DVDPlayer();
public static DVDPlayer getInstanc() {
return instance;
}
public void on() {
System.out.println(" dvd on ");
}
public void off() {
System.out.println(" dvd off ");
}
public void play() {
System.out.println(" dvd is playing ");
}
//....
public void pause() {
System.out.println(" dvd pause ..");
}
}
Popcorn:
package com.java.facade;
public class Popcorn {
private Popcorn() {
}
private static Popcorn instance = new Popcorn();
public static Popcorn getInstance() {
return instance;
}
public void on() {
System.out.println(" popcorn on ");
}
public void off() {
System.out.println(" popcorn off ");
}
public void pop() {
System.out.println(" popcorn is poping ");
}
}
Projector:
package com.java.facade;
public class Projector {
private Projector() {
}
private static Projector instance = new Projector();
public static Projector getInstance() {
return instance;
}
public void on() {
System.out.println(" Projector on ");
}
public void off() {
System.out.println(" Projector off ");
}
public void focus() {
System.out.println(" Projector is Projector ");
}
//...
}
Screen:
package com.java.facade;
public class Screen {
private Screen() {
}
private static Screen instance = new Screen();
public static Screen getInstance() {
return instance;
}
public void up() {
System.out.println(" Screen up ");
}
public void down() {
System.out.println(" Screen down ");
}
}
Stereo:
package com.java.facade;
public class Stereo {
private Stereo() {
}
private static Stereo instance = new Stereo();
public static Stereo getInstance() {
return instance;
}
public void on() {
System.out.println(" Stereo on ");
}
public void off() {
System.out.println(" Screen off ");
}
public void up() {
System.out.println(" Screen up.. ");
}
//...
}
TheaterLight:
package com.java.facade;
public class TheaterLight {
private TheaterLight() {
}
private static TheaterLight instance = new TheaterLight();
public static TheaterLight getInstance() {
return instance;
}
public void on() {
System.out.println(" TheaterLight on ");
}
public void off() {
System.out.println(" TheaterLight off ");
}
public void dim() {
System.out.println(" TheaterLight dim.. ");
}
public void bright() {
System.out.println(" TheaterLight bright.. ");
}
}e
1.5.5 外观模式的注意事项和细节
- 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
- 外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易维护和扩展
- 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
- 当系统需要进行分层设计时,可以考虑使用Facade模式
- 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口,让新系统与Facade类交互,提高复用性
不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。要以让系统有层次,利于维护为目的。
1.6 享元模式
1.6.1 展示网站项目需求
小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同:
有客户要求以新闻的形式发布
- 有客户人要求以博客的形式发布
- 有客户希望以微信公众号的形式发布
传统方案解决网站展现项目
- 直接复制粘贴一份,然后根据客户不同要求,进行定制修改
- 给每个网站租用一个空间
- 方案设计示意图
传统方案解决网站展现项目-问题分析
- 需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费
- 解决思路:整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源
- 对于代码来说,由于是一份实例,维护和扩展都更加容易
-
1.6.2 享元模式基本介绍
享元模式(Flyweight Pattern) 也叫 蝇量模式: 运用共享技术有效地支持大量细粒度的对象
- 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
- 享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率
- 享元模式经典的应用场景就是池技术,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式
1.6.3 享元模式的原理类图
对原理图的说明-即(模式的角色及职责)
- FlyWeight 是抽象的享元角色, 他是产品的抽象类, 同时定义出对象的外部状态和内部状态的接口或实现
- ConcreteFlyWeight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务
- UnSharedConcreteFlyweight 是不可共享的角色,一般不会出现在享元工厂
- FlyWeightFactory 享元工厂类,用于构建一个池容器(集合),同时提供从池中获取对象方法
内部状态和外部状态
比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态
- 享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两个部分:内部状态和外部状态
- 内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
- 外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
- 举个例子:围棋理论上有361个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题(共享棋子的颜色)
1.6.4 享元模式解决网站展现项目
WebSite: ```java package com.java.flyweight;
public abstract class WebSite {
public abstract void use(User user);// user 外部状态
}
**ConcreteWebSite:**
```java
package com.java.flyweight;
// 具体网站
public class ConcreteWebSite extends WebSite {
// 共享的部分,内部状态
private String type = ""; // 网站发布的形式(类型)
public ConcreteWebSite(String type) {
this.type = type;
}
@Override
public void use(User user) {
System.out.println("网站的发布形式为" + type + "在使用中,使用者是" + user.getName());
}
}
User:
package com.java.flyweight;
// 外部状态
public class User {
private String name;
public User(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
}
WebSiteFactory:
package com.java.flyweight;
import java.util.HashMap;
// 网站工厂类,根据需求返回一个网站
public class WebSiteFactory {
// 充当池的作用
private HashMap<String, ConcreteWebSite> pool = new HashMap<>();
// 根据网站的类型,返回一个网站,如果没有则创建一个网站,放入池中
public WebSite getWebSiteCategory(String type) {
// 如果没有则创建一个网站,放入池中
if(!pool.containsKey(type)) {
pool.put(type, new ConcreteWebSite(type));
}
return (WebSite)pool.get(type);
}
// 获取网站分类的总数(池中有多少网站类型)
public int getWebSiteCount() {
return pool.size();
}
}
Client:
package com.java.flyweight;
public class Client {
public static void main(String[] args) {
// 创建一个工厂类
WebSiteFactory factory = new WebSiteFactory();
// 客户要一个新闻形式发布的网站
WebSite webSite1 = factory.getWebSiteCategory("新闻");
webSite1.use(new User("tom"));
// 客户要一个博客形式发布的网站
WebSite webSite2 = factory.getWebSiteCategory("博客");
webSite2.use(new User("jack"));
// 客户要一个博客形式发布的网站
WebSite webSite3 = factory.getWebSiteCategory("博客");
webSite3.use(new User("smith"));
// 客户要一个博客形式发布的网站
WebSite webSite4 = factory.getWebSiteCategory("博客");
webSite4.use(new User("king"));
System.out.println("网站的分类共=" + factory.getWebSiteCount());
}
}
1.6.5 享元模式在JDK-Interger的应用源码分析
package com.java.jdk;
public class FlyWeight {
public static void main(String[] args) {
// Integer.valueOf(x) x 在 -128 --- 127 直接,使用享元模式返回(缓存中),如果不在,则新创建Integer对象
// 小结
//1. 在valueOf 方法中,先判断值是否在 IntegerCache 中,如果不在,则新创建的Integer(new), 否则,就直接从缓存池中返回
//2. valueOf 方法就用到享元模式
//3. 如果使用valueOf 方法得到一个Integer 实例,范围在 -128 - 127 ,执行速度比new 快
Integer x = Integer.valueOf(127); // Integer
Integer y = new Integer(127); // Integer
Integer z = Integer.valueOf(127);//..
Integer w = new Integer(127);
System.out.println(x.equals(y)); // true
System.out.println(x == y ); // false
System.out.println(x == z ); // true
System.out.println(w == x ); // false
System.out.println(w == y ); // false
Integer x1 = Integer.valueOf(200);
Integer x2 = Integer.valueOf(200);
System.out.println("x1==x2" + (x1 == x2)); // false
}
}
1.6.6 享元模式的注意事项和细节
- 在享元模式这样理解,“享”就表示共享,“元”表示对象
- 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式
- 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储
- 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
- 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方.
- 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、数据库连接池
1.7 代理模式
1.7.1 代理模式的基本介绍
代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
- 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
- 代理模式有不同的形式, 主要有三种 静态代理、动态代理 (JDK代理、接口代理)和 Cglib代理 (可以在内存动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。
- 代理模式示意图
1.7.2 静态代理
1.7.2.1 静态代码模式的基本介绍
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一 起实现相同的接口或者是继承相同父类 。
1.7.2.2 应用实例
具体要求
- 定义一个接口:ITeacherDao
- 目标对象TeacherDAO实现接口ITeacherDAO
- 使用静态代理方式,就需要在代理对象TeacherDAOProxy中也实现ITeacherDAO
- 调用的时候通过调用代理对象的方法来调用目标对象.
- 特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法。
思路分析图解(类图)
代码实现
ITeacherDao: 接口
package com.java.proxy.staticproxy;
// 接口
public interface ITeacherDao {
void teach(); // 授课方法
}
ITeacherDao: 目标对象
package com.java.proxy.staticproxy;
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("老师授课中。。。");
}
}
TeacherDaoProxy: 代理对象
package com.java.proxy.staticproxy;
// 代理对象,静态代理
public class TeacherDaoProxy implements ITeacherDao{
private ITeacherDao target; // 目标对象,被代理的对象,通过接口来聚合
public TeacherDaoProxy(ITeacherDao target) {
this.target = target;
}
@Override
public void teach() {
System.out.println("开始代理。。。"); //
target.teach();
System.out.println("结束代理。。。");//
}
}
Client:
package com.java.proxy.staticproxy;
public class Client {
public static void main(String[] args) {
// 创建目标对象(被代理对象)
TeacherDao teacherDao = new TeacherDao();
// 创建代理对象,同时将被代理对象传递给代理对象
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
// 通过代理对象,调用被代理对象的方法
// 即:执行的是代理对象的方法,代理对象再去调用目标对象的方法
teacherDaoProxy.teach();
}
}
静态代理优缺点
- 优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展
- 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
-
1.7.3 动态代理
1.7.3.1 动态代理模式的基本介绍
代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
-
1.7.3.2 JDK中生成代理对象的API
代理类所在包:java.lang.reflect.Proxy
- JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
动态代理应用实例
将上面的静态代理改进成动态代理模式(即:JDK代理模式)
思路图解(类图)
代码实现
ITeacherDao: 接口
package com.java.proxy.dynamic;
// 接口
public interface ITeacherDao {
void teach(); //授课方法
void sayHello(String name);
}
TeacherDao: 目标对象
package com.java.proxy.dynamic;
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("正在授课。。。");
}
@Override
public void sayHello(String name) {
System.out.println("hello " + name);
}
}
ProxyFactory: 代理对象
package com.java.proxy.dynamic;
import java.lang.reflect.Proxy;
public class ProxyFactory {
// 目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
// 给目标对象生成一个代理对象
public Object getProxyInstance() {
/**
* 1. Classloader loader: 指定当前目标对象使用的类加载器,获取加载器的方法固定、
* 2. Class<?>[] interfaces:目标对象实现的接口类型,使用泛型方法确认类型
* 3. InvocationHandler h:事情处理,执行目标对象的方法时,会触发事情处理器方法。会把当前执行的目标对象方法作为参数传入
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
System.out.println("JDK动态代理开始");
Object returnVal = method.invoke(target, args);
System.out.println("JDK动态代理结束");
return returnVal;
});
}
}
Client:
package com.java.proxy.dynamic;
public class Client {
public static void main(String[] args) {
ITeacherDao target = new TeacherDao();
ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance();
// proxyInstance=class com.sun.proxy.$Proxy0
System.out.println("proxyInstance=" + proxyInstance.getClass());
// proxyInstance.teach();
proxyInstance.sayHello(" tom ");
}
}
1.7.3.3 Cglib代理模式的基本介绍
- 静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是Cglib代理
- Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib代理归属到动态代理。
- Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP,实现方法拦截
- 在AOP编程中如何选择代理模式:
- 目标对象需要实现接口,用JDK代理
- 目标对象不需要实现接口,用Cglib代理
- Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类
- 在内存中动态构建子类,注意代理的类不能为final,否则报错 java.lang.IllegalArgumentException:
- 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的 业务方法
Cglib代理模式应用实例
动态代理应用实例
将上面的案例用Cglib代理模式实现
思路图解(类图)
代码实现
TeacherDao: 目标对象
package com.java.proxy.cglib;
public class TeacherDao {
public String teach() {
System.out.println("老师授课中,cglib代理,不需要实现接口");
return "hello";
}
}
ProxyFactory: 代理对象
package com.java.proxy.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class ProxyFactory implements MethodInterceptor {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
// 返回一个代理对象,是target对象的代理对象
public Object getProxyInstance() {
// 1. 创建一个工具类
Enhancer enhancer = new Enhancer();
// 2. 设置父类
enhancer.setSuperclass(target.getClass());
// 3. 设置回调函数
enhancer.setCallback(this);
// 4. 创建子类对象,即代理对象
return enhancer.create();
}
@Override
public Object intercept(Object arg0, Method method, Object[] args, MethodProxy arg3) throws Throwable {
System.out.println("Cglib代理开始");
Object returnVal = method.invoke(target, args);
System.out.println("Cglib代理结束");
return returnVal;
}
}
Client:
package com.java.proxy.cglib;
public class Client {
public static void main(String[] args) {
TeacherDao target = new TeacherDao();
TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(target).getProxyInstance();
String res = proxyInstance.teach();
System.out.println("res=" + res);
}
}
1.7.4 代理模式(Proxy)的变体
几种常见的代理模式介绍— 几种变体
- 防火墙代理
内网通过代理穿透防火墙,实现对公网的访问。
- 缓存代理
比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则ok,如果取不到资源,再到公网或者数据库取,然后缓存。
- 远程代理
远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息。
- 同步代理:主要使用在多线程编程中,完成多线程间同步工作