五、结构型模式
1、适配器模式
Adapter Pattern
1)基本介绍
- 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配,不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
- 适配器模式属于结构型模式
- 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
2)工作原理
- 适配器模式:将一个类的接口转换成另一种接口。让原本接口不兼容的类可以兼容
- 从用户的角度看不到被适配者,是解耦的
- 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
- 用户收到反馈结果,感觉只是和目标接口交互
3)类适配器
1、介绍
基本介绍:Adapter类,通过继承 src 类,实现 dst 类接口,完成 src -> dst 的适配
2、实例
手机需要 5V直流电,充电器本身相当于 Adapter,220V交流电相当于 src (即被适配者)
实现步骤:
充电器(Adapter类)继承(extends) 220V交流电类,提供 5V直流电方法
手机 使用 充电器,调用 5V直流电方法,完成充电
3、注意事项和细节
- Java 是单继承机制,所以类适配器需要继承 src 类这一点算是一个缺点,因为这要求 dst 必须是接口,有一定局限性
- src 类的方法在 Adapter 中都会暴露出来,也增加了使用的成本
- 由于其继承了 src 类,所以它可以根据需求重写 src 类的方法,使得 Adapter的灵活性增强了
4)对象适配器
1、介绍
- 基本思路和类的适配器模式相同,只是将 Adapter 类作修改,不是继承 src 类,而是持有 src 类的实例,以解决兼容性的问题。即:持有 src 类,实现 dst 类接口,完成 src -> dst 的适配
- 根据合成复用原则,在系统中尽量使用关联关系(聚合)来替代继承关系
- 对象适配器模式是适配器模式常用的一种
2、实例类图
3、注意事项和细节
- 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同
- 根据合成复用原则,使用聚合/组合替代继承,所以它解决了类适配器必须继承 src 的局限性问题,也不再要求 dst 必须是接口
- 使用成本更低,更灵活
5)接口适配器
1、介绍
- 一些书籍称为:适配器模式(Default Adapter Pattern)或 缺省适配器模式
- 核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供 一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
- 适用于一个接口不想使用其所有的方法的情况
2、案例
图形用户界面(GUI)中,添加监听器
//监听事件:监听窗口关闭System.exit(0)
//适配器模式
frame.addWindowListener(new WindowAdapter() {
//点击窗口关闭要做的事情
@Override
public void windowClosing(WindowEvent e) {
//结束程序
//0:正常退出
//1:非正常退出
System.exit(0);
}
});
WindowAdapter 为窗口适配器类,里面有很多方法(窗口关闭、最小化、最大化……),我们只需要监听窗口关闭事件,所以 匿名内部类 中只实现 windowClosing() 方法即可
6)模拟SpringMVC
- 控制器
// 多种Controller实现
public interface Controller {
}
class HttpController implements Controller {
public void doHttpHandler() {
System.out.println("http...");
}
}
class SimpleController implements Controller {
public void doSimplerHandler() {
System.out.println("simple...");
}
}
class AnnotationController implements Controller {
public void doAnnotationHandler() {
System.out.println("annotation...");
}
}
- 适配器
// 定义一个Adapter接口
public interface HandlerAdapter {
public boolean supports(Object handler);
public void handle(Object handler);
}
// 多种适配器类
class SimpleHandlerAdapter implements HandlerAdapter {
// 调用相应的控制器处理请求
public void handle(Object handler) {
((SimpleController) handler).doSimplerHandler();
}
// 判断是否支持相应的控制器
public boolean supports(Object handler) {
return (handler instanceof SimpleController);
}
}
class HttpHandlerAdapter implements HandlerAdapter {
public void handle(Object handler) {
((HttpController) handler).doHttpHandler();
}
public boolean supports(Object handler) {
return (handler instanceof HttpController);
}
}
class AnnotationHandlerAdapter implements HandlerAdapter {
public void handle(Object handler) {
((AnnotationController) handler).doAnnotationHandler();
}
public boolean supports(Object handler) {
return (handler instanceof AnnotationController);
}
}
- 客户请求
import java.util.ArrayList;
import java.util.List;
public class DispatchServlet {
public static List<HandlerAdapter> handlerAdapters = new ArrayList<HandlerAdapter>();
public DispatchServlet() {
handlerAdapters.add(new AnnotationHandlerAdapter());
handlerAdapters.add(new HttpHandlerAdapter());
handlerAdapters.add(new SimpleHandlerAdapter());
}
public void doDispatch() {
// 此处模拟SpringMVC从request取handler的对象,
// 适配器可以获取到希望的Controller
HttpController controller = new HttpController();
// AnnotationController controller = new AnnotationController();
//SimpleController controller = new SimpleController();
// 得到对应适配器
HandlerAdapter adapter = getHandler(controller);
// 通过适配器执行对应的controller对应方法
adapter.handle(controller);
}
public HandlerAdapter getHandler(Controller controller) {
//遍历:根据得到的controller(handler), 返回对应适配器
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(controller)) {
return adapter;
}
}
return null;
}
public static void main(String[] args) {
new DispatchServlet().doDispatch(); // http...
}
}
7)注意事项和细节
- 三种命名方式,是根据 src 是以怎样的形式给到 Adapter(在 Adapter里的形式)来命名的
- 类适配器:在 Adapter 里,就是将 src 当做类,继承
- 对象适配器:在 Adapter 里,将 src 当做对象,聚合
- 接口适配器:在 Adapter 里,将 src 当做接口,实现
- Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作
2、桥接模式
Bridge Pattern
1)基本介绍
- 桥接模式是指:将实现与抽象放在不同的类层次中,使两个层次可以独立改变
- 是一种结构型设计模式
- 桥接模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责
- 主要特点是把 抽象(Abstraction) 与 行为实现(Implementation) 分离开来,从而可以保持各部分的独立性以及功能扩展
2)原理类图
-
Client类:桥接模式的调用者
-
抽象类(Abstraction):充当桥接类
维护了 Implementor(以及实现类 ConcreteImplementorA..),二者是聚合关系
-
RefinedAbstraction:Abstraction 抽象类的子类
-
Implementor:行为实现类的接口
-
ConcreteImplementorA / B:行为的具体实现类
3)案例分析
对 不同品牌(华为、小米…) 不同类型(直立式、折叠式…) 的手机实现同样的功能(比如:开机、关机、上网,打电话等)
- 传统方式
- 扩展性差(类爆炸)
- 款式与品牌间的耦合性太强。如果再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果增加手机品牌,也要在各个手机样式类下增加
- 违反了单一职责原则
- 桥接模式
把款式和品牌分离开。品牌以聚合的形式加入手机中
- 代码实现
品牌接口(Brand)
package bridge;
/**
* @author ajun
* Date 2021/7/25
* @version 1.0
* 手机品牌
*/
public interface Brand {
String getName();
}
华为品牌
package bridge;
/**
* @author ajun
* Date 2021/7/25
* @version 1.0
* 华为手机品牌
*/
public class HuaWeiBrand implements Brand{
// 品牌名称
private String name;
public HuaWeiBrand(){
this.name = "华为";
}
@Override
public String getName() {
return name;
}
}
小米品牌
package bridge;
/**
* @author ajun
* Date 2021/7/25
* @version 1.0
* 小米手机品牌
*/
public class XiaoMiBrand implements Brand{
// 品牌名称
private String name;
public XiaoMiBrand() {
this.name = "小米";
}
@Override
public String getName() {
return name;
}
}
手机抽象类(Phone)
把品牌聚合进来
定义手机功能(开机、关机、打电话、发短信……)
package bridge;
/**
* @author ajun
* Date 2021/7/25
* @version 1.0
*/
public abstract class Phone {
// 品牌(聚合)
private Brand brand;
/**
* 构造器
* @param brand
*/
public Phone(Brand brand){
this.brand = brand;
}
/**
* 开机
*/
abstract void open();
/**
* 关机
*/
abstract void close();
/**
* 打电话
*/
abstract void call();
/**
* 发短信
*/
abstract void sendMsg();
public Brand getBrand() {
return brand;
}
}
折叠手机
继承自抽象手机类
package bridge;
/**
* @author ajun
* Date 2021/7/25
* @version 1.0
* 折叠手机
*/
public class FoldedPhone extends Phone{
/**
* 构造器
* @param brand
*/
public FoldedPhone(Brand brand) {
super(brand);
}
@Override
void open() {
System.out.println(getBrand().getName() + " 折叠手机 开机");
}
@Override
void close() {
System.out.println(getBrand().getName() + " 折叠手机 关机");
}
@Override
void call() {
System.out.println(getBrand().getName() + " 折叠手机 打电话");
}
@Override
void sendMsg() {
System.out.println(getBrand().getName() + " 折叠手机 发短信");
}
}
直立手机
继承自抽象手机类
package bridge;
/**
* @author ajun
* Date 2021/7/25
* @version 1.0
* 直立手机
*/
public class UprightPhone extends Phone{
/**
* 构造器
*
* @param brand
*/
public UprightPhone(Brand brand) {
super(brand);
}
@Override
void open() {
System.out.println(getBrand().getName() + " 直立手机 开机");
}
@Override
void close() {
System.out.println(getBrand().getName() + " 直立手机 关机");
}
@Override
void call() {
System.out.println(getBrand().getName() + " 直立手机 打电话");
}
@Override
void sendMsg() {
System.out.println(getBrand().getName() + " 直立手机 发短信");
}
}
客户
使用手机
package bridge;
/**
* @author ajun
* Date 2021/7/25
* @version 1.0
* 客户
*/
public class Client {
public static void main(String[] args) {
new FoldedPhone(new HuaWeiBrand()).call();
new UprightPhone(new XiaoMiBrand()).sendMsg();
}
}
// 运行结果
华为 折叠手机 打电话
小米 直立手机 发短信
//PlantUML
@startuml
abstract class Phone {
桥接类(抽象手机)
Brand
open()
close()
call()
sendMsg()
}
class FoldedPhone {
折叠手机
}
class UprightPhone {
直立手机
}
Phone <|-- FoldedPhone
Phone <|-- UprightPhone
Client -> Phone
interface Brand {
品牌接口
}
class HuaWeiBrand {
华为
}
class XiaoMiBrand {
小米
}
Phone o- Brand
Brand <|.. HuaWeiBrand
Brand <|.. XiaoMiBrand
@enduml
4)JDBC 桥接模式
5)注意事项和细节
- 把 抽象 和 实现部分 分离,从而极大地提高了系统的灵活性,有助于系统进行分层设计
- 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成
- 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本
- 增加了系统的可读性和设计难度,由于聚合关系建立在抽象层,要求针对抽象进行设计和编程
- 要求正确识别出系统中两个独立变化的维度(抽象、实现),因此其使用范围有一定的局限性,即需要有这样的应用场景
- 对于那些不希望使用继承或因为多层次继承导致类的个数急剧增加的系统,桥接模式尤为适用
6)常见的应用场景
-
JDBC驱动程序
-
银行转账系统
- 转账分类:网上转账,柜台转账,AMT转账
- 转账用户类型:普通用户,银卡用户,金卡用户..
-
消息管理
- 消息类型:即时消息,延时消息
- 消息分类:手机短信,邮件消息,QQ消息...
3、装饰者模式
Decorator Pattern
[ˈdekəreɪtər]
1)基本介绍
动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了
开闭原则(ocp)
2)原理
装饰者模式就像打包一个快递,一层一层地给物品添加外层包装
-
被装饰者
主体(Component):比如:陶瓷、衣服……
-
装饰者
包装(Decorator) :比如:报纸填充、塑料泡沫、纸板、木板……
3)案例分析
- 星巴克咖啡订单项目
- 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
- 调料:Milk(牛奶)、Soy(豆浆)、Chocolate(巧克力)
- 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
- 使用递归的方式来计算不同种类咖啡的费用:客户可以点单品咖啡,也可以单品咖啡+调料组合
// PlantUML
@startuml
abstract class Drink {
private des 描述
private price 价格
abstract cost() 费用
}
class Coffee {
cost() 费用
}
Drink <|-- Coffee
class EspressoCoffee {
意大利咖啡
}
class LongBlackCoffee {
美国咖啡
}
class DecafCoffee {
无因咖啡
}
Coffee <|-- EspressoCoffee
Coffee <|-- LongBlackCoffee
Coffee <|-- DecafCoffee
class Decorator{
装饰者
Drink drink 饮品
cost() 费用递归计算
getDes() 描述递归计算
}
Drink <|-o Decorator
class Milk {
调味:牛奶
}
class Soy {
调味:豆浆
}
class Chocolate {
调味:巧克力
}
Decorator <|- Milk
Decorator <|-- Soy
Decorator <|-- Chocolate
class CoffeeBar {
咖啡馆
}
CoffeeBar -> Drink
@enduml
- 模拟订单
- 代码实现
饮品基类 Drink
package decorator;
/**
* @author ajun
* Date 2021/7/25
* @version 1.0
* 饮品基类
* 被装饰者
*/
public abstract class Drink {
// 描述
private String des;
// 价格
private int price;
// 费用
public abstract int cost();
public String getDes() {
return "[" + des + " 价格:" + price + "]";
}
public void setDes(String des) {
this.des = des;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
Coffee 类
实现共有方法 cost()
package decorator.coffee;
import decorator.Drink;
/**
* @author ajun
* Date 2021/7/25
* @version 1.0
* 单品咖啡父类
* 实现共有方法:cost
*/
public class Coffee extends Drink {
@Override
public int cost() {
return getPrice();
}
}
单品咖啡类
DecafCoffee:无因咖啡
EspressoCoffee:意大利咖啡
LongBlackCoffee:美式咖啡
package decorator.coffee;
/**
* @author ajun
* Date 2021/7/25
* @version 1.0
* 无因咖啡
*/
public class DecafCoffee extends Coffee{
/**
* 构造器
*/
public DecafCoffee(){
setDes("无因咖啡");
setPrice(3);
}
}
public class EspressoCoffee extends Coffee{
/**
* 构造器
*/
public EspressoCoffee(){
setDes("意大利咖啡");
setPrice(5);
}
}
public class LongBlackCoffee extends Coffee {
/**
* 构造器
*/
public LongBlackCoffee() {
setDes("美式咖啡");
setPrice(6);
}
}
装饰器 Decorator
依附于主体之上,所以聚合了 Drink。在构造器中需传入 Drink 对象
package decorator.decorator;
import decorator.Drink;
/**
* @author ajun
* Date 2021/7/25
* @version 1.0
* 装饰器
* 依附于主体之上,所以聚合了 Drink。在构造器中需传入 Drink 对象
*/
public class Decorator extends Drink {
// 饮品基类(聚合)
private Drink drink;
/**
* 构造器
* @param drink
*/
public Decorator(Drink drink){
this.drink = drink;
}
@Override
public int cost() {
// 自身价格 + 之前订单费用(递归方式)
return super.getPrice() + drink.cost();
}
@Override
public String getDes() {
// 自身描述、价格 + 饮品基类描述(递归方式)
return super.getDes() + "\n" + drink.getDes();
}
}
调味品
Milk:牛奶
Soy:豆浆
Chocolate:巧克力
package decorator.decorator;
import decorator.Drink;
/**
* @author ajun
* Date 2021/7/25
* @version 1.0
* 调味:牛奶
*/
public class Milk extends Decorator{
/**
* 构造器
*
* @param drink
*/
public Milk(Drink drink) {
super(drink);
setDes("调味:牛奶");
setPrice(2);
}
}
public class Soy extends Decorator{
/**
* 构造器
*
* @param drink
*/
public Soy(Drink drink) {
super(drink);
setDes("调味:豆浆");
setPrice(1);
}
}
public class Chocolate extends Decorator{
/**
* 构造器
*
* @param drink
*/
public Chocolate(Drink drink) {
super(drink);
setDes("调味:巧克力");
setPrice(3);
}
}
咖啡馆
package decorator;
import decorator.coffee.*;
import decorator.decorator.*;
/**
* @author ajun
* Date 2021/7/25
* @version 1.0
* 咖啡馆
*/
public class CoffeeBar {
public static void main(String[] args) {
// 模式订单:2份巧克力 + 一份牛奶的 LongBlack
// 一份 LongBlack
Drink order = new LongBlackCoffee();
//System.out.println(order.getDes());
//System.out.println(order.cost());
//System.out.println("---------------");
// 加一份牛奶
order = new Milk(order);
//System.out.println(order.getDes());
//System.out.println(order.cost());
//System.out.println("---------------");
// 加一份巧克力
order = new Chocolate(order);
//System.out.println(order.getDes());
//System.out.println(order.cost());
//System.out.println("---------------");
// 再加一份巧克力
order = new Chocolate(order);
System.out.println(order.getDes());
System.out.println(order.cost());
}
}
// 运行结果
[调味:巧克力 价格:3]
[调味:巧克力 价格:3]
[调味:牛奶 价格:2]
[美式咖啡 价格:6]
14
4)JDK中的应用分析
Java的 IO 结构,FilterInputStream 就是一个装饰者
//PlantUML
@startuml
abstract class InputStream {
抽象主体(被装饰者)
}
class FileInputStream {
具体实现主体
}
class StringBufferInputStream {
具体实现主体
}
class ByteArrayInputStream {
具体实现主体
}
class FilterInputStream {
过滤器(装饰者)
InputStream in (聚合了主体)
}
class BufferedInputStream {
具体过滤器(装饰者)
}
class DataInputStream {
具体过滤器(装饰者)
}
class LineNumberInputStream {
具体过滤器(装饰者)
}
InputStream <|--- FileInputStream
InputStream <|--- StringBufferInputStream
InputStream <|--- ByteArrayInputStream
InputStream <|-o FilterInputStream
FilterInputStream <|-- BufferedInputStream
FilterInputStream <|-- DataInputStream
FilterInputStream <|- LineNumberInputStream
@enduml
4、组合模式
Composite Pattern
1)基本介绍
-
组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系
-
组合模式依据树形结构来组合对象,用来表示部分以及整体层次
-
这种类型的设计模式属于结构型模式
-
组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象和组合对象
2)原理类图
- Component :这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理 Component 子部件,Component可以是抽象类或者接口
- Leaf :在组合中表示叶子节点,叶子节点没有子节点
- Composite :非叶子节点,用于存储子部件,在 Component接口中实现子部件的相关操作,比如增加(add), 删除
3)案例分析
在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系
组织组件 OrganizationComponent
package composite;
/**
* @author ajun
* Date 2021/7/26
* @version 1.0
* 组织组件
*/
public abstract class OrganizationComponent {
// 名称
private String name;
// 描述
private String des;
/**
* 构造器
* @param name
* @param des
*/
public OrganizationComponent(String name, String des) {
this.name = name;
this.des = des;
}
/**
* 添加
* @param organizationComponent
*/
protected void add(OrganizationComponent organizationComponent){
throw new UnsupportedOperationException();
}
/**
* 删除
* @param organizationComponent
*/
protected void remove(OrganizationComponent organizationComponent){
throw new UnsupportedOperationException();
}
/**
* 输出信息
*/
protected abstract void print();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
}
大学 University
聚合了 学院 College
package composite;
import java.util.ArrayList;
import java.util.List;
/**
* @author ajun
* Date 2021/7/26
* @version 1.0
* 大学
*/
public class University extends OrganizationComponent{
// 学院集合
private List<OrganizationComponent> colleges = new ArrayList<OrganizationComponent>();
/**
* 构造器
* @param name
* @param des
*/
public University(String name, String des) {
super(name, des);
}
@Override
protected void add(OrganizationComponent organizationComponent) {
colleges.add(organizationComponent);
}
@Override
protected void remove(OrganizationComponent organizationComponent) {
colleges.remove(organizationComponent);
}
@Override
protected void print() {
System.out.println("------"+getName()+"------");
for (OrganizationComponent college : colleges) {
college.print();
}
}
}
学院 College
聚合了 系 Department
package composite;
import java.util.ArrayList;
import java.util.List;
/**
* @author ajun
* Date 2021/7/26
* @version 1.0
* 学院
*/
public class College extends OrganizationComponent{
// 系的集合
private List<OrganizationComponent> departments = new ArrayList<OrganizationComponent>();
/**
* 构造器
* @param name
* @param des
*/
public College(String name, String des) {
super(name, des);
}
@Override
protected void add(OrganizationComponent organizationComponent) {
departments.add(organizationComponent);
}
@Override
protected void remove(OrganizationComponent organizationComponent) {
departments.remove(organizationComponent);
}
@Override
protected void print() {
System.out.println("\n-----"+getName()+"-----\n");
for (OrganizationComponent department : departments) {
department.print();
}
}
}
系 Department
系为叶子节点,不需要聚合别的组件,不需要添加方法
package composite;
/**
* @author ajun
* Date 2021/7/26
* @version 1.0
*/
public class Department extends OrganizationComponent {
/**
* 构造器
*
* @param name
* @param des
*/
public Department(String name, String des) {
super(name, des);
}
@Override
protected void print() {
System.out.println("[专业:" + getName() + "] " + getDes());
}
}
客户端 Client
package composite;
/**
* @author ajun
* Date 2021/7/26
* @version 1.0
*/
public class Client {
public static void main(String[] args) {
// 创建学校
OrganizationComponent university = new University("清华大学","中国一流大学");
// 创建学院
OrganizationComponent college1 = new College("计算机学院","计算机学院666");
OrganizationComponent college2 = new College("信息工程学院","信息工程学院666");
// 创建系(专业)
college1.add(new Department("软件工程","软件工程不错"));
college1.add(new Department("网络工程","网络工程不错"));
college1.add(new Department("计算机科学与技术","计算机科学与技术是老牌的专业"));
college2.add(new Department("通信工程","通信工程不好学"));
college2.add(new Department("信息工程","通信工程好学"));
// 把学院加入学校
university.add(college1);
university.add(college2);
university.print();
}
}
// 运行结果
------清华大学------
-----计算机学院-----
[专业:软件工程] 软件工程不错
[专业:网络工程] 网络工程不错
[专业:计算机科学与技术] 计算机科学与技术是老牌的专业
-----信息工程学院-----
[专业:通信工程] 通信工程不好学
[专业:信息工程] 通信工程好学
4)JDK集合(HashMap)源码分析
5)注意事项和细节
- 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题
- 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动
- 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的 树形结构
- 需要遍历组织机构,或者处理的对象具有树形结构时,非常适合使用组合模式
- 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式
5、外观模式
Facade Pattern
[fəˈsɑːd]
又称:门面模式
1)基本介绍
- 外观模式(Facade),也叫“过程模式:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了 一个高层接口,这个接口使得这一子系统更加容易使用
- 外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需关心这个接口的调用,而无 需关心子系统的内部细节
- 解决多个复杂接口带来的使用困难,起到简化用户操作的作用
2)原理类图
- 外观类(Facade):为调用端提供统一的调用接口,外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当子系统对象
- 调用者(Client):外观接口的调用者
- 子系统的集合:指模块或者子系统,处理 Facade 对象指派的任务,是功能的实际提供者
3)案例分析
组建一个家庭影院:
DVD播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的功能,其过程为:
直接用遥控器:统筹各设备开关 开爆米花机
放下屏幕
开投影仪
开音响
开 DVD,选 dvd 去拿爆米花
调暗灯光
播放
观影结束后,关闭各种设备
DVD播放器 DVDPlayer
单例(静态类实现)
package facade;
/**
* @author ajun
* Date 2021/7/26
* @version 1.0
* DVD播放器
*/
public class DVDPlayer {
/**
* 私有构造器
*/
private DVDPlayer() {
}
/**
* 单例模式(静态内部类)
*/
private static class CreateInstance {
private static final DVDPlayer INSTANCE = new DVDPlayer();
}
/**
* 获取类对象的对外接口
*
* @return
*/
public static DVDPlayer getInstance() {
return CreateInstance.INSTANCE;
}
public void on() {
System.out.println("DVD播放器 开启");
}
public void off() {
System.out.println("DVD播放器 关闭");
}
public void play() {
System.out.println("DVD播放器 播放");
}
public void pause() {
System.out.println("DVD播放器 暂停");
}
}
爆米花机 Popcorn
单例(静态类实现)
package facade;
/**
* @author ajun
* Date 2021/7/26
* @version 1.0
* 爆米花机
*/
public class Popcorn {
/**
* 私有构造器
*/
private Popcorn() {
}
/**
* 静态内部类
*/
private static class CreateInstance {
private static final Popcorn INSTANCE = new Popcorn();
}
/**
* 获取类对象的对外接口
*
* @return
*/
public static Popcorn getInstance() {
return CreateInstance.INSTANCE;
}
public void on() {
System.out.println("爆火花机 开启");
}
public void off() {
System.out.println("爆火花机 关闭");
}
public void pop() {
System.out.println("爆火花机 制作爆米花");
}
}
投影仪 Projector
单例(静态类实现)
package facade;
/**
* @author ajun
* Date 2021/7/26
* @version 1.0
* 投影仪
*/
public class Projector {
/**
* 私有构造器
*/
private Projector() {
}
/**
* 静态内部类
*/
private static class CreateInstance {
private static final Projector INSTANCE = new Projector();
}
/**
* 获取类对象的对外接口
*
* @return
*/
public static Projector getInstance() {
return CreateInstance.INSTANCE;
}
public void on() {
System.out.println("投影仪 开启");
}
public void off() {
System.out.println("投影仪 关闭");
}
public void focus() {
System.out.println("投影仪 对焦");
}
}
影幕 Screen
单例(静态类实现)
package facade;
/**
* @author ajun
* Date 2021/7/26
* @version 1.0
* 影幕
*/
public class Screen {
/**
* 私有构造器
*/
private Screen(){}
/**
* 静态内部类
*/
private static class CreateInstance{
private static final Screen INSTANCE = new Screen();
}
/**
* 取类对象的对外接口
* @return
*/
public static Screen getInstance(){
return CreateInstance.INSTANCE;
}
public void up(){
System.out.println("影幕 升起");
}
public void down(){
System.out.println("影幕 落下");
}
}
立体声 Stereo
单例(静态类实现)
package facade;
/**
* @author ajun
* Date 2021/7/26
* @version 1.0
* 立体声
*/
public class Stereo {
/**
* 私有构造器
*/
private Stereo(){}
/**
* 静态内部类
*/
private static class CreateInstance{
private static final Stereo INSTANCE = new Stereo();
}
/**
* 取类对象的对外接口
* @return
*/
public static Stereo getInstance(){
return CreateInstance.INSTANCE;
}
public void on(){
System.out.println("立体声 开启");
}
public void off(){
System.out.println("立体声 关闭");
}
}
灯光 TheaterLight
单例(静态类实现)
package facade;
/**
* @author ajun
* Date 2021/7/26
* @version 1.0
* 灯光
*/
public class TheaterLight {
/**
* 私有构造器
*/
private TheaterLight(){}
/**
* 静态内部类
*/
private static class CreateInstance{
private static final TheaterLight INSTANCE = new TheaterLight();
}
/**
* 获取类对象的对外接口
* @return
*/
public static TheaterLight getInstance(){
return CreateInstance.INSTANCE;
}
public void on(){
System.out.println("灯光 开启");
}
public void off(){
System.out.println("灯光 关闭");
}
public void dim(){
System.out.println("灯光 调暗");
}
public void bright(){
System.out.println("灯光 调亮");
}
}
家庭影院 (外观类) HomeTheaterFacade
聚合了各个组成实体;提供 ready()、play()、pause()、end()方法
package facade;
/**
* @author ajun
* Date 2021/7/26
* @version 1.0
* 家庭影院 外观类
*/
public class HomeTheaterFacade {
// 聚合各个组成实体
private DVDPlayer dvdPlayer;
private TheaterLight theaterLight;
private Popcorn popcorn;
private Screen screen;
private Projector projector;
private Stereo stereo;
/**
* 构造器
*/
public HomeTheaterFacade() {
super();
this.dvdPlayer = DVDPlayer.getInstance();
this.theaterLight = TheaterLight.getInstance();
this.popcorn = Popcorn.getInstance();
this.screen = Screen.getInstance();
this.projector = Projector.getInstance();
this.stereo = Stereo.getInstance();
}
/**
* 准备
*/
public void ready() {
System.out.println("\n----- 准备放映 -----");
// 开启灯光
theaterLight.on();
// 开启爆火花机
popcorn.on();
// 制作爆米花
popcorn.pop();
// 落下影幕
screen.down();
// 开启投影仪
projector.on();
projector.focus();
// 开启立体声
stereo.on();
// 开启DVD播放器
dvdPlayer.on();
// 调暗灯光
theaterLight.dim();
}
/**
* 播放
*/
public void play() {
System.out.println("\n----- 开始放映 -----");
// DVD播放
dvdPlayer.play();
}
/**
* 暂停
*/
public void pause() {
System.out.println("\n----- 暂停放映 -----");
// DVD暂停
dvdPlayer.pause();
}
/**
* 结束
*/
public void end() {
System.out.println("\n----- 结束放映 -----");
// 调亮灯光
theaterLight.bright();
// 关闭DVD播放器
dvdPlayer.off();
// 关闭立体声
stereo.off();
// 关闭投影仪
projector.off();
// 升起影幕
screen.up();
// 关闭爆米花机
popcorn.off();
// 关闭灯光
theaterLight.off();
}
}
客户类 Client
package facade;
/**
* @author ajun
* Date 2021/7/26
* @version 1.0
* 客户
*/
public class Client {
public static void main(String[] args) {
HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade();
homeTheaterFacade.ready();
homeTheaterFacade.play();
homeTheaterFacade.pause();
homeTheaterFacade.end();
}
}
// 运行结果
----- 准备放映 -----
灯光 开启
爆火花机 开启
爆火花机 制作爆米花
影幕 落下
投影仪 开启
投影仪 对焦
立体声 开启
DVD播放器 开启
灯光 调暗
----- 开始放映 -----
DVD播放器 播放
----- 暂停放映 -----
DVD播放器 暂停
----- 结束放映 -----
灯光 调亮
DVD播放器 关闭
立体声 关闭
投影仪 关闭
影幕 升起
爆火花机 关闭
灯光 关闭
4)Mybatis 中应用案例
5)注意事项和细节
- 外观模式对外屏蔽了子系统的细节,降低了客户端对子系统使用的复杂性
- 外观模式把 客户端 与 子系统 解耦,让子系统内部的模块更易维护和扩展
- 合理的使用外观模式,可以更好的划分访问的层次
- 当系统需要进行分层设计时,可以考虑使用 Facade 模式
- 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个 Facade类,来提供遗留系统的比较清晰简单的接口,让新系统与 Facade 类交互,提高复用性
- 不能过多的或者不合理的使用外观模式,是否使用,应以让系统有层次,利于维护为目的
6、享元模式
Flyweight Pattern
[ˈflaɪweɪt]
1)基本介绍
- 享元模式(Flyweight Pattern)也叫蝇量模式:运用共享技术有效地支持大量细粒度的对象
- 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
- 享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不用总是创建新对 象,可以从缓冲池里拿。这样可以降低系统内存的使用,同时提高效率
- 享元模式经典的应用场景就是池技术了,String常量池、数据库连接池、缓冲池、线程池等等都是享元模式的应用,享元模式是池技术的重要实现方式
2)原理类图
- FlyWeight 是抽象的享元角色,是产品的抽象类;同时定义出对象的 外部状态 和 内部状态 的接口或实现
- ConcreteFlyWeight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务
- UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂
- FlyWeightFactory 享元工厂类,用于构建一个池容器(集合) ,同时提供从池中获取对象方法
内部状态和外部状态
- 享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两 个部分:内部状态和外部状态
- 内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
- 外部状态指对象得以依赖的一个标记,是随环境改变而改变的,不可共享的状态
3)案例分析
小型的外包项目:产品展示网站
- 有客户要求以 新闻 的形式发布
- 有客户人要求以 博客 的形式发布
- 有客户希望以 微信公众号 的形式发布
用户类 User
享元模式中的外部状态
随用户改变而改变的、不可共享的状态
package flyweight;
/**
* @author ajun
* Date 2021/7/27
* @version 1.0
* 用户类
* 享元模式中的外部状态
* 随用户改变而改变的、不可共享的状态
*/
public class User {
private String name;
public User(String name){
this.name = name;
}
public String getName() {
return name;
}
}
网站抽象类 WebSite
package flyweight;
/**
* @author ajun
* Date 2021/7/27
* @version 1.0
* 网站抽象类
* 供具体的网站实现类来继承
*/
public abstract class WebSite {
abstract void user(User user);
}
具体网站 ConcreteWebSite
package flyweight;
/**
* @author ajun
* Date 2021/7/27
* @version 1.0
* 具体网站
*/
public class ConcreteWebSite extends WebSite{
// 网站发布的类型(共享状态)
private String type;
/**
* 构造器
* @param type
*/
public ConcreteWebSite(String type) {
this.type = type;
}
@Override
public void user(User user) {
System.out.println("网站的发布形式为: " + type + " [使用中] 使用者是: " + user.getName());
}
}
网站工厂类 WebSiteFactory
生成网站
package flyweight;
import java.util.HashMap;
/**
* @author ajun
* Date 2021/7/27
* @version 1.0
* 网站工厂类
* 生成网站
*/
public class WebSiteFactory {
// 网站池
private HashMap<String,ConcreteWebSite> pool = new HashMap<>();
/**
* 根据网站类型获取网站
* @param type
* @return
*/
public WebSite getWebSiteByType(String type){
if(!pool.containsKey(type)){
pool.put(type,new ConcreteWebSite(type));
}
return pool.get(type);
}
/**
* 得到网站数量
* @return
*/
public int getWebSiteCount(){
return pool.size();
}
}
客户端 Client
package flyweight;
/**
* @author ajun
* Date 2021/7/27
* @version 1.0
*/
public class Client {
public static void main(String[] args) {
WebSiteFactory factory = new WebSiteFactory();
WebSite webSite1 = factory.getWebSiteByType("新闻");
webSite1.user(new User("Tom"));
WebSite webSite2 = factory.getWebSiteByType("博客");
webSite2.user(new User("Jack"));
WebSite webSite3 = factory.getWebSiteByType("博客");
webSite3.user(new User("Smith"));
WebSite webSite4 = factory.getWebSiteByType("公众号");
webSite4.user(new User("Mary"));
System.out.println(factory.getWebSiteCount());
}
}
运行结果
网站的发布形式为: 新闻 [使用中] 使用者是: Tom
网站的发布形式为: 博客 [使用中] 使用者是: Jack
网站的发布形式为: 博客 [使用中] 使用者是: Smith
网站的发布形式为: 公众号 [使用中] 使用者是: Mary
3
4)JDK中应用案例
Integer中的享元模式
//如果 Integer.valueOf(x) x在 -128 —— 127 之间,就是使用享元模式返回,如果不在范围内,则仍然 new
//小结:
//1.在 valueOf 方法中,先判断值是否在 IntegerCache 中,如果不在,就创建新的 Integer(new),否则,就直接从缓存池返回
//2. valueOf 方法,就使用到享元模式
//3.如果使用 valueOf 方法得到一个 Integer 实例,范围在 -128 —— 127之间,执行速度比 new 快
Integer x = Integer.valueOf(127); //得到 x实例,类型 Integer
Integer y = new Integer(127); //得到 y实例,类型 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
5)注意事项和细节
- 在享元模式中这样理解,享就表示共享,元表示对象
- 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以内部化时,我们就可以考虑选用享元模式
- 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable 存储
- 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
- 享元模式提高了系统的复杂度。需要分离出 内部状态 和 外部状态 ,而内部状态具有固化特性,不应该随着外部状态的改变而改变,这是我们使用享元模式需要注意的地方
- 使用享元模式时,注意划分 内部状态 和 外部状态,并且需要有一个工厂类加以控制
- 享元模式经典的应用场景是需要缓冲池的场景;比如:String 常量池、数据库连接池 、线程池
7、代理模式
Proxy Pattern
详见Spring中的代理模式点击进入
1)静态代理
2)动态代理
- 基于接口的动态代理----JDK动态代理
- 基于类的动态代理--cglib