设计模式—结构型模式
结构型模式有以下几种模式:
- 适配器模式
- 桥接模式
- 装饰模式
- 组合模式
- 外观模式
- 享元模式
- 代理模式。
注意:加粗的为最主要的模式
适配器模式
基本介绍
- 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
- 适配器模式属于结构型模式
- 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
工作原理
- 适配器模式:将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼 容
- 从用户的角度看不到被适配者,是解耦的
- 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
- 用户收到反馈结果,感觉只是和目标接口交互
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kyCh6Gwz-1610701140102)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210104144135504.png)]
类适配器模式
基本介绍:
Adapter类,通过继承 src类,实现 dst 类接口,完成src->dst的适配。
应用实例:
以生活中充电器的例子来讲解适配器,充电器本身相当于Adapter,220V交流电相当于src (即被适配者),我们的目dst(即 目标)是5V直流电
代码实现:
//220电压,source被适配着
public class Volt220 {
public int output220(){
int src=220;
System.out.println("输出220v电压");
return src;
}
}
----
//目标电流,dst
public interface Volt5 {
public int output5V();
}
----
//插头类,也就是适配器Adapter
public class Plug extends Volt220 implements Volt5{
@Override
public int output5V() {
int volt = output220();
int dst=volt/44;
System.out.println("输出电压5V");
return dst;
}
}
-----
//手机充电,需要5v的电压
public class Phone{
public void charge(Volt5 volt5){
int i = volt5.output5V();
if(i==5) {
System.out.println(i + "伏电流正在为手机充电");
}else {
System.out.println("危险!电压过大");
}
}
}
----
//调用者
public class Client {
public static void main(String[] args) {
Phone phone=new Phone();
Plug plug=new Plug();
phone.charge(plug);
}
}
优缺点:
- Java是单继承机制,所以类适配器需要继承src类这一点算是一个缺点, 因为这要求dst必须是接口,有一定局限性;
- src类的方法在Adapter中都会暴露出来,也增加了使用的成本。
- 由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了。
对象适配器模式
基本介绍:
- 基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而是持有src类的实例,以解决兼容性的问题。 即:持有 src类,实现 dst 类接口,完成src->dst的适配
- 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系。
- 对象适配器模式是适配器模式常用的一种
应用实例:
以生活中充电器的例子来讲解适配器,充电器本身相当于Adapter,220V交流电相当于src (即被适配者),我们的目dst(即目标)是5V直流电,使用对象适配器模式完成
代码实现:
//220电压,source被适配着
public class Volt220 {
public int output220(){
int src=220;
System.out.println("输出220v电压");
return src;
}
}
----------
//目标电流,dst
public interface Volt5 {
public int output5V();
}
----------
//插头类,也就是适配器Adapter
public class Plug implements Volt5 {
private Volt220 volt220;//通过组合方式
public Volt220 getVolt220() {
return volt220;
}
public void setVolt220(Volt220 volt220) {
this.volt220 = volt220;
}
public Plug(Volt220 volt220){
this.volt220=volt220;
}
@Override
public int output5V() {
int volt = volt220.output220();
int dst=volt/44;
System.out.println("输出电压5V");
return dst;
}
}
------------
//手机充电,需要5v的电压
public class Phone{
public void charge(Volt5 volt5){
int i = volt5.output5V();
if(i==5) {
System.out.println(i + "伏电流正在为手机充电");
}else {
System.out.println("危险!电压过大");
}
}
}
------------
public class Client {
public static void main(String[] args) {
Phone phone=new Phone();
Plug plug=new Plug(new Volt220());
phone.charge(plug);
}
}
优缺点:
- 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承src的局限性问题,也不再要求dst必须是接口。
- 使用成本更低,更灵活。
接口适配器模式
基本介绍:
- 一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。
- 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
- 适用于一个接口不想使用其所有的方法的情况。
代码实现
//接口
public interface UserDao {
public void add();
public void del();
public void test();
public void hh();
}
//抽象层
public class UserAbstract implements UserDao {
@Override
public void add() {
}
@Override
public void del() {
}
@Override
public void test() {
}
@Override
public void hh() {
}
}
//具体实现
public class UserDaoExtend extends UserAbstract {
@Override
public void add() {
System.out.println("添加");
}
}
适配器在JDK源码分析
适配器模式在SpringMVC框架应用的源码分析
桥接模式
基本介绍
- 桥接模式(Bridge模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。2)
- 是一种结构型设计模式
- Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展
原理类图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1j4rfaa3-1610701140113)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210108151038608.png)]
原理类图说明
- Client类:桥接模式的调用者
- 抽象类(Abstraction) :维护了 Implementor / 即它的实现类ConcreteImplementorA…, 二者是聚合关系, Abstraction 充当桥接类
- RefinedAbstraction:是Abstraction抽象类的子类
- Implementor :行为实现类的接口
- ConcreteImplementorA/B :行为的具体实现类
从UML 图看出:这里的抽象类和接口是聚合的关系,其实调用和被调用关系
案例理解
现在对不同手机类型的不同品牌实现操作编程(比如: 开机、关机、上网,打电话等)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2t9fMPWk-1610701140115)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210108152034417.png)]
传统方案
传统方案解决手机使用问题(类图)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SPQNZFqQ-1610701140119)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210108152210905.png)]
传统方案解决手机操作问题分析
- 扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。
- 违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本
桥接模式方案
桥接模式类图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NxLeAeDd-1610701140122)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210108152340365.png)]
代码实现:
//接口类brand
public interface Brand {
public void open();
public void close();
public void call();
}
----
//接口实现类
public class HuaWei implements Brand {
@Override
public void open() {
System.out.println("华为开机");
}
@Override
public void close() {
System.out.println("华为关机");
}
@Override
public void call() {
System.out.println("华为打电话");
}
}
----
public class Vivo implements Brand {
@Override
public void open() {
System.out.println("小米打开");
}
@Override
public void close() {
System.out.println("小米关闭");
}
@Override
public void call() {
System.out.println("小米打电话");
}
}
----
//抽象类Phone
public abstract class Phone {
private Brand brand;
public Phone(Brand brand){
this.brand=brand;
}
protected void open(){
brand.open();
}
protected void close(){
brand.close();
}
protected void call(){
brand.call();
}
}
----
//抽象继承
public class FolderPhone extends Phone {
public FolderPhone(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("折叠样式手机");
}
}
----
public class SpinPhone extends Phone {
public SpinPhone(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("折叠样式手机");
}
}
----
//调用
public class Client {
public static void main(String[] args) {
Phone folderPhone = new FolderPhone(new HuaWei());
folderPhone.call();
folderPhone.open();
folderPhone.close();
}
}
优缺点
- 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
- 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成。
- 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
- 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性,即需要有这样的应用场景。
- -JDBC驱动程序
- -银行转账系统
- -转账分类: 网上转账,柜台转账,AMT转账
- -转账用户类型:普通用户,银卡用户,金卡用户…
- -消息管理
- -消息类型:即时消息,延时消息
- -消息分类:手机短信,邮件消息,QQ消息…
桥接模式在JDK源码分析
桥接模式在jdbc的应用分析
装饰者设计模式
基本介绍
- 装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)
- 这里提到的动态的将新功能附加到对象和ocp原则,在后面的应用实例上会以代码的形式体现,请同学们注意体会
原理分析
装饰者模式可以分为两个部分,一个是主体,一个是包装
- 主体:比如:陶瓷、衣服 (Component) // 被装饰者
- 包装:比如:报纸填充、塑料泡沫、纸板、木板(Decorator)
原理类图(代补自己画的)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wq1Z3WE4-1610701140132)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210108160251246.png)]
原理类图解析
- Component主体:比如类似前面的Drink
- ConcreteComponent和Decorator,ConcreteComponent:具体的主体,比如前面的各个单品咖啡Decorator: 装饰者,比如各调料.
- 在如图的ComponentConcreteComponent之间,如果ConcreteComponent类很多,还可以设计一个缓冲层,将共有的部分提取出来,抽象层一个类
案例理解
星巴克咖啡订单项目(咖啡馆):
- 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
- 调料:Milk、Soy(豆浆)、Chocolate
- 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
- 使用OO的来计算不同种类咖啡的费用 客户可以点单品咖啡,也可以单品咖啡调料组合
案例类图(待补充自己画胡)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r8I9K5NN-1610701140135)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210108160706978.png)]
类图说明
- Drink 类就是前面说的抽象类, Component
- ShortBlack 就单品咖啡
- Decorator 是一个装饰类,含有一个被装饰的对象(Drink obj)
- Decorator 的cost 方法 进行一个费用的叠加计算,递归的计算价格
代码实现
//Drink
public abstract class Drink {
private String des;
private Double price;
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public abstract double cost();
}
//decorate
public class Decorate extends Drink {
private Drink drink;
public Decorate(Drink drink) {
this.drink=drink;
}
@Override
public double cost() {
return super.getPrice()+drink.cost();
}
}
//decorate的具体类
public class Milk extends Decorate{
public Milk(Drink drink) {
super(drink);
setDes("牛奶");
setPrice(1.5);
}
}
---
public class Sugar extends Decorate {
public Sugar(Drink drink) {
super(drink);
setDes("白糖");
setPrice(2.6);
}
}
//coffee抽象层继承Drink
public abstract class Coffee extends Drink{
@Override
public double cost() {
return super.getPrice();
}
}
//coffee的具体类
public class BlackCoffee extends Coffee {
public BlackCoffee() {
setDes("黑咖啡");
setPrice(2.8);
}
}
public class CivetCoffee extends Coffee {
public CivetCoffee() {
setDes("猫屎咖啡");
setPrice(8.8);
}
}
优缺点:
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了。
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
- 由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。
装饰者模式在JDK源码分析
Java的IO结构,FilterInputStream就是一个装饰者
组合模式
基本介绍
- 组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系。
- 组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
- 这种类型的设计模式属于结构型模式。
- 组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象
原理类图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QhaKPgeI-1610701140137)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210108171348393.png)]
原理类图说明
- Component :这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component子部件, Component可以是抽象类或者接口
- Leaf:在组合中表示叶子节点,叶子节点没有子节点
- Composite :非叶子节点,用于存储子部件,在Component接口中实现子部件的相关操作,比如增加(add),删除。
案例理解
看一个学校院系展示需求
编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pe3yQQ2L-1610701140139)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210108171540128.png)]
传统方式实现
- 将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的
- 实际上我们的要求是 :在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系, 因此这种方案,不能很好实现的管理的操作,比如对学院、系的添加,删除,遍历等
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zl0axP53-1610701140147)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210108171628136.png)]
组合模式实现
类图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uaUmuCZw-1610701140150)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210108171740364.png)]
代码实现:
//组合中对象声明
public abstract class OrganizationComponent {
private String name;
private String desc;
public OrganizationComponent(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
protected void add(OrganizationComponent organizationComponent){
throw new UnsupportedOperationException();
}
protected void remove(OrganizationComponent organizationComponent){
throw new UnsupportedOperationException();
}
protected abstract void print();
}
---
//学校 非叶子节点
public class University extends OrganizationComponent {
private List<OrganizationComponent> organizationComponents=new ArrayList<>();
public University(String name, String desc) {
super(name, desc);
}
@Override
protected void print() {
System.out.println("-------------"+getName()+"----------------");
for(OrganizationComponent organizationComponent:organizationComponents){
organizationComponent.print();
}
}
@Override
public void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}
@Override
public void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
}
---
//学院 非叶子节点
public class College extends OrganizationComponent {
private List<OrganizationComponent> organizationComponents=new ArrayList<>();
public College(String name, String desc) {
super(name, desc);
}
@Override
protected void print() {
System.out.println("-------------"+getName()+"----------------");
for(OrganizationComponent organizationComponent:organizationComponents){
organizationComponent.print();
}
}
@Override
public void add(OrganizationComponent organizationComponent) {
organizationComponents.add(organizationComponent);
}
@Override
public void remove(OrganizationComponent organizationComponent) {
organizationComponents.remove(organizationComponent);
}
}
---
//专业 叶子节点
public class Department extends OrganizationComponent{
public Department(String name, String desc) {
super(name, desc);
}
@Override
protected void print() {
System.out.println("-------------"+getName()+"----------------");
}
}
---
//调用者
public class Client {
public static void main(String[] args) {
University university=new University("广东理工学院","弟弟学校");
College college=new College("信息学院","教混子");
College college2=new College("艺术学院","教婆娘");
Department department=new Department("软件工程","学划水");
Department department2=new Department("软件技术","学摸鱼");
Department department3=new Department("软件爬虫","学捉虫");
Department department4=new Department("服装","做衣服");
Department department5=new Department("画画","学画画");
college.add(department);
college.add(department2);
college.add(department3);
college2.add(department4);
college2.add(department5);
university.add(college);
university.add(college2);
university.print();
}
}
优缺点
- 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
- 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动.
- 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构
- 需要遍历组织机构,或者处理的对象具有树形结构时, 非常适合使用组合模式.
- 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式
组合模式在JDK源码分析
Java的集合类-HashMap就使用了组合模式
外观模式
基本介绍
- 外观模式(Facade),也叫“过程模式:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
- 外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节
原理类图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-frQ0tOUN-1610701140151)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210111175616733.png)]
原理类图说明
- 外观类(Facade): 为调用端提供统一的调用接口, 外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当子系统对象
- 调用者(Client): 外观接口的调用者
- 子系统的集合:指模块或者子系统,处理Facade 对象指派的任务,他是功能的实际提供者
案例理解
组建一个家庭影院:
DVD播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的
功能,其过程为:
- 直接用遥控器:统筹各设备开关
- 开爆米花机
- 放下屏幕
- 开投影仪
- 开音响
- 开DVD,选dvd
- 去拿爆米花
- 调暗灯光
- 播放
- 观影结束后,关闭各种设备
传统方式实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5WKCQPyB-1610701140152)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210111180731333.png)]
传统方式的问题:
- 在ClientTest 的main方法中,创建各个子系统的对象,并直接去调用子系统(对象) 相关方法,会造成调用过程混乱,没有清晰的过程
- 不利于在ClientTest 中,去维护对子系统的操作
外观模式实现
- 外观模式可以理解为转换一群接口,客户只要调用一个接口,而不用调用多个接口才能
- 达到目的。比如:在pc上安装软件的时候经常有一键安装选项(省去选择安装目录、安装的组件等等),还有就是手机的重启功能(把关机和启动合为一个操作)。
- 外观模式就是解决多个复杂接口带来的使用困难,起到简化用户操作的作用
示意图说明
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pNPvLfSW-1610701140156)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210111181325474.png)]
代码实现:
//DVD
public class DVDPlayer {
private DVDPlayer(){
}
private static DVDPlayer dvdPlayer=new DVDPlayer();
public static DVDPlayer getInstance(){
return dvdPlayer;
}
public void on(){
System.out.println("DVD on");
}
public void off(){
System.out.println("DVD off");
}
public void play(){
System.out.println("DVD play");
}
public void pause(){
System.out.println("DVD pause");
}
}
//projector
public class Projector {
private Projector(){
}
private static Projector projector=new Projector();
public static Projector getInstance(){
return projector;
}
public void on(){
System.out.println("Projector on");
}
public void off(){
System.out.println("Projector off");
}
public void focus(){
System.out.println("focus play");
}
}
//其余的类也类似上面
//外观类
public class HomeTheatre {
private DVDPlayer dvdPlayer;
private Screen screen;
private Lamplight lamplight;
private Projector projector;
private CornPopper cornPopper;
private SurroundSound surroundSound;
public HomeTheatre(){
this.dvdPlayer=DVDPlayer.getInstance();
this.screen=Screen.getInstance();
this.lamplight=Lamplight.getInstance();
this.projector=Projector.getInstance();
this.cornPopper=CornPopper.getInstance();
this.surroundSound=SurroundSound.getInstance();
}
public void prepare(){
cornPopper.on();
screen.down();
projector.on();
dvdPlayer.on();
projector.focus();
lamplight.dim();
surroundSound.on();
cornPopper.off();
cornPopper.get();
System.out.println("准备工作已完成");
}
public void start(){
dvdPlayer.play();
}
public void pause(){
//这里也可以加其他的
dvdPlayer.pause();
}
public void terminate(){
lamplight.light();
surroundSound.off();
dvdPlayer.off();
projector.off();
screen.up();
}
}
//client调用
public class Client {
public static void main(String[] args) {
HomeTheatre homeTheatre=new HomeTheatre();
homeTheatre.prepare();
System.out.println("-----------------");
homeTheatre.start();
System.out.println("----------------");
homeTheatre.pause();
System.out.println("----------------");
homeTheatre.terminate();
System.out.println("----------------");
}
}
优缺点
- 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
- 外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易维护和扩展
- 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
- 当系统需要进行分层设计时,可以考虑使用Facade模式
- 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口,让新系统与Facade类交互,提高复用性
- 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。要以让系统有层次,利于维护为目的。
外观模式在MyBatis框架应用的源码分析
MyBatis 中的Configuration 去创建MetaObject 对象使用到外观模式
享元模式
基本介绍
- 享元模式(Flyweight Pattern) 也叫 蝇量模式: 运用共享技术有效地支持大量细粒度的对象
- 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个
- 享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率
- 享元模式经典的应用场景就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式
原理类图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JpQ3rL8j-1610701140163)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210113115108351.png)]
原理类图说明
- FlyWeight 是抽象的享元角色,他是产品的抽象类,同时定义出对象的外部状态和内部状态(后面介绍)的接口或实现
- ConcreteFlyWeight是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务
- UnSharedConcreteFlyWeight是不可共享的角色,一般不会出现在享元工厂。
- FlyWeightFactory享元工厂类,用于构建一个池容器(集合),同时提供从池中获取对象方法
内部状态和外部状态
-
享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两个部分:内部状态和外部状态
-
内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
-
外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
-
举个列子
- 比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一 点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态。围棋理论上有361个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题
案例理解
小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同:
- 有客户要求以新闻的形式发布
- 有客户人要求以博客的形式发布
- 有客户希望以微信公众号的形式发布
传统方式实现
直接复制粘贴一份,然后根据客户不同要求,进行定制修改,给每个网站租用一个空间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C2ddDJrq-1610701140172)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210113115518885.png)]
问题分析
需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费
享元模式实现
UML类图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0H1OVhTa-1610701140174)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210113115616423.png)]
代码实现
//website 抽象的享元角色
public abstract class WebSite {
public abstract void use(User user);
}
//ConcreteWebSite 内部状态
public class ConcreteWebSite extends WebSite {
private String type="";
public ConcreteWebSite(String type) {
this.type = type;
}
@Override
public void use(User user) {
user.getName();
System.out.println("网站发布形式为"+this.type+"当前使用者为"+user.getName());
}
}
//User 外部状态
public class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
//WebSiteFactory 享元对象工厂
public class WebSiteFactory {
private Map<String,ConcreteWebSite> webSiteMap=new HashMap<>();
public WebSite getWebSite(String type){
WebSite webSite = webSiteMap.get(type);
if(webSite==null){
webSiteMap.put(type,new ConcreteWebSite(type));
}
return webSiteMap.get(type);
}
public int getWebSiteSize(){
return webSiteMap.size();
}
}
//调用测试
public class Client {
public static void main(String[] args) {
User user=new User("fys",12);
WebSiteFactory webSiteFactory=new WebSiteFactory();
WebSite web = webSiteFactory.getWebSite("web");
web.use(user);
System.out.println(webSiteFactory.getWebSiteSize());
System.out.println("-------------------------");
WebSite tb = webSiteFactory.getWebSite("tb");
tb.use(user);
System.out.println(webSiteFactory.getWebSiteSize());
}
}
优缺点:
- 在享元模式这样理解,“享”就表示共享,“元”表示对象
- 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式
- 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储
- 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
- 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方.
- 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
- 享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、数据库连接池
享元模式在JDK-Interger的应用源码分析
Integer中的享元模式
代理模式
基本介绍
- 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
- 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
- 代理模式有不同的形式, 主要有三种 静态代理、动态代理 (JDK代理、接口代理)和 Cglib代理 (可以在内存动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。
原理类图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B73yCXZU-1610701140188)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210115105814589.png)]
原理类图说明
- TargetObject:目标对象,需要被代理的对象
- ProxyObject:代理对象
静态代理
基本介绍
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类
案例理解
- 定义一个接口:ITeacherDao
- 目标对象TeacherDAO实现接口ITeacherDAO
- 使用静态代理方式,就需要在代理对象TeacherDAOProxy中也实现ITeacherDAO
- 调用的时候通过调用代理对象的方法来调用目标对象.
- 特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法
类图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jVoembQX-1610701140190)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210115111441584.png)]
代码实现
//ITeachDao
public interface ITeachDao {
void teach();
}
//TeachDao
public class TeachDao implements ITeachDao {
@Override
public void teach() {
System.out.println("teach....中");
}
}
//TeachProxy
public class TeachProxy implements ITeachDao {
private TeachDao teachDao;
public TeachProxy(TeachDao teachDao){
this.teachDao=teachDao;
}
@Override
public void teach() {
teachDao.teach();
}
}
//Client
public class Client {
public static void main(String[] args) {
TeachDao teachDao=new TeachDao();
TeachProxy teachProxy=new TeachProxy(teachDao);
teachProxy.teach();
}
}
优缺点
- 优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展
- 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
- 一旦接口增加方法,目标对象与代理对象都要维护
动态代理
基本介绍
- 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
- 动态代理也叫做:JDK代理、接口代理
原理:JDK中生成代理对象的API
- 代理类所在包:java.lang.reflect.Proxy
- JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是: static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
案例理解
将前面的静态代理改进成动态代理模式(即:JDK代理模式)
类图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vULcWptg-1610701140192)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210115115106083.png)]
代码实现
//ITeachDao
public interface ITeachDao {
void teach();
}
//TeachDao
public class TeachDao implements ITeachDao {
@Override
public void teach() {
System.out.println("动态代理授课中");
}
}
//ProxyFactory
public class ProxyFactory {
private TeachDao teachDao;
public ProxyFactory(TeachDao teachDao) {
this.teachDao = teachDao;
}
//给目标对象 生成一个代理对象
public Object getProxyInstance(){
//说明
/*
* public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
//1. ClassLoader loader : 指定当前目标对象使用的类加载器, 获取加载器的方法固定
//2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型
//3. InvocationHandler h : 事情处理,执行目标对象的方法时,会触发事情处理器方法, 会把当前执行的目标对象方法作为参数传入
*/
return Proxy.newProxyInstance(teachDao.getClass().getClassLoader(), teachDao.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
Object invoke = method.invoke(teachDao, objects);
return invoke;
}
}
);
}
}
//client
public class Client {
public static void main(String[] args) {
TeachDao teachDao=new TeachDao();
ProxyFactory proxyFactory=new ProxyFactory(teachDao);
ITeachDao proxyInstance = (ITeachDao) proxyFactory.getProxyInstance();
System.out.println(proxyInstance);
proxyInstance.teach();
}
}
Cglib代理
基本介绍
- 静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现 代理-这就是Cglib代理
- Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib代理归属到动态代理。
- Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接 口.它广泛的被许多AOP的框架使用,例如Spring AOP,实现方法拦截
- Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类
在AOP编程中如何选择代理模式
- 目标对象需要实现接口,用JDK代理
- 目标对象不需要实现接口,用Cglib代理
实现步骤
- 需要引入cglib的jar文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CrV7xL0X-1610701140194)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210115161336194.png)]
- 在代理工厂实现接口MethodInterceptor
- 实现intercept方法
注意事项
- 在内存中动态构建子类,注意代理的类不能为final,否则报错java.lang.IllegalArgumentException:
- 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
案例理解
将前面的静态代理改进成Cglib代理模式
类图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3nM9ZyBm-1610701140196)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210115161658368.png)]
代码实现
//TeachDao
public class TeachDao {
public void teach(){
System.out.println("授课中");
}
}
//ProxyFactory
public class ProxyFactory implements MethodInterceptor {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//返回一个代理对象
public Object getProxyInstance(){
//1. 创建一个工具类
Enhancer enhancer = new Enhancer();
//2. 设置父类
enhancer.setSuperclass(target.getClass());
//3. 设置回调函数
enhancer.setCallback(this);
//4. 创建子类对象,即代理对象
return enhancer.create();
}
//重写 intercept 方法,会调用目标对象的方法
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object invoke = method.invoke(target, objects);
return invoke;
}
}
//client
public class Client {
public static void main(String[] args) {
TeachDao teachDao=new TeachDao();
ProxyFactory proxyFactory=new ProxyFactory(teachDao);
TeachDao proxyInstance = (TeachDao) proxyFactory.getProxyInstance()
proxyInstance.teach();
}
}
代理模式(Proxy)的变体
1) 防火墙代理
内网通过代理穿透防火墙,实现对公网的访问。
2) 缓存代理
比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则ok,如果取不到资源,
再到公网或者数据库取,然后缓存。
3) 远程代理
远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和
真正的远程对象沟通信息。
public void teach(){
System.out.println(“授课中”);
}
}
//ProxyFactory
public class ProxyFactory implements MethodInterceptor {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//返回一个代理对象
public Object getProxyInstance(){
//1. 创建一个工具类
Enhancer enhancer = new Enhancer();
//2. 设置父类
enhancer.setSuperclass(target.getClass());
//3. 设置回调函数
enhancer.setCallback(this);
//4. 创建子类对象,即代理对象
return enhancer.create();
}
//重写 intercept 方法,会调用目标对象的方法
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object invoke = method.invoke(target, objects);
return invoke;
}
}
//client
public class Client {
public static void main(String[] args) {
TeachDao teachDao=new TeachDao();
ProxyFactory proxyFactory=new ProxyFactory(teachDao);
TeachDao proxyInstance = (TeachDao) proxyFactory.getProxyInstance()
proxyInstance.teach();
}
}
代理模式(**Proxy)**的变体
1) 防火墙代理
内网通过代理穿透防火墙,实现对公网的访问。
2) 缓存代理
比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则ok,如果取不到资源,
再到公网或者数据库取,然后缓存。
3) 远程代理
远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和
真正的远程对象沟通信息。
4) 同步代理:主要使用在多线程编程中,完成多线程间同步工作
参考资料地址说明
参考资料来源:www.atguigu.com
[参考资料来源][www.atguigu.com]
如若侵权,请联系删除