一、适配器模式(类适配器、对象适配器、接口适配器)
1、现实生活中的例子
泰国插座用的是两孔的(欧标),可以买个多功能转换插头(适配器),这样就可以使用了国内的电器了。
2、基本介绍
适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主要目的是兼容性,让原本接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)。适配器模式属于结构型模式。主要分为三类:类适配器模式、对象适配器模式、接口适配器模式。工作原理
适配器模式:将一个类的接口转成另一种接口,让原本不兼容的类可以兼容。从用户的角度看不到被适配者,是解耦的。用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法。用户收到反馈结果,感觉只是和目标接口交互,如图。3、类适配器模式
Adapter类,通过继承 src 类,实现dst类接口,完成 src->dst 的适配。
应用实例说明:以生活中充电器的例子来讲解适配器,充电器本身相当于 Adapter,220V 交流电相当于 src(即被是适配者),我们的目标 dst 是5v直流电。
思路分析
代码实现
适配接口
//适配器接口public interface IVoltage5V {int output5V();}
被适配的类
//被适配的类public class Voltage220V {public int output220V() {int src = 220;System.out.println("输出"+src+"V电压");return src;}}
适配器类
//适配器public class VoltageAdapter extends Voltage220V implements IVoltage5V {@Overridepublic int output5V() {int src = output220V();System.out.println("适配器进行电压转换...");int dst = src/44;System.out.println("转换完成,输出"+dst+"V电压");return dst;}}
手机(可以使用适配器的类)
public class Phone {//充电public void charging(IVoltage5V iVoltage5V) {if (iVoltage5V.output5V() == 5) {System.out.println("电压正常,进行充电");} else if (iVoltage5V.output5V() > 5) {System.out.println("电压过高,不能充电");}}}
客户端
public class Client {public static void main(String[] args) {Phone phone = new Phone();phone.charging(new VoltageAdapter());}}
类适配器模式注意事项和细节
Java 是单继承机制,所以类适配器需要继承 src 类这一点是缺点,因为者要求 dst 必须是接口,有一定的局限性。src 类的方法在 Adapter 中都会暴露出来,也增加了使用的成本。由于其继承了 src 类,所以它可以根据需求重写 src 类的方法,是的 Adapter 的灵活性增强了。4、对象适配器模式
以生活中充电器的例子来讲解适配器,充电器本身相当于 Adapter,220V 交流电相当于 src(即被适配者),我们的目标 dst 是5V 直流电,使用对象适配器模式完成。
思路分析
我们只需要修改下适配器即可,我们将继承 Voltage220V 改成 持有Voltage220V 类型属性即可,如下图
代码实现(只修改了客户端和适配器类:适配器类改为聚合关系)
//适配器public class VoltageAdapter implements IVoltage5V {private Voltage220V voltage220V;public VoltageAdapter(Voltage220V voltage220V) {this.voltage220V = voltage220V;}@Overridepublic int output5V() {if(voltage220V != null) {int src = voltage220V.output220V();System.out.println("适配器进行电压转换...");int dst = src/44;System.out.println("转换完成,输出"+dst+"V电压");return dst;}return 0;}}
public class Client {public static void main(String[] args) {Phone phone = new Phone();phone.charging(new VoltageAdapter(new Voltage220V()));}}
对象适配器模式注意事项和细节
1、对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承 src 的局限性问题,也不在要求 dst 必须是接口。
2、使用成本更低,更灵活
5、接口适配器模式
介绍
1、一些数据称为:适配器模式(Default Adapter Pattern)或者缺省适配器模式。
2、核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象实现接口,并未该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类就可以有选择地覆盖父类的某些方法来实现需求。
3、使用与一个接口不想使用其所有方法的情况。
实例
类图:
public interface Interface {void operator1();void operator2();void operator3();void operator4();}public abstract class AbsAdapter implements Interface {@Overridepublic void operator1() {}@Overridepublic void operator2() {}@Overridepublic void operator3() {}@Overridepublic void operator4() {}}public class A {public static void main(String[] args) {AbsAdapter absAdapter = new AbsAdapter(){//只需要去覆盖我们有需要的方法即可@Overridepublic void operator1() {System.out.println("使用了 operator1 方法");}};absAdapter.operator1();}}
在Android的属性动画中 ValueAnimator类,就使用的接口适配器模式。
6、适配器模式在SpringMVC框架应用的源码剖析
1、SpringMVC 中的 HandlerAdapter ,就使用了适配器模式
2、SpringMVC 处理请求的流程回顾
3、使用HandlerAdapter的原因分析
可以看到不同类型的处理器,有着不同的实现方式,其调用方式就是不确定的。如果需要调用 Controller 的方法,调用的时候就得不断的使用【if else】判断是哪一种子类然后执行。那么如果后面要扩展 Controller ,就得修改原来的代码,这样就违背了 OCP 原则。
4、代码分析+Debug源码
手动编写SpringMVC 通过适配器模式执行 doDispatch的过程
类图
代码实现
public interface Controller {}class HttpController implements Controller {public void doHttpHandler() {System.out.println("http handler....");}}class SimpleController implements Controller {public void doSimpleHandler() {System.out.println("simple handler....");}}class AnnotationController implements Controller {public void doAnnotationHandler() {System.out.println("annotation handler....");}}
public interface HandlerAdapter {boolean supports(Object handler);void handle(Object handler);}class HttpHandlerAdapter implements HandlerAdapter {@Overridepublic boolean supports(Object handler) {return handler instanceof HttpController;}@Overridepublic void handle(Object handler) {((HttpController)handler).doHttpHandler();}}class SimpleHandlerAdapter implements HandlerAdapter {@Overridepublic boolean supports(Object handler) {return handler instanceof SimpleController;}@Overridepublic void handle(Object handler) {((SimpleController)handler).doSimpleHandler();}}class AnnotationHandlerAdapter implements HandlerAdapter {@Overridepublic boolean supports(Object handler) {return handler instanceof AnnotationController;}@Overridepublic void handle(Object handler) {((AnnotationController)handler).doAnnotationHandler();}}
public class DispatcherServlet {private List<HandlerAdapter> handlerAdapters = new ArrayList<>();public DispatcherServlet() {handlerAdapters.add(new HttpHandlerAdapter());handlerAdapters.add(new SimpleHandlerAdapter());handlerAdapters.add(new AnnotationHandlerAdapter());}public HandlerAdapter getHandler(Controller controller) {//遍历:根据得到的Controller(handler),返回对应的适配器for (HandlerAdapter adapter : this.handlerAdapters) {if (adapter.supports(controller)) {return adapter;}}return null;}public void doDispatch() {//此处模拟 SpringMVC 从Request取 Handler 对象//适配器可以获取到希望的ControllerHttpController controller = new HttpController();// SimpleController controller = new SimpleController();// AnnotationController controller = new AnnotationController();//得到对应适配器HandlerAdapter adapter = getHandler(controller);//通过适配器执行对应的方法adapter.handle(controller);}public static void main(String[] args) {new DispatcherServlet().doDispatch();}}
说明:
Spring 定义了一个适配器接口,是的每一种Controller有一种对应的适配器实现类。适配器代替Controller执行相应的方法。扩展Controller时,只需要增加一个适配器类就完成了 SpringMVC的扩展了这就是设计模式的力量
7、适配器模式的注意事项和细节
1、适配器模式有三种命名方式,是根据 src(被适配)是以怎么样的形式给到 Adapter来命名的。
类适配器模式:以类给到Adapter,就是将 src 当做父类,继承。对象适配器模式:以对象给到 Adapter,就是将 src 作为一个对象,持有(聚合)。接口适配器模式:以接口给到 Adapter,就是将 src 作为一个接口,实现(可以抽象适配器类实现 src,在默认实现,再根据需要在子类实现相应的方法)。
2、Adapter 模式最大的作用还是将原本不兼容的接口融合在一起。
3、实际开发中,实现起来不拘泥于我们讲解的三种经典形式。
二、桥接模式(Bridge)
1、需求
现在对不同手机类型的不同品牌实现编程操作。比如,开机、关机、上网、打电话等,如图:
2、传统方案 解决手机操作的问题
对应类图
问题分析
扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样,如果我们增加一个手机品牌,也要在各个手机样式类下增加。违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本。解决方案,使用桥接模式。
3、桥接模式(Bridge)
基本介绍
1.桥接模式(Bridge)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
2.是一种结构型设计模式。
3.Bridge 模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展。
原理类图
上图说明:
Clent 类:桥接模式的调用者。抽象类(Abstraction):维护了 Implementor,即它的实现类ConcreteImplementorA...,二者是聚合的关系,Abstraction 充当桥接类。RefinedAbstraction:是 Abstraction 抽象类的子类。Implementor:行为实现类的接口。ConcreteImplementorA/B:行为具体实现类。这里的抽象类和接口是聚合的关系,其实是调用和被调用的关系。
桥接模式解决手机操作问题
手机品牌 (Vivo、小米...)
public interface Brand {void call();void open();void close();}public class Vivo implements Brand {public void call() {System.out.println("Vivo 手机打电话");}public void open() {System.out.println("Vivo 手机开机");}public void close() {System.out.println("Vivo 手机关机");}}public class XiaoMi implements Brand {public void call() {System.out.println("小米 手机打电话");}public void open() {System.out.println("小米 手机开机");}public void close() {System.out.println("小米 手机关机");}}
手机样式(折叠、滑盖...)
public abstract class Phone {private Brand brand;public Phone(Brand brand) {this.brand = brand;}protected void call() {brand.call();}protected void open() {brand.open();}protected void close() {brand.close();}}public class FoldedPhone extends Phone {public FoldedPhone(Brand brand) {super(brand);}@Overrideprotected void call() {super.call();System.out.println("折叠屏手机");}@Overrideprotected void open() {super.open();System.out.println("折叠屏手机");}@Overrideprotected void close() {super.close();System.out.println("折叠屏手机");}}public class UpRightPhone extends Phone {public UpRightPhone(Brand brand) {super(brand);}@Overrideprotected void call() {super.call();System.out.println("滑盖手机");}@Overrideprotected void open() {super.open();System.out.println("滑盖手机");}@Overrideprotected void close() {super.close();System.out.println("滑盖手机");}}
客户端
public class Client {public static void main(String[] args) {Phone phone = new FoldedPhone(new Vivo());phone.open();phone.call();phone.close();System.out.println("------------------------");Phone phone1 = new UpRightPhone(new Vivo());phone1.open();phone1.call();phone1.close();}}
运行结果
Vivo 手机开机折叠屏手机Vivo 手机打电话折叠屏手机Vivo 手机关机折叠屏手机------------------------Vivo 手机开机滑盖手机Vivo 手机打电话滑盖手机Vivo 手机关机滑盖手机
此时如果需要新增一个手机品牌或者手机样式,都可以很方便的进行扩展。
4、桥接模式在JDBC的源码剖析
1.jdbc 的 Driver 接口,如果从桥接模式来看,Driver 就是一个接口,下面可以有 Mysql 的 Driver,Oracle 的 Driver,这些就可以当作实现接口类。
public class Driver extends NonRegisteringDriver implements java.sql.Driver {public Driver() throws SQLException { }static {try {//1.注册驱动//2. 调用DriverManager中的getConnection DriverManager.registerDriver(new Driver());} catch (SQLException var1) {throw new RuntimeException("Can't register driver!"); }}}..
Driver和Connection之间是通过DriverManager 类进行桥接的。
对 jdbc 源码分析的类图
5、桥接模式的注意事项和细节
实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而更好的结构化系统。对系统的高层部分,只需要知道抽象部分和 实现部分的接口就可以了,其他的部分由具体业务来完成。桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行和编程。桥接模式要求正确识别出系统中两个独立变化的维度(抽象和实现),因此其使用范围有一定的局限性,即需要有这样的应用场景。对于那些不希望使用继承或者因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为使用。6、常见的应用常景
1.JDBC驱动程序。
2.银行转账系统
转账分类:网上转账、柜台转账、ATM 转账转账账户类型:普通用户、银行卡用户、金卡用户......
3、消息管理
消息类型:即时消息、延时消息消息分类:手机短信、邮件消息、QQ消息......
三、装饰着模式(通俗易懂)
1、需求
1.咖啡种类(单品咖啡):Espresso(意大利咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)。
2.调料:Milk、Soy(豆浆)、Chocolate(巧克力)
3.要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便。
4.使用 OO 来计算不同种类咖啡的费用:客户可以点单品咖啡,也可以家咖啡和调料组合。
2、方案1 - 解决星巴克咖啡订单项目
此方案问题:
Drink 是一个抽象类,表示饮料des 就是对咖啡的描述,比如咖啡的名字cost() 方法就是计算费用 ,在Drink 中cost()是一个抽象方法Decaf就是单品咖啡,继承Drink,并实现 costEspress&&Milk就是单品咖啡+调料,这个组合会很多这样设计,会有很多类,当我们增加一个单品咖啡,或者一个新的调料,类的数量就会倍增,就会出现类爆炸
3、方案2 - 解决星巴克咖啡订单项目
前面分析到方案1,因为咖啡+调料组合会造成类的倍增,因此可以做改进,将调料内置到 Drink 类,这样就不会造成类数量过多。从而提高项目的维护性。
说明:milk、soy、chocolate 可以设计为Boolean,表示是否要添加相应的调料。
此方案问题分析:
方案2可以控制类的数量,不至于造成很多的类在增加或者删除调料种类时,代码的维护了很大考虑到用户可以添加多分调料时,可以将 hasMilk 返回一个对应的 int考虑到装饰者模式。
4、装饰者模式介绍
装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)。
原理
1.装饰者模式就像打包一个快递
主体:陶瓷、衣服(Component,被装饰者)包装:报纸填充、塑料泡沫、纸板、木板(Decorator)
ponent 主体:例如前面的 Drink。
3.ConcreteComponent:具体的主体,比如前面的单品咖啡。
4.Decorator:装饰者,比如前面的各种调料。
下图的 Component 与ConcreteComponent 之间,如果ConcreteComponent 类很多,还可以设计一个缓冲层,将共有的部分提取出来,抽象成一个类。
5、方案三 - 装饰者模式解决星巴克订单项目
案例分析
说明:
Drink类就是前面说的抽象类(主体,被装饰者)ShortBlack 就是单品咖啡Decorator 是一个装饰类Decorator 的cost()方法进行一个费用的叠加计算,递归的计算价格
实现功能:2分巧克力+一份牛奶的LongBlack
代码实现
首先是我们的Drink,也就是主体
public abstract class Drink {private String desc;private float price;public void setDesc(String desc) {this.desc = desc;}public void setPrice(float price) {this.price = price;}public String getDesc() {return desc;}public float getPrice() {return price;}public abstract float cost();}
接着是具体的被装饰者,注意我们设计了一个缓冲层 Coffee
public class Coffee extends Drink {@Overridepublic float cost() {return getPrice();}}public class Decaf extends Coffee {public Decaf() {setDesc(" 无因咖啡 ");setPrice(3.0f);}}public class ShortBlack extends Coffee {public ShortBlack() {setDesc(" 黑咖啡 ");setPrice(5.0f);}}
然后是装饰者,我们也是抽象出一个类,
public class Decorator extends Drink {private Drink obj;public Decorator(Drink obj) {super();this.obj = obj;}@Overridepublic String getDesc() {//输出被装饰者信息return super.getDesc() + " " + super.getPrice() + " && " +obj.getDesc();}@Overridepublic float cost() {//价格计算return obj.cost()+super.getPrice();}}public class Chocolate extends Decorator {public Chocolate(Drink obj) {super(obj);setDesc(" 巧克力 ");setPrice(3.0f);}}public class Milk extends Decorator {public Milk(Drink obj) {super(obj);setDesc(" 牛奶 ");setPrice(2.0f);}}
客户端调用:
public class Client {public static void main(String[] args) {//1、单点一份咖啡Drink drink = new ShortBlack();System.out.println("费用1="+drink.cost());System.out.println("描述="+drink.getDesc());//2、加入牛奶drink = new Milk(drink);System.out.println("加入牛奶 费用2="+drink.cost());System.out.println("加入牛奶 描述="+drink.getDesc());//2、加入巧克力drink = new Chocolate(drink);System.out.println("加入1牛奶和1巧克力 费用2="+drink.cost());System.out.println("加入1牛奶和1巧克力 描述="+drink.getDesc());//3、加入巧克力drink = new Chocolate(drink);System.out.println("加入1牛奶和2巧克力 费用2="+drink.cost());System.out.println("加入1牛奶和2巧克力 描述="+drink.getDesc());}}
如果还想增加咖啡的种类,或者调料的种类都可以很方便的添加和组合,不会影响现有的功能。
6、装饰着模式在 JDK 应用的源码分析
Java 的 IO 结构:
InputStream 是一个抽象类,即Component(主体);类似我们例子的Drink。FileInputStream 继承 InputStream,是一个具体的被装饰者,类似我们例子的Decaf、ShortBlack。FilterInputStream 就是一个装饰者,即Decorator,类似我们例子的Decorator。FilterInputStream 中有一个InputStream 对象,它就是被装饰的对象。DataInputStream 是FilterInputStream 的子类,也继承了被装饰的对象;它类似我们例子中的 Milk、Chocolate。
7、总结
桥接模式:抽象类(抽象接口)
装饰模式 抽象父类(抽象子类)
四、组合模式(部分整体模式)
1、需求
编写一个程序展示一个学校院系结构,要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。
2、传统思路解决学校院系展示
传统方法的问题:
将学院看成是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层的。实际上我们的要求是:在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系 ,因此这种方案,不能很好的实现管理的操作,比如对学院、系的添加、删除、遍历等。解决方案:把学校、学院、系都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实现管理操作。=>组合模式。
2、组合模式介绍
基本介绍
组合模式(Composite Pattern),又叫整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系。这种类型的设计模式属于结构模式。组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象。原理
说明:
Component:这是组合模式中对象什么接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component子部件,Component可以是抽象类或者接口。Leaf:在组合中表示叶子节点,叶子节点没有子节点。Composite:非叶子节点,用于存储子部件,在Component接口中实现子部件的相关操作。,比如增加、删除。
3、组合模式解决学校院系展示
Component抽象类或接口-OrganizationComponent
public abstract class OrganizationComponent {/** 名字 */private String name;/** 说明 */private String des;public OrganizationComponent(String name, String des) {this.name = name;this.des = des;}public void setName(String name) {this.name = name;}public void setDes(String des) {this.des = des;}public String getName() {return name;}public String getDes() {return des;}protected void add(OrganizationComponent organizationComponent){//默认实现throw new UnsupportedOperationException();}protected void remove(OrganizationComponent organizationComponent){//默认实现throw new UnsupportedOperationException();}//抽象方法:输出信息protected abstract void print();}
Composite(非叶子节点) - college,University
public class University extends OrganizationComponent {private List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();public University(String name, String des) {super(name, des);}@Overrideprotected void add(OrganizationComponent organizationComponent) {organizationComponents.add(organizationComponent);}@Overrideprotected void remove(OrganizationComponent organizationComponent) {organizationComponents.remove(organizationComponent);}@Overridepublic String getName() {return super.getName();}@Overridepublic String getDes() {return super.getDes();}@Overrideprotected void print() {System.out.println("-------------------"+ getName() +"-----------------");for (OrganizationComponent organizationComponent : organizationComponents) {organizationComponent.print();}}}public class College extends OrganizationComponent {private List<OrganizationComponent> organizationComponents = new ArrayList<OrganizationComponent>();public College(String name, String des) {super(name, des);}@Overrideprotected void add(OrganizationComponent organizationComponent) {organizationComponents.add(organizationComponent);}@Overrideprotected void remove(OrganizationComponent organizationComponent) {organizationComponents.remove(organizationComponent);}@Overridepublic String getName() {return super.getName();}@Overridepublic String getDes() {return super.getDes();}@Overrideprotected void print() {System.out.println("-------------------"+ getName() +"-----------------");for (OrganizationComponent organizationComponent : organizationComponents) {organizationComponent.print();}}}
Leaf(叶子节点)-Department
public class Department extends OrganizationComponent {//叶子节点-没有集合public Department(String name, String des) {super(name, des);}@Overridepublic String getName() {return super.getName();}@Overridepublic String getDes() {return super.getDes();}@Overrideprotected void print() {System.out.println(getName());}}
客户端调用-Client
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();}}
4、 组合模式在JDK源码分析-HashMap
Map、AbstractHashMap(抽象分两层设计)就是一个抽象的构建(类似我们的Component)HashMap是一个中间的构建(Composite),实现/继承了相关方法,put、putAllNode 是HashMap得到静态内部类,类似 Leaf 叶子节点,这里就没有put、putAll5、总结
简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者叶子节点的问题。具有较强的扩展性,当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动。方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或叶子从而创建出复杂的树形结构。需要遍历组织机构,或者处理的对象具有树形结构时,非常适合组合模式。要求较高的抽象性,如果节点和叶子有很多差异的话,比如很多方法和树形都不一样,不适合使用组合模式。五、外观模式(过程模式)
1、需求
组件一个家庭影院,DVD 播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的功能,其过程为:
开爆米花机放下屏幕开投影仪开音响开 DVD,选dvd去拿爆米花调暗灯光播放观影结束,关闭各种设备
2、传统方式解决家庭影院需求
问题分析:
在 ClientTest 的 main 方法终,创建各个子系统的对象,并直接去调用子系统(对象)相关方法,会造成调用过程混乱,没有清晰的过程。不利于ClientTest 维护对子系统的操作。解决思路:定义一个高层接口,给子系统中的一组接口提供一个一致的界面(比如,在高层接口提供四个方法 ready、play、pause、end),用来访问子系统中的一群接口。也就是说,通过定义一个一致的接口(界面类),用来屏蔽内部子系统的细节,使得调用端只需要跟这个接口发生调用,而无需关系这个子系统内部细节 =》外观模式。
2、外观模式介绍
基本介绍
外观模式(Facade),也叫过程模式。外观模式为子系统提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端跟这个接口发生调用,而无需关心这个子系统的内部细节。
外观模式就是解决多个复杂接口带来的使用困难,起到简化用户操作的作用。
外观模式原理
类图说明:
外观类(Facade):为调用端提供统一的调用接口,外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给合适的子系统对象。调用者(Client):外观接口的调用者。子系统集合:指模块或者子系统,处理Facade对象指派的任务,它是功能的实际提供者。
3、外观模式解决家庭影院需求
代码实现
各个子系统
public class 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 ");}}public class Popcorn {public void on() {System.out.println(" 爆米花机 on ");}public void off() {System.out.println(" 爆米花机 off ");}public void pop() {System.out.println(" 爆米花机 pop ");}}public class Projector {public void on() {System.out.println(" 投影仪 on ");}public void off() {System.out.println(" 投影仪 off ");}}public class Screen {public void down() {System.out.println(" 屏幕 down ");}public void up() {System.out.println(" 屏幕 up ");}}public class Stereo {public void on() {System.out.println(" 立体声 on ");}public void off() {System.out.println(" 立体声 off ");}}public class TheaterLight {public void dim() {System.out.println(" 影院灯光 调暗 ");}public void bright() {System.out.println(" 影院灯光 调亮 ");}}
外观类 -HomeTheaterFacade
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 = new TheaterLight();this.popcorn = new Popcorn();this.stereo = new Stereo();this.projector = new Projector();this.screen = new Screen();this.dvdPlayer = new DvdPlayer();}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();}}
客户端-调用外观类进行操作
public class Client {public static void main(String[] args) {HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade();System.out.println("-----------打开家庭影院----------");homeTheaterFacade.ready();System.out.println("\n-----------播放家庭影院----------");homeTheaterFacade.play();System.out.println("\n-----------关闭家庭影院----------");homeTheaterFacade.end();}}
4、外观模式在 MyBatis 框架影院的源码分析
MyBatis 中的 Configuration 去创建 MetaObject 对象( newMetaObject() )时,使用到外观模式。
类图如下:
以下是截图的部分源码:
public class Configuration {...protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();protected ObjectFactory objectFactory = new DefaultObjectFactory();protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();...public MetaObject newMetaObject(Object object) {return MetaObject.forObject(object, objectFactory, objectWrapperFactory, reflectorFactory);}...}public class MetaObject {...private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {this.originalObject = object;this.objectFactory = objectFactory;this.objectWrapperFactory = objectWrapperFactory;this.reflectorFactory = reflectorFactory;if (object instanceof ObjectWrapper) {this.objectWrapper = (ObjectWrapper) object;} else if (objectWrapperFactory.hasWrapperFor(object)) {this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);} else if (object instanceof Map) {this.objectWrapper = new MapWrapper(this, (Map) object);} else if (object instanceof Collection) {this.objectWrapper = new CollectionWrapper(this, (Collection) object);} else {this.objectWrapper = new BeanWrapper(this, object);}}...public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {if (object == null) {return SystemMetaObject.NULL_META_OBJECT;} else {return new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory);}}...}
5、总结
注意事项和细节:
外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性。外观模式对客户端与子系统的耦合关系-解耦,让子系统内部的模块更易维护和扩展。通过合理的使用外观模式,可以帮我们更好的划分访问层次。当系统进行分层设计时,可以考虑使用 Facade 模式。在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口,让新系统与 Facade 类交互,提高复用性。不能过多或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。要以让系统更有层次,利于维护为目的。
六、享元模式(蝇量模式、池原理)
1、需求:网站展示项目
小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但要求都有些不同:
有客户要求以新闻的形式发布。有客户要求以博客的形式发布。有客户要求以微信公众号的形式发布。
2、传统方案解决网站展示项目
直接复制粘贴一份,然后根据客户不同要求,进行定制修改给每个网站租用一个空间问题分析:
需要的网站结构相似度很高,而且都不是高访问的网站,如果分成多个虚拟空间来处理,相当于相同网站的实例对象很多,造成服务器的资源浪费。解决思路:整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源。对于代码来说,由于是一份实例,维护和扩展都更加容易。
3、享元模式介绍
基本介绍
享元模式(Flyweight Pattern)是一种软件设计模式。他使用共享物价,用来尽可能减少内存使用量以及分享资讯给尽可能多的相似物件;它适用于只是因重复而导致使用无法令人接受的大量内存的大量物件。通常物件中的部分状态是可以分享的。常见做法是把它们放在外部数据结构,当需要使用时再将它们传递给享元。
享元模式又叫蝇量模式,运用共享技术有效地支持大量细粒度的对象。常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要,则创建一个。享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需要总是创建对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率。享元模式经典的应用常见就是池技术了,Spring 常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式。
原理类图
说明:
FlyWeight 是抽象的享元角色,它是产品的抽象类,同时产品的抽象类,同时定义出对象的外部状态和内部状态的接口或实现。ConcreteFlyWeight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务。UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂。FlyWeightFactory 享元工厂类,用于构建一个池容器(集合),同时提供从池中获取对象方法。
4、内部状态和外部状态
比如围棋、五子棋、跳棋,它们有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子的颜色是定的,但是位置是变化的,所以棋子坐标就是它外部状态。
享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两个部分:内部状态和外部状态。
内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变。
外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。
举个例子:围棋理论上有361 个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题。
5、享元模式解决网站展示项目
使用到的
抽象的享元角色
public interface WebSite {void use(User user);}
具体的享元角色
public class ConcreteWebSite implements WebSite {//共享的部分,内部状态private String type = ""; //网站发布的形式(类型)public ConcreteWebSite(String type) {this.type = type;}@Overridepublic void use(User user) {System.out.println("网站的发布形式为:" + type + " 在使用中 .. 使用者是" + user.getName());}}
不可共享的角色
public class User {private String name;public User(String name) {this.name = name;}public String getName() {return name;}}
享元工厂类
// 网站工厂类,根据需要返回压一个网站public class WebSiteFactory {//集合, 充当池的作用private HashMap<String, WebSite> pool = new HashMap<>();//根据网站的类型,返回一个网站, 如果没有就创建一个网站,并放入到池中,并返回public WebSite getWebSiteCategory(String type) {if(!pool.containsKey(type)) {//就创建一个网站,并放入到池中pool.put(type, new ConcreteWebSite(type));}return pool.get(type);}//获取网站分类的总数 (池中有多少个网站类型)public int getWebSiteCount() {return pool.size();}}
客户端
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());}}
输出
网站的发布形式为:新闻 在使用中 .. 使用者是tom网站的发布形式为:博客 在使用中 .. 使用者是jack网站的发布形式为:博客 在使用中 .. 使用者是smith网站的发布形式为:博客 在使用中 .. 使用者是king网站的分类共=2
6、享元模式在 JDK——Integer 的应用源码分析
Integer类的 valueOf() 使用了享元模式,如果Integer.valueOf(x) x 在-128 --- 127 直接,就是使用享元模式返回,如果不在范围内,则仍然new。
public final class Integer extends Number implements Comparable<Integer> {...private static class IntegerCache {static final int low = -128;static final int high;static final Integer cache[];static {// high value may be configured by propertyint h = 127;String integerCacheHighPropValue =sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");if (integerCacheHighPropValue != null) {try {int i = parseInt(integerCacheHighPropValue);i = Math.max(i, 127);// Maximum array size is Integer.MAX_VALUEh = Math.min(i, Integer.MAX_VALUE - (-low) -1);} catch( NumberFormatException nfe) {// If the property cannot be parsed into an int, ignore it.}}high = h;cache = new Integer[(high - low) + 1];int j = low;for(int k = 0; k < cache.length; k++)cache[k] = new Integer(j++);// range [-128, 127] must be interned (JLS7 5.1.7)assert IntegerCache.high >= 127;}private IntegerCache() {}}...public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);}...}
下面是测试类
public class FlyWeight {public static void main(String[] args) {/*** 如果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 实例,类型IntegerInteger y = new Integer(127); // 得到y 实例,类型IntegerInteger z = Integer.valueOf(127);//..Integer w = new Integer(127);System.out.println(x.equals(y)); // 大小,trueSystem.out.println(x == y ); // falseSystem.out.println(x == z ); // trueSystem.out.println(w == x ); // falseSystem.out.println(w == y ); // falseInteger x1 = Integer.valueOf(200);Integer x2 = Integer.valueOf(200);System.out.println("x1==x2" + (x1 == x2)); // false}}
7、注意事项和细节
在享元模式这样理解,“享”就表示共享,“元”表示对象。
系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑使用享元模式。
用唯一标识码判断,如果在内存中有,则返回这个唯一标识的对象,用 HashMap/HashTable 存储。
享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率。
享元模式提供了系统复杂度。需要分离出内部状态和外部状态,而内部状态具有固化特性,不应该随着外部状态的改变而改变,这是我们使用享元模式需要注意的地方。
使用享元模式时,注意划分内部状态和外部状态,并且需要一个工厂类加以控制。
享元模式经典的应用常见是需要缓冲池的场景,比如 Spring常量池、数据库连接池。
七、代理模式(静态代理、动态代理、Cglib代理)
1、基本介绍
代理模式:为一个对象提供一个替身,以控制这个对象的访问。即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能,即扩展目标对象的功能。
被代理对象可以是远程对象、创建开销大的对象或者需要安全控制的对象。
代理模式有不同的形式,主要有三种:静态代理、动态代理(JDK代理、接口代理)、Cglib代理(可以在内存动态的创建对象,而不需要实现接口,它是属于动态代理的范畴)。
示意图
2、静态代理
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同的父类(以构造器方式注入代理类)。
应用实例
定义一个接口 ITeacherDao目标对象TeacherDao 实现ITeacherDao使用静态代理方式,就需要在代理对象TeacherDaoProxy 中也实现TeacherDao调用的时候通过调用代理对象的方法来调用目标对象特别提醒:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法。代码实现
定义一个接口-ITeacherDao
public interface ITeacherDao {void teach();}
创建目标对象 -TeacherDao
public class TeacherDao implements ITeacherDao {public void teach() {System.out.println(" 老师授课中 。。。。。");}}
创建代理对象 -TeacherDaoProxy
public class TeacherDaoProxy implements ITeacherDao {private TeacherDao target;public TeacherDaoProxy(TeacherDao target) {this.target = target;}public void teach() {System.out.println("开始代理 完成某些操作。。。。。 ");//方法target.teach();System.out.println("提交。。。。。");//方法}}
创建客户端 - client
public class Client {public static void main(String[] args) {//创建目标对象(被代理对象)TeacherDao teacherDao = new TeacherDao();//创建代理对象, 同时将被代理对象传递给代理对象TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);//通过代理对象,调用到被代理对象的方法//即:执行的是代理对象的方法,代理对象再去调用目标对象的方法teacherDaoProxy.teach();}}
输出
开始代理 完成某些操作。。。。。 老师授课中 。。。。。提交。。。。。
静态代理优缺点
优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展。
缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类。
一旦接口增加方法,目标对象与代理对象都要维护。
3、动态代理
基本介绍
代理对象不需要实现接口,但是目标对象要实现接口,否则不能用动态代理。
代理对象的生产,是利用 JDK 的API,动态的在内存中构建代理对象。
动态代理也叫做:JDK代理、接口代理。
JDK 中生成代理对象的API
1、代理类所在报: java.lang.reflect.Proxy。
2、JDK实现代理只需要使用 newProxyInstance 方法,但是该方法需要接收三个参数,完整的写法是:
@CallerSensitivepublic static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
方法有三个参数:
第一参数:类加载器第二参数:增强方法所在的类,这个类实现的接口,支持多个接口第三参数:实现这个接口InvocationHandler ,创建的代理对象,写增强的部分
/*** proxy - 调用该方法的代理实例* method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。* args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。**/public Object invoke(Object proxy, Method method, Object[] args)
应用实例
定义一个接口-ITeacherDao
public interface ITeacherDao {void teach();}
创建目标对象 -TeacherDao
public class TeacherDao implements ITeacherDao {public void teach() {System.out.println(" 老师授课中 。。。。。");}public void sayHello(String name) {System.out.println("hello " + name);}}
创建代理对象 -TeacherDaoProxy
public class TeacherDaoProxy {private Object target;public TeacherDaoProxy(TeacherDao target) {this.target = target;}//给目标对象 生成一个代理对象public Object getProxyInstance() {return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("JDK代理开始~~");//反射机制调用目标对象的方法Object returnVal = method.invoke(target, args);System.out.println("JDK代理提交");return returnVal;}});}}
创建客户端 - client
public class Client {public static void main(String[] args) {//创建目标对象(被代理对象)TeacherDao target = new TeacherDao();//给目标对象,创建代理对象, 可以转成 ITeacherDaoITeacherDao proxyInstance = (ITeacherDao) new TeacherDaoProxy(target).getProxyInstance();//通过代理对象,调用目标对象的方法proxyInstance.teach();proxyInstance.sayHello(" tom ");}}
输出
JDK代理开始~~老师授课中 。。。。。JDK代理提交JDK代理开始~~hello tom JDK代理提交
为什么需要目标类继承接口
因为 JDK 动态代理已经继承了 Proxy 类,所以只能通过接口来与被代理类简历联系(两个类建立起联系,一是继承的关系【jdk已经不能通过这个方式了,因为java仅支持单继承】,另一种就是实现同一个接口【JDK动态代理选这种】),所以必须要求被代理类实现一个接口,这样的话代理类与被代理类就能通过这个接口建立联系了。
4、Cglib代理
Cglib代理模式基本介绍
静态代理和JDK代理都要求目标对象实现一个接口,但有时候目标对象只是一个单独的对象,并没有实现任何接口,这个时候可以使用目标对象子类来实现代理 —— Cgilb代理。
Cglib代理也叫作子类代理,它是内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将Cglib代理归属到动态代理。
Cglib 是一个强大的高性能的代码生成包,它可以在运行期间扩展 Java 类与实现 java 接口。它被广泛用于许多 AOP 的框架,例如 Spring AOP,实现方法拦截。
在 AOP 编程中如何选择代理模式:
目标对象需要实现接口,使用 JDK 代理。目标对象不需要实现接口,使用 Cglib 代理。
Cglib 包的底层原理是通过使用字节码处理框架 ASM 来转换字节码并生成新的类。
Cglib代理模式实现步骤
1、引入 cglib 的依赖
<dependencies><dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency></dependencies>
2、在内存中动态构建子类,注意代理类不能为 final,否则报错(java.lang.IllegalArgumentException) 。
3、目标对象的方法如果为 final、static ,那么就不会被拦截。即不会执行目标对象额外的业务方法。
CGLib动态代理是通过字节码底层继承要代理类来实现,因此如果被代理类被final关键字所修饰,会失败。
应用实例
被代理对象 - TeacherDao
public class TeacherDao {public String teach() {System.out.println(" 老师授课中 。。。。。");return "hello";}}
代理类 - ProxyFactory
public class ProxyFactory implements MethodInterceptor {private Object target;//构造器传入一个被代理对象public ProxyFactory(Object target) {this.target = target;}//返回一个代理对象,是 target 对象的代理对象public Object getProxyInstance() {//1.创建一个工具类3Enhancer enhancer = new Enhancer();//2.设置父类enhancer.setSuperclass(target.getClass());//3.设置回调函数enhancer.setCallback(this);//4.创建子类对象,即代理对象return enhancer.create();}//重写 intercept 方法,会调用目标对象的方法@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("Cglib代理模式 ~~ 开始");Object returnVal = method.invoke(target, args);System.out.println("Cglib代理模式 ~~ 提交");return returnVal;}}
客户端
public class Client {public static void main(String[] args) {//创建目标对象TeacherDao target = new TeacherDao();//获取到代理对象,并且将目标对象传递给代理对象TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(target).getProxyInstance();//执行代理对象的方法,触发intecept 方法,从而实现 对目标对象的调用String res = proxyInstance.teach();System.out.println("res=" + res);}}
输出
Cglib代理模式 ~~ 开始老师授课中 。。。。。Cglib代理模式 ~~ 提交res=hello
5、总结
集中常见的代理模式介绍:
防火墙代理:内网通过代理穿透防火墙,实现对公网的访问。缓存代理:比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则ok,如果取不到资源,再到公网或者数据库取,然后缓存。远程代理:远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息。同步代理:主要使用在多线程编程中,完成多线程间同步工作。
静态代理:代理类 目标类都继承同一个接口 (目的为了实现类和代理类都有同一个方法,方便增强),目标类以构造函数的方式注入代理类,代理类调用方法。
动态代理: 目标类有接口,目标类通过接口构造器注入代理类工厂,调用Proxy.newProxyInstance,第三个参数注意InvocationHandler。
Cglib 代理:代理类工厂implements MethodInterceptor,目标类通过类构造器注入代理类工厂,代理类工厂重写 intercept 方法,返回对象。
使用注意:
如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。如果目标对象实现了接口,可以强制使用CGLIB实现AOP。如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换。
图解Java设计模式学习笔记——结构型模式(适配器模式 桥接模式 装饰者模式 组合模式 外观模式 享元模式 代理模式)