一、代理模式(结构型)(重要)
代理模式概述
Proxy模式又叫做代理模式,是构造型的设计模式之一,它可以为其他对象提供一种代理(Proxy)以控制对这个对象的访问。
所谓代理,是指具有与代理元(被代理的对象)具有相同的接口的类,客户端必须通过代理与被代理的目标类交互,而代理一般在交互的过程中(交互前后),进行某些特别的处理。
代理模式的结构
代理模式的角色与职责
- subject(抽象主题角色):真实主题与代理主题的共同接口或抽象类。
- RealSubject(真实主题角色):定义了代理角色所代表的真实对象。
- Proxy(代理主题角色):含有对真实主题角色的引用,代理角色通常在将客户端调用传递给真是主题对象之前或者之后执行某些操作,而不是单纯返回真实的对象。
举个例子说明一下代理模式
比如说买书,网上有很多专门卖书的网站,我们从这些商城买书,但是书不是这些商城印的,他们只负责卖,书是出版社印的,所以说到底,我们其实还是从出版社买书,网上书城只是出版社的代理,所以出版社是被代理对象,书城是代理对象。
所以,根据角色与职责划分,subject(抽象主题角色)就是卖书,卖书是书城与出版社的共同功能,RealSubject(真实主题角色)就是出版社,它的功能就是卖书,但不直接卖给用户,而是被书城代理,通过代理来卖,Proxy(代理主题角色)就是书城,根据概念发现,代理对象在代理的过程中不仅仅只有被代理对象的功能,他还会执行某些他自己的操作,这个意思是,比如,书城会推出优惠券与打折活动。在卖书的基础上,增加许多功能来吸引消费者。
接下来,我们用代码实现刚才的例子:
代理模式分为两种类型:(1)静态代理(2)动态代理
1.1、静态代理
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。
首先,创建subject,一个接口,是代理对象与被代理对象的共同接口
书城与出版社都有卖书的功能。
public interface Subject {
public void sailBook();
}
然后,创建RealSubject,被代理对象,也就是出版社。
public class RealSubject implements Subject {
@Override
public void sailBook() {
System.out.println("卖书");
}
}
然后,创建代理对象,也就是书城。
public class ProxySubject implements Subject{
//代理对象含有对真实主题角色的引用
private Subject subject;
public ProxySubject(Subject subject){
this.subject = subject;
}
@Override
public void sailBook() {
dazhe();
this.subject.sailBook();
give();
}
//代理角色通常在将客户端调用传递给真是主题对象之前或者之后执行某些操作,而不是单纯返回真实的对象。
public void dazhe(){
System.out.println("打折");
}
public void give(){
System.out.println("赠送代金券");
}
}
最后,创建客户端,也就是用户
首先如果,不使用代理直接从出版社买
public class MainClass {
public static void main(String[] args) {
Subject realSubject = new RealSubject();
realSubject.sailBook();
}
}
结果如下:仅仅是卖书
而如果我们通过代理也就是书城来买
public class MainClass {
public static void main(String[] args) {
Subject realSubject = new RealSubject();
Subject proxySubject = new ProxySubject(realSubject);
proxySubject.sailBook();
}
}
结果变成了这样,作为用户,我们享受了更多的优惠,所以我们当然更愿意通过代理来买
静态代理总结
- 可以做到在不修改目标对象的功能前提下,对目标功能扩展。
- 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护。
如何解决静态代理中的缺点呢?答案是可以使用动态代理方式
1.2、动态代理
动态代理有以下特点:
- 代理对象,不需要实现接口
代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
- 动态代理也叫做:JDK代理,接口代理
- 代理代理,Subject与RealSubject不变
而使用动态代理,首先要创建代理实例的调用处理程序,通过这个程序来执行代理对象特有的方法。
创建MyHandler实现InvocationHandler
(是代理实例的调用处理程序 实现的接口)这个接口,并覆盖invoke(Object proxy, Method method, Object[] args)
这个方法。
Object invoke(Object proxy, Method method, Object[] args)
//在代理实例上处理方法调用并返回结果。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyHandler implements InvocationHandler{
//这里也需要传入被代理对象
private Subject subject;
public MyHandler(Subject subject){
this.subject = subject;
}
//这个方法中就是代理对象要执行的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
dazhe();
//执行被代理对象中的方法
Object result = method.invoke(subject, args);
give();
return result;
}
public void dazhe() {
System.out.println("打折");
}
public void give() {
System.out.println("赠送代金券");
}
}
最后,写客户端,客户端中需要动态创建代理对象,要使用Proxy类中的newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
方法
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
//返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
- 参数:
loader - 定义代理类的类加载器
interfaces - 代理类要实现的接口列表
h - 指派方法调用的调用处理程序
- 返回:
一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口
- 抛出:
IllegalArgumentException - 如果违反传递到 getProxyClass 的参数上的任何限制
NullPointerException - 如果 interfaces 数组参数或其任何元素为 null,或如果调用处理程序 h 为 null
所以三个参数,第一个是RealSubject的类加载器,第二个是被代理类要实现的接口列表,第三个就是处理程序也就是MyHandler。
public class MainClass {
public static void main(String[] args) {
Subject realSubject = new RealSubject();
MyHandler myHandler = new MyHandler(realSubject);
//创建代理对象实例
Subject proxySubject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(), realSubject.getClass().getInterfaces(), myHandler);
proxySubject.sailBook();
}
}
结果是相同的
代理模式总结
代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理。
十二、外观模式(结构型)
外观模式概述
Facade模式也叫外观模式,是由GoF提出的23种设计模式中的一种。Facade模式为一组具有类似功能的类群,比如类库,子系统等等,提供一个一致的简单的界面。这个一致的简单的界面被称作facade。
外观模式的角色和职责
- Facade:为调用方定义简单的调用接口。
- Clients:调用者。通过Facade接口调用提供某功能的内部类群。
- Packages:功能提供者。指提供功能的类群(模块或子系统)。
使用场景
- 设计初期阶段,应该有意识的将不同层分离,层与层之间建立外观模式。
- 开发阶段,子系统越来越复杂,增加外观模式提供一个简单的调用接口。
- 维护一个大型遗留系统的时候,可能这个系统已经非常难以维护和扩展,但又包含非常重要的功能,为其开发一个外观类,以便新系统与其交互。
简单来说,使用了外观模式,用户就不必直接面对众多功能模块,降低了使用难度,简单举个例子
电脑开机进入系统,我们把他分为4步,首先打开电源,bois自检,系统引导,进入系统,4个功能是四个功能模块
打开电源
public class StartPower {
/*
* 打开电源
*/
public void startPower(){
System.out.println("电脑通电");
}
}
bois自检
public class BoisSelfTest {
/*
* bios自检
*/
public void boisSelfTest(){
System.out.println("bios自检");
}
}
系统引导
public class SystemGuide {
/*
* 系统引导
*/
public void systemGuide(){
System.out.println("系统引导");
}
}
进入系统
public class EnterSystem {
/*
* 进入系统
*/
public void enterSystem(){
System.out.println("进入系统");
}
}
如果,我们没有使用外观模式,就需要用户自己挨个使用这些功能
public class MainClass {
public static void main(String[] args) {
StartPower startPower = new StartPower();
startPower.startPower();
BoisSelfTest boisSelfTest = new BoisSelfTest();
boisSelfTest.boisSelfTest();
SystemGuide systemGuide = new SystemGuide();
systemGuide.systemGuide();
EnterSystem enterSystem = new EnterSystem();
enterSystem.enterSystem();
}
}
运行结果为:
这样,电脑顺利启动,可是同样可以看出来,用户使用非常繁琐,不仅需要用户自己挨个使用所有用到的功能,同时还需要用户知道电脑启动的顺序,按顺序使用功能,不然就会导致问题,这样显然是不可取的
所以,用到外观模式,创建一个Facade,专门用于使用功能模块
public class Facade {
private StartPower startPower = null;
private BoisSelfTest boisSelfTest = null;
private SystemGuide systemGuide = null;
private EnterSystem enterSystem = null;
public void startComputer(){
startPower = new StartPower();
boisSelfTest = new BoisSelfTest();
systemGuide = new SystemGuide();
enterSystem = new EnterSystem();
startPower.startPower();
boisSelfTest.boisSelfTest();
systemGuide.systemGuide();
enterSystem.enterSystem();
}
}
这时客户端就是这样
public class MainClass {
public static void main(String[] args) {
Facade computer = new Facade();
computer.startComputer();
}
}
可以看到,用户这时只需要调用Facade中的方法即可,无需知道电脑有什么具体的功能模块,无需知道功能模块执行的顺序是什么,只是调用一下就好了。
简化了使用,同时也增加了代码的复用与可维护性。
优点
- 松散耦合:使得客户端和子系统之间解耦,让子系统内部的模块功能更容易扩展和维护;
- 简单易用:客户端根本不需要知道子系统内部的实现,或者根本不需要知道子系统内部的构成,它只需要跟Facade类交互即可。
- 更好的划分访问层次:有些方法是对系统外的,有些方法是系统内部相互交互的使用的。子系统把那些暴露给外部的功能集中到门面中,这样就可以实现客户端的使用,很好的隐藏了子系统内部的细节。
十三、组合模式(结构型)
组合模式概述
Composite模式也叫组合模式,是构造型的设计模式之一。通过递归手段来构造树形的对象结构,并可以通过一个对象来访问整个对象树。
组合(Composite)模式的其它翻译名称也很多,比如合成模式、树模式等等。在《设计模式》一书中给出的定义是:将对象以树形结构组织起来,以达成“部分-整体”的层次结构,使得客户端对单个对象和组合对象的使用具有一致性。
从定义中可以得到使用组合模式的环境为:在设计中想表示对象的“部分-整体”层次结构;希望用户忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象。
组合模式的角色和职责
1、Component (树形结构的节点抽象)
- 为所有的对象定义统一的接口(公共属性,行为等的定义)
- 提供管理子节点对象的接口方法
- [可选]提供管理父节点对象的接口方法
2、Leaf (树形结构的叶节点)
- Component的实现子类
3、Composite(树形结构的枝节点)
- Component的实现子类
安全性与透明性
组合模式中必须提供对子对象的管理方法,不然无法完成对子对象的添加删除等等操作,也就失去了灵活性和扩展性。但是管理方法是在Component中就声明还是在Composite中声明呢?
一种方式是在Component里面声明所有的用来管理子类对象的方法,以达到Component接口的最大化(如下图所示)。目的就是为了使客户看来在接口层次上树叶和分支没有区别——透明性。但树叶是不存在子类的,因此Component声明的一些方法对于树叶来说是不适用的。这样也就带来了一些安全性问题。
另一种方式就是只在Composite里面声明所有的用来管理子类对象的方法(如下图所示)。这样就避免了上一种方式的安全性问题,但是由于叶子和分支有不同的接口,所以又失去了透明性。
《设计模式》一书认为:在这一模式中,相对于安全性,我们比较强调透明性。对于第一种方式中叶子节点内不需要的方法可以使用空处理或者异常报告的方式来解决。
我们举个例子,比如说文件夹与文件,层次结构就符合树形结构
首先我们新建一个Component接口(树形结构的节点抽象)
/*
* 文件节点抽象(是文件和目录的父类)
*/
public interface IFile {
//显示文件或者文件夹的名称
public void display();
//添加
public boolean add(IFile file);
//移除
public boolean remove(IFile file);
//获得子节点
public List<IFile> getChild();
}
该例子符合透明性,如果是安全性,将IFile中的方法去除,不在IFile声明,直接在子类中声明即可。
接下来,创建一个Leaf(叶子结点),因为文件是不可再分的,所以File是叶子结点。
/*
* 文件(leaf 叶子结点)
*/
public class File implements IFile {
private String name;
public File(String name) {
this.name = name;
}
public void display() {
System.out.println(name);
}
public List<IFile> getChild() {
return null;
}
public boolean add(IFile file) {
return false;
}
public boolean remove(IFile file) {
return false;
}
}
然后继续创建Composite(文件夹),因为文件夹下面还可能有文件与文件夹,所以是composite。
public class Folder implements IFile{
private String name;
private List<IFile> children;
public Folder(String name) {
this.name = name;
children = new ArrayList<IFile>();
}
public void display() {
System.out.println(name);
}
public List<IFile> getChild() {
return children;
}
public boolean add(IFile file) {
return children.add(file);
}
public boolean remove(IFile file) {
return children.remove(file);
}
}
然后是客户端,用递归的形式把这个具有树形结构的对象遍历出来。
public class MainClass {
public static void main(String[] args) {
//C盘
Folder rootFolder = new Folder("C:");
//C盘下的目录一
Folder folder1 = new Folder("目录一");
//C盘下的文件一
File file1 = new File("文件一.txt");
rootFolder.add(folder1);
rootFolder.add(file1);
//目录一下的目录二
Folder folder2 = new Folder("目录二");
//目录一下的文件二
File file2 = new File("文件二.txt");
folder1.add(folder2);
folder1.add(file2);
//目录二下的目录三
Folder folder3 = new Folder("目录三");
//目录二下的文件三
File file3 = new File("文件三.txt");
folder2.add(folder3);
folder2.add(file3);
displayTree(rootFolder,0);
}
public static void displayTree(IFile rootFolder, int deep) {
for(int i = 0; i < deep; i++) {
System.out.print("--");
}
//显示自身的名称
rootFolder.display();
//获得子树
List<IFile> children = rootFolder.getChild();
//遍历子树
for(IFile file : children) {
if(file instanceof File) {
for(int i = 0; i <= deep; i++) {
System.out.print("--");
}
file.display();
}else {
displayTree(file,deep + 1);
}
}
}
}
这样子,就把,这棵树遍历了出来。
优缺点
优点:
- 使客户端调用简单,客户端可以一致的使用组合结构或其中单个对象,用户就不必关心自己处理的是单个对象还是整个组合结构,这就简化了客户端代码。
- 更容易在组合体内加入对象部件。客户端不必因为加入了新的对象部件而更改代码。这一点符合开闭原则的要求,对系统的二次开发和功能扩展很有利!
缺点:
- 组合模式不容易限制组合中的构件。
总结
组合模式是一个应用非常广泛的设计模式,它本身比较简单但是很有内涵,掌握了它对你的开发设计有很大的帮助。
十四、桥接模式(构造型)
桥接模式概述
Bridge 模式又叫做桥接模式,是构造型的设计模式之一。Bridge模式基于类的最小设计原则,通过使用封装,聚合以及继承等行为来让不同的类承担不同的责任。它的主要特点是把抽象(abstraction)与行为实现(implementation)分离开来,从而可以保持各部分的独立性以及应对它们的功能扩展。
举个例子,我们都知道,汽车有不同的发动机,有油的,有电的,还有用天然气的
我们先举两个不采用桥接模式的例子,通过对比,来看出桥接模式的优势。
第一个:
先新建一个接口Car,所有车都实现这个接口,这个接口中有一个公共的方法installEngine()安装发动机
/*
* 汽车
*/
public interface Car {
//安装引擎
public void installEngine();
}
然后,不同的车,比如公交,suv,轿车,都有不同的发动机,所以先新建不同的车,实现Car接口,因为不同种类的车下面还有不同的发动机,所以将具体的车建为抽象类,将其中实现Car的方法也抽象化。
公交
/*
* 公交车
*/
public abstract class Bus implements Car{
@Override
public abstract void installEngine();
}
SUV
/*
* SUV
*/
public abstract class Suv implements Car{
@Override
public abstract void installEngine();
}
不同的车有不同的发动机,每种车的不同发动机都是一个子类。
public class OilBus extends Bus{
@Override
public void installEngine() {
System.out.println("给公交安装燃油发动机");
}
}
public class ElectricityBus extends Bus{
@Override
public void installEngine() {
System.out.println("给公交安装电动发动机");
}
}
public class OilSuv extends Suv{
@Override
public void installEngine() {
System.out.println("给SUV安装燃油发动机");
}
}
public class ElectricitySuv extends Suv{
@Override
public void installEngine() {
System.out.println("给SUV安装电动发动机");
}
}
运行客户端
public class MainClass {
public static void main(String[] args) {
Car car = new OilBus();
car.installEngine();
}
}
可以看到这样的结果
但是这样有个很大的弊端
可以看到,一种车每增加一种发动机就要增加一个子类,到了后期,子类会越来越多,越来越庞大。这种方法不推荐使用。
第二个例子:
把所有的发动机都定义到Car接口中,每种车实现一次就好了
Car
/*
* 汽车
*/
public interface Car {
//安装燃油引擎
public void installOilEngine();
//安装电动引擎
public void installElectricityEngine();
}
Bus
/*
* 公交车
*/
public class Bus implements Car{
@Override
public void installOilEngine() {
System.out.println("给公交安装燃油发动机");
}
@Override
public void installElectricityEngine() {
System.out.println("给公交安装电动发动机");
}
}
SUV
/*
* SUV
*/
public class Suv implements Car{
@Override
public void installOilEngine() {
System.out.println("给SUV安装燃油发动机");
}
@Override
public void installElectricityEngine() {
System.out.println("给SUV安装电动发动机");
}
}
客户端
public class MainClass {
public static void main(String[] args) {
Car bus = new Bus();
Car suv = new Suv();
bus.installOilEngine();
bus.installElectricityEngine();
suv.installOilEngine();
suv.installElectricityEngine();
}
}
运行结果
种方式就没有那么多子类了,每种车只有一个实现类,但是这种方法不符合我们的开放封闭原则,每增加一种发动机,就要修改Car接口,并修改所有实现类。这种方法肯定也是不推荐的。
接下来,就要说到桥接模式了。
桥接模式的概念:Bridge模式基于类的最小设计原则,通过使用封装,聚合以及继承等行为来让不同的类承担不同的责任。它的主要特点是把抽象(abstraction)与行为实现(implementation)分离开来,从而可以保持各部分的独立性以及应对它们的功能扩展。
把抽象与行为实现分开,保证各部分的独立性,所以我们可以把车和发动机分离开来,保证他们各自的独立性。
增加一种车,对发动机没有影响,增加发动机吧对车也没有影响,Car接口中有对Engine的引用,也就是Car体系与Engine体系之间的桥梁。
桥接模式的角色和职责
-
Client:Bridge模式的使用者
-
Abstraction(Car)
(1)抽象类接口(接口或抽象类)
(2)维护对行为实现(Implementor)的引用
-
Refined Abstraction(Bus、Suv):Abstraction子类
-
Implementor(Engine): 行为实现类接口
(Abstraction接口定义了基于Implementor接口的更高层次的操作) -
ConcreteImplementor(OilEngine、ElectricityEngine):Implementor子类
接下来,用代码实现:
首先,新建发动机体系
/*
* 发动机
*/
public interface Engine {
public void installEngine();
}
/*
* 燃油发动机
*/
public class OilEngine implements Engine{
@Override
public void installEngine() {
System.out.println("安装燃油发动机");
}
}
/*
* 电动发动机
*/
public class ElectricityEngine implements Engine{
@Override
public void installEngine() {
System.out.println("安装电动发动机");
}
}
新建Car体系
/*
* 汽车
*/
public abstract class Car {
private Engine engine;
public Car(Engine engine){
this.engine = engine;
}
//安装发动机
public abstract void installEngine();
public Engine getEngine() {
return engine;
}
public void setEngine(Engine engine) {
this.engine = engine;
}
}
/*
* 公交车
*/
public class Bus extends Car{
public Bus(Engine engine) {
super(engine);
}
@Override
public void installEngine() {
System.out.print("公交:");
this.getEngine().installEngine();
}
}
/*
* SUV
*/
public class Suv extends Car{
public Suv(Engine engine) {
super(engine);
}
@Override
public void installEngine() {
System.out.print("SUV:");
this.getEngine().installEngine();
}
}
最后,新建客户端调用
public class MainClass {
public static void main(String[] args) {
Engine oilEngine = new OilEngine();
Engine electricityEngine = new ElectricityEngine();
Car oilBus = new Bus(oilEngine);
Car electricityBus = new Bus(electricityEngine);
Car oilSuv = new Suv(oilEngine);
Car electricitySuv = new Suv(electricityEngine);
oilBus.installEngine();
electricityBus.installEngine();
oilSuv.installEngine();
electricitySuv.installEngine();
}
}
结果如下
可以看到,这样子的话,新建这的种类,丝毫不影响发动机,新建一种发动机,也不影响车,这就是一个简单的桥接模式的例子
桥接模式总结
桥接模式的优点
(1)实现了抽象和实现部分的分离
桥接模式分离了抽象部分和实现部分,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,分别定义接口,这有助于系统进行分层设计,从而产生更好的结构化系统。对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了。
(2)更好的可扩展性
由于桥接模式把抽象部分和实现部分分离了,从而分别定义接口,这就使得抽象部分和实现部分可以分别独立扩展,而不会相互影响,大大的提供了系统的可扩展性。
(3)可动态的切换实现
由于桥接模式实现了抽象和实现的分离,所以在实现桥接模式时,就可以实现动态的选择和使用具体的实现。
(4)实现细节对客户端透明,可以对用户隐藏实现细节。
桥接模式的缺点
(1)桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程。
(2)桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性。
桥接模式的使用场景
(1)如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
(2)抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
(3)一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
(4)虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
(5)对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
十五、适配器模式(重要)
适配器模式概述
Adapter模式也叫适配器模式,是构造型模式之一,通过Adapter模式可以改变已有类(或外部类)的接口形式。
举个例子:我们使用电脑,家里的电源是220V的,而我们的电脑是18V的,这时如果我们直接把电源连接电脑,一定会导致电脑被烧坏,因为电源电压太高了,这时我们就需要一个电源适配器,连接在电源与电脑之间,通过适配器进行一个降压,来保证电脑的正常工作。
增加适配器
用代码实现:
首先如果不使用适配器的话
新建一个220V电源
//220V电源
public class PowerSupply {
public void powerSupply220V(){
System.out.println("使用220V电源");
}
}
新建一个笔记本电脑,使用电源
//笔记本电脑使用220V电源
public class Computer {
public static void main(String[] args) {
PowerSupply powerSupply = new PowerSupply();
powerSupply.powerSupply220V();
}
}
结果如下:
这样笔记本电脑就直接使用了220V电压,但是这样的话,笔记本电脑会直接烧毁,无法使用,因为电压太高了,所以我们需要在中间接一个适配器,以达到降压的目的
适配器继承220V电压
//适配器
public class Adapter extends PowerSupply{
public void powerSupply18V(){
System.out.println("使用适配器");
this.powerSupply220V();
System.out.println("电压降为18V");
}
}
笔记本电脑通过适配器调用电源
//笔记本电脑通过适配器使用220V电源
public class Computer {
public static void main(String[] args) {
Adapter adapter = new Adapter();
adapter.powerSupply18V();
}
}
结果如下:
可以看到,通过这种形式,我们使用的还是之前的那个电源,但是通过适配器,电压就降到了18V,电脑就可以正常使用了。
但是这只是适配器模式的其中一种形式。
下面更详细的说明一下适配器模式
适配器模式使用场景
在大规模的系统开发过程中,我们常常碰到诸如以下这些情况:
我们需要实现某些功能,这些功能已有还不太成熟的一个或多个外部组件,如果我们自己重新开发这些功能会花费大量时间;所以很多情况下会选择先暂时使用外部组件,以后再考虑随时替换。但这样一来,会带来一个问题,随着对外部组件库的替换,可能需要对引用该外部组件的源代码进行大面积的修改,因此也极可能引入新的问题等等。如何最大限度的降低修改面呢?
Adapter模式就是针对这种类似需求而提出来的。Adapter模式通过定义一个新的接口(对要实现的功能加以抽象),和一个实现该接口的Adapter(适配器)类来透明地调用外部组件。这样替换外部组件时,最多只要修改几个Adapter类就可以了,其他源代码都不会受到影响。
简单来说:
1、 系统需要使用现有的类,而这些类的接口不符合系统的需要。
2、 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
3、 需要一个统一的输出接口,而输入端的类型不可预知。
适配器模式的结构
- 通过继承实现Adapter
这种形式,就是我们刚才举的例子
Client:就是笔记本电脑(Computer)
Target:就是笔记本电脑中调用的方法(adapter.powerSupply18V())
Adaptee:就是220V电压(PowerSupply)
Adapter:就是适配器(Adapter)
- 通过委让实现Adapter
第二种模式,只需修改适配器与电脑即可
适配器不再继承电源,而是当成一个成员变量
//适配器
public class Adapter2{
private PowerSupply powerSupply;
public Adapter(PowerSupply powerSupply){
this.powerSupply = powerSupply;
}
public void powerSupply18V(){
System.out.println("使用适配器");
this.powerSupply.powerSupply220V();
System.out.println("电压降为18V");
}
}
电脑
//笔记本电脑通过适配器使用220V电源
public class Computer {
public static void main(String[] args) {
Adapter2 adapter = new Adapter2(new PowerSupply());
adapter.powerSupply18V();
}
}
一般,使用第二种委让形式更多一些,因为这种方式不必继承,使用成员变量更加的灵活。
十六、解释器模式(行为型)
解释器模式
Interpreter模式也叫解释器模式,是行为模式之一,它是一种特殊的设计模式,它建立一个解释器,对于特定的计算机程序设计语言,用来解释预先定义的文法。简单地说,Interpreter模式是一种简单的语法解释器构架。
解释器模式应用场景
当有一个语言需要解释执行, 并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。而当存在以下情况时该模式效果最好:
1、该文法简单,对于复杂的文法, 文法的类层次变得庞大而无法管理。此时语法分析程序生成器这样的工具是更好的选择。它们无需构建抽象语法树即可解释表达式, 这样可以节省空间而且还可能节省时间。
2、效率不是一个关键问题,最高效的解释器通常不是通过直接解释语法分析树实现的, 而是首先将它们转换成另一种形式。例如,正则表达式通常被转换成状态机。但即使在这种情况下, 转换器仍可用解释器模式实现, 该模式仍是有用的。
解释器模式结构图
解释器模式的角色和职责
1、Context:解释器上下文环境类。用来存储解释器的上下文环境,比如需要解释的文法等,简单来说,它的任务一般是用来存放文法中各个终结符所对应的具体值,比如R=R1+R2,给R1赋值100,给R2赋值200,这些信息需要存放到环境中。
2、AbstractExpression:解释器抽象类。抽象表达式,声明一个所有的具体表达式都需要实现的抽象接口;这个接口主要是一个interpret()方法,称做解释操作。
3、Terminal Expression:终结符表达式,解释器具体实现类,实现了抽象表达式所要求的接口;文法中的每一个终结符都有一个具体终结表达式与之相对应。比如公式R=R1+R2,R1和R2就是终结符,对应的解析R1和R2的解释器就是终结符表达式。
4、Nonterminal Expression:非终结符表达式,解释器具体实现类,文法中的每一条规则都需要一个具体的非终结符表达式,非终结符表达式一般是文法中的运算符或者其他关键字,比如公式R=R1+R2中,“+"就是非终结符,解析“+”的解释器就是一个非终结符表达式。
5、Client:客户端,指的是使用解释器的客户端,通常在这里将按照语言的语法做的表达式转换成使用解释器对象描述的抽象语法树,然后调用解释操作。
举一个计算器的例子
首先,新建一个Context,通过一个map来存储上下文信息
/*
* 解释器上下文环境类
*/
public class Context {
private Map<Character, Double> variable;
public Map<Character, Double> getVariable() {
if (this.variable == null)
{
this.variable = new HashMap<Character, Double>();
}
return this.variable;
}
//返回结果
public void setVariable(Map<Character, Double> variable) {
this.variable = variable;
}
}
再新建AbstractExpression
/*
* 抽象表达式
*/
public abstract class Expression {
public abstract double Interpret(Context context);
}
新建Terminal Expression
/*
* 变量,终结符表达式
*/
public class VariableExpression extends Expression {
private char key;
public VariableExpression(char key)
{
this.key = key;
}
@Override
public double Interpret(Context context) {
return context.getVariable().get(key);
}
}
再新建Nonterminal Expression,因为这其中包含许多不同的实现功能,比如加减乘除,所以我们先新建一个抽象类,再新建不同的实现类
抽象运算符号解析器
/*
* 操作符,非终结符表达式
*/
public abstract class OperatorExpression extends Expression {
protected Expression left;
protected Expression right;
public OperatorExpression(Expression left, Expression right)
{
this.left = left;
this.right = right;
}
}
具体的解析器
/*
* 加法解析器
*/
public class AddExpression extends OperatorExpression {
public AddExpression(Expression left, Expression right) {
super(left, right);
}
@Override
public double Interpret(Context context) {
return this.left.Interpret(context) + this.right.Interpret(context);
}
}
/*
* 减法解析器
*/
public class SubExpression extends OperatorExpression {
public SubExpression(Expression left, Expression right) {
super(left, right);
}
@Override
public double Interpret(Context context) {
return this.left.Interpret(context) - this.right.Interpret(context);
}
}
/*
* 乘法解析器
*/
public class MulExpression extends OperatorExpression {
public MulExpression(Expression left, Expression right) {
super(left, right);
}
@Override
public double Interpret(Context context) {
return this.left.Interpret(context) * this.right.Interpret(context);
}
}
/*
* 除法解析器
*/
public class DivExpression extends OperatorExpression {
public DivExpression(Expression left, Expression right) {
super(left, right);
}
@Override
public double Interpret(Context context) {
return this.left.Interpret(context) / this.right.Interpret(context);
}
}
接下来,新建一个解析器封装类
解析器封装类,这个类是根据迪米特法则进行封装,他并不是解释器模式的组成部分,目的是让Client只与功能模块打交道,不涉及到具体的实现,相当于Facade(外观模式)
public class Calculator {
private String expression;
private Context context;
public Calculator(String expression)
{
this.expression = expression;
this.context = new Context();
}
public double Calculate()
{
char[] vars = this.expression.toCharArray();
for(char c : vars){
if (c == '+' || c == '-' || c == '*' || c == '/')
{
continue;
}
if (!this.context.getVariable().containsKey(c))
{
System.out.println("请输入一个数字:");
System.out.print(c+"=");
Scanner sn = new Scanner(System.in);
String readLine = sn.next();
this.context.getVariable().put(c, Double.parseDouble(readLine));
}
}
Expression left = new VariableExpression(vars[0]);
Expression right = null;
Stack<Expression> stack = new Stack<Expression>();
stack.push(left);
for (int i = 1; i < vars.length; i += 2)
{
left = stack.pop();
right = new VariableExpression(vars[i + 1]);
switch (vars[i])
{
case '+':
stack.push(new AddExpression(left, right));
break;
case '-':
stack.push(new SubExpression(left, right));
break;
case '*':
stack.push(new MulExpression(left, right));
break;
case '/':
stack.push(new DivExpression(left, right));
break;
}
}
double value = stack.pop().Interpret(this.context);
stack.clear();
return value;
}
}
最后,是客户端,解释器的使用者
//Client客户端
public class Client {
public static void main(String[] args) {
Calculator c = new Calculator("a*b/c");
System.out.println("结果等于" + c.Calculate());
}
}
运行结果:
优点: 1、可扩展性比较好,灵活。 2、增加了新的解释表达式的方式。 3、易于实现简单文法。
缺点: 1、可利用场景比较少。 2、对于复杂的文法比较难维护。 3、解释器模式会引起类膨胀。 4、解释器模式采用递归调用方法。
十七、中介者模式
中介者模式概述
Mediator模式也叫中介者模式,是由GoF提出的23种软件设计模式的一种。Mediator模式是行为模式之一,在Mediator模式中,类之间的交互行为被统一放在Mediator的对象中,对象通过Mediator对象同其他对象交互,Mediator对象起着控制器的作用。
中介者模式其实就好比租房中介和相亲网站一样,房东将信息发布到租房中介,而租客可以中介挑选自己理想的房子,或者单身男女各自将自己的信息发布到相亲网站,同时也可以挑选符合自己条件的另一半。
拿相亲网站举个例子,首先不用中介者模式
相亲,肯定是人相亲,所以新建一个Person类,并提供一个getCompanion(Person person)
方法
public abstract class Person {
private String name;
private int condition;
public Person(String name, int condition){
this.name = name;
this.condition = condition;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getCondition() {
return condition;
}
public void setCondition(int condition) {
this.condition = condition;
}
//得到是否符合信息
public abstract void getCompanion(Person person);
}
人肯定是分为男人和女人,所以在新建Man与Woman类
public class Man extends Person {
public Man(String name, int condition) {
super(name, condition);
}
@Override
public void getCompanion(Person person) {
if(person instanceof Man){
System.out.println("我不是gay");
}else{
if(person.getCondition() == this.getCondition()){
System.out.println(this.getName()+"先生与"+person.getName()+"女士"+"非常般配");
}else{
System.out.println(this.getName()+"先生与"+person.getName()+"女士"+"不合适");
}
}
}
}
public class Woman extends Person{
public Woman(String name, int condition) {
super(name, condition);
}
@Override
public void getCompanion(Person person) {
if(person instanceof Woman){
System.out.println("我不是gay");
}else{
if(person.getCondition() == this.getCondition()){
System.out.println(this.getName()+"女士与"+person.getName()+"先生"+"非常般配");
}else{
System.out.println(this.getName()+"女士与"+person.getName()+"先生"+"不合适");
}
}
}
}
在写一个客户端
public class MainCLass {
public static void main(String[] args) {
Person xiaoming = new Man("小明",1);
Person xiaoqiang = new Man("小强",2);
Person xiaohong = new Woman("小红",1);
xiaoming.getCompanion(xiaohong);
xiaoqiang.getCompanion(xiaohong);
xiaoming.getCompanion(xiaoqiang);
}
}
运行结果:
可以看到,这种形式判断对方是否合适都交给了男女双方自己来解决,自己去寻找是否符合条件的,像大海捞针一样,不管是男,女人,甚至不是人都要挨个去比较。
同时这样Man和Woman之间存在了一个交互行为,大大提高了代码的耦合性,如果这两个类中都有自己特定的方法需要对方调用时,只要一方修改,另一方就需要跟着修改。两个类紧紧联系到了一起,这样的设计是很不好的。
下面,正式进入中介者模式
中介者模式结构图
中介者模式的角色和职责
1、Mediator:中介者类的抽象父类
抽象中介者角色定义统一的接口,用于各角色(男和女)之间的通信。
2、ConcreteMediator:具体中介者角色
具体中介者角色,通过协调各角色(男和女)实现协作行为,因此它必须依赖于各个角色。
3、Colleague:关联类的抽象父类
每一个角色都知道中介者角色,而且与其它的角色通信的时候,一定要通过中介者角色来协作。
每个类(Person)的行为分为二种(男和女):一种是男女本身的行为,这种行为叫做自发行为(Self-Method);第二种是必须依赖中介者才能完成的行为,叫做依赖行为(Dep-Method)。
4、concreteColleague:具体的关联类(Man和Woman)。
下面,用代码实现中介者模式
首先新建一个Mediator
public abstract class Mediator {
private Man man;
private Woman woman;
public Man getMan() {
return man;
}
public void setMan(Man man) {
this.man = man;
}
public Woman getWoman() {
return woman;
}
public void setWoman(Woman woman) {
this.woman = woman;
}
//比较条件的方法
public abstract void getCompanion(Person person);
}
在新建一个ConcreteMediator:具体中介者角色
public class BlindDateMediator extends Mediator{
@Override
public void getCompanion(Person person) {
if(person instanceof Man) {
this.setMan((Man)person);
} else {
this.setWoman((Woman)person);
}
//如果是同性
if(this.getMan() == null || this.getWoman() == null) {
System.out.println("我不是gay");
}else {
//条件合适
if(this.getMan().getCondition() == this.getWoman().getCondition()) {
System.out.println(this.getMan().getName() + "先生与" + this.getWoman().getName() + "女士很般配");
//条件不合适
}else {
System.out.println(this.getMan().getName() + "先生" + this.getWoman().getName() + "女士不合适");
}
}
//比较之后,将条件置空,不然会影响下一次比较
this.setMan(null);
this.setWoman(null);
}
}
接下来,新建Person,Man与Woman
public abstract class Person {
private String name;
private int condition;
private Mediator mediator;
public Person(String name, int condition, Mediator mediator){
this.name = name;
this.condition = condition;
this.mediator = mediator;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getCondition() {
return condition;
}
public void setCondition(int condition) {
this.condition = condition;
}
public Mediator getMediator() {
return mediator;
}
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
//得到是否符合信息
public abstract void getCompanion(Person person);
}
public class Man extends Person {
public Man(String name, int condition, Mediator mediator) {
super(name, condition, mediator);
}
@Override
public void getCompanion(Person person) {
this.getMediator().setMan(this);
this.getMediator().getCompanion(person);
}
}
public class Woman extends Person{
public Woman(String name, int condition, Mediator mediator) {
super(name, condition, mediator);
}
@Override
public void getCompanion(Person person) {
this.getMediator().setWoman(this);
this.getMediator().getCompanion(person);
}
}
最后是客户端
public class MainCLass {
public static void main(String[] args) {
Mediator mediator = new BlindDateMediator();
Person xiaoming = new Man("小明",1,mediator);
Person lisi = new Man("李四",2,mediator);
Person xiaohong = new Woman("小红",1,mediator);
xiaoming.getCompanion(xiaohong);
lisi.getCompanion(xiaohong);
xiaoming.getCompanion(lisi);
}
}
运行结果如下:
中介者模式的优缺点
优点:
- 将系统按功能分割成更小的对象,符合类的最小设计原则。
- 对关联对象的集中控制。
- 减小类的耦合程度,明确类之间的相互关系:当类之间的关系过于复杂时,其中任何一个类的修改都会影响到其他类,不符合类的设计的开闭原则,而Mediator模式将原来相互依存的多对多的类之间的关系简化为Mediator控制类与其他关联类的一对多的关系,当其中一个类修改时,可以对其他关联类不产生影响(即使有修改,也集中在Mediator控制类)。
- 有利于提高类的重用性。
缺点:
中介者会膨胀得很大,而且逻辑复杂,原本N个对象直接的相互依赖关系转换为中介者和同事类的依赖关系,同事类越多,中介者的逻辑就越复杂。