OOAD-设计模式(四)结构型模式之适配器、装饰器、代理模式
前言
前面我们学习了创建型设计模式,其中有5中,个人感觉比较重要的是工厂方法模式、单例模式、原型模式。接下来我将分享的是结构型模式!
一、适配器模式
1.1、适配器模式概述
适配器模式(Adapter)属于结构型设计模式,它的作用如同它的名字一样,用于转换接口。像我们的手机、电脑的电源适配器一样,适配器模式可以使彼此不兼容的代码间优雅地协作。
适配器模式将某个类的接口转换成客户端(用户)期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。
1)角色及职责
目标接口: Target,即我们想用适配器转换过去的新的目标接口。
被适配者: Adaptee,适配器应用的对象。
适配器: Adapter,转换接口的类
2)适用场景
大多数情况下适配器模式会用在这两个场景下:
系统中历史遗留的代码与新的代码之间的兼容处理,用于将旧的接口转换为新的接口,使得新的代码与旧的代码能够愉快地玩耍。
有两个或者多个系统或者模块存在不兼容的接口,用于将各个模块或系统间互不兼容的接口转换为通用的接口,使得它们愉快地合作。
1.2、类的适配器模式
核心思想就是:有一个Source类,拥有一个方法,待适配,目标接口是Targetable,通过Adapter类,将Source的功能扩展到Targetable里。
public class Source { public void method1() { System.out.println("this is original method!"); } } public interface Targetable { /* 与原类中的方法相同 */ public void method1(); /* 新的方法 */ public void method2(); } //类Source和接口Targetable因为不兼容,导致不能在一起工作 //适配器Adapter则可以在不改变源代码的基础上解决这个问题 //这样Targetable接口的实现类Adapter的对象即使Targetable类型,也能访问到Source中的方法 public class Adapter extends Source implements Targetable { public void method2() { System.out.println("this is the targetable method!"); } } //测试类 这样Targetable接口的实现类就具有了Source类的功能。 public class AdapterTest { public static void main(String[] args) { Targetable target = new Adapter(); target.method1(); target.method2(); } }
1.3、对象的适配器模式
基本思路和类的适配器模式相同,只是将Adapter类作修改,这次不继承Source类,而是持有Source类的实例,以达到解决兼容性的问题。
//只需要修改Adapter类的源码即可: public class Wrapper implements Targetable { private Source source; public Wrapper(Source source){ this.source = source; } public void method2() { System.out.println("this is the targetable method!"); } public void method1() { source.method1(); } } //测试类 输出与第一种情况一样,只是使用的适配方法不同而已。 public class AdapterTest { public static void main(String[] args) { Source source = new Source(); Targetable target = new Wrapper(source); target.method1(); target.method2(); } }
1.4、接口的适配器模式
接口的适配器是这样的:有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,
有时只需要某一些,此处为了解决这个问题,我们引入了接口的适配器模式。
解决:借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取
得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行。例如我们这GUI这个章节应该是见过不少的监听器接口的适配器类:XxxxAdapter。
public interface Sourceable { public void method1(); public void method2(); } //抽象类 public abstract class Wrapper implements Sourceable{ public void method1(){} public void method2(){} }
之后在我们写的子类中需要什么方法去重写什么方法就可以了,就不需要把接口中的所有方法都实现了。
1.5、总结
三种情况适配器模式的总结:
类的适配器模式:
当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。
对象的适配器模式:
当希望将一个对象转换成满足另一个新接口的对象时,可以创建一个Wrapper类,持有原类的一个实例,在Wrapper类的方法中,调用实例的方法就行。
接口的适配器模式:
当不希望实现一个接口中所有的方法时,可以创建一个抽象类Wrapper,实现所有方法,我们写别的类的时候,继承抽象类即可。
二、装饰器模式
2.1、适配器概述
装饰器模式(Decorator),属于结构型设计模式。通过对象组合的方式给对象动态增加行为。
顾名思义,装饰器模式就是给一个对象增加一些新的功能,而且是【动态】的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例
这里的动态指的是用户可以根据自己的需求把之前定好的功能任意组合。
JDK中的IO流部分就是典型的使用了装饰模式,回忆一下BufferedReader对象的是如何创建的?
1)角色以及职责
Component: 装饰器模式应用的目标组件的抽象
Concrete Component: Component 的具体实现
Decorator: 装饰器的抽象
Concrete Decorator: 装饰器的实现类
2)适用场景
想通过对象组合的方式动态的给对象增加行为
2.2、代码实现
//功能接口 public interface Action { public void go(); } //被装饰的类 就是需要我们装饰的目标 public class Person implements Action{ public void go() { System.out.println("我在走路"); } } //抽象的装饰类 public abstract class Decorator implements Action{ private Action action; public Decorator(Action action) { this.action = action; } public void go() { this.action.go(); } } //具体的装饰类 可以添加一个听音乐的功能 public class ListenDecorator extends Decorator{ public ListenDecorator(Action action) { super(action); } public void go() { listen();//可以在go方法【前】添加一个听音乐的功能 super.go(); } public void listen(){ System.out.println("我在听音乐"); } } //具体的装饰类 可以添加一个休息的功能 public class RelaxDecorator extends Decorator{ public RelaxDecorator(Action action) { super(action); } public void go() { super.go(); relax();//可以在go方法【后】添加一个休息的功能 } public void relax(){ System.out.println("我在休息"); } } //测试类 public class Test { /*用户可以根据需求 任意给go方法添加听音乐或者休息的功能*/ //Action a = new Person(); //Action a = new ListenDecorator(new Person()); //Action a = new RelaxDecorator(new Person()); //Action a = new RelaxDecorator(new ListenDecorator(new Person())); Action a = new ListenDecorator(new RelaxDecorator(new Person())); a.go(); }
装饰器模式的应用场景:
需要扩展一个类的功能,但是又不能修改源代码。
动态的为一个对象增加功能,而且还能动态撤销。
缺点:产生过多相似的对象,不易排错!
三、代理模式(Proxy)-重点
3.1、代理模式概述
代理模式(Proxy)属于结构型设计模式。很多时候,为了节省资源或者控制对象访问,我们需要通过一个代理对象去访问指定的对象。这个代理对象,将作为真实对象的“替身”或者“占位符”,
让我们在控制对象访问的同时保持对目标对象的访问的透明性。这种编程技巧在面向对象编程中有一个固定的套路和约定的名称,即为代理模式。
其实每个模式名称就表明了该模式的作用,代理模式就是多一个代理类出来,替原对象进行一些操作,比如我们在租房子的时候回去找中介,为什么呢?
因为你对该地区房屋的信息掌握的不够全面,希望找一个更熟悉的人去帮你做,此处的代理就是这个意思。
再如我们有的时候打官司,我们需要请律师,因为律师在法律方面有专长,可以替我们进行操作,表达我们的想法。
1)角色与职责
Subject: 代理模式作用的目标对象的抽象。有些人把它翻译为主题,不过我认为这里翻译为主体或许更为恰当。
Real Subject: Subject 的具体实现。可以简单的理解为代理模式作用的目标对象对应的类。
Proxy: 代理对象,Real Subject 的替身。
3.2、代码简单实现
//公共接口 public interface Sourceable { public void method(); } //目标类/被代理类 public class Source implements Sourceable { public void method() { System.out.println("the original method!"); } } //代理类 public class Proxy implements Sourceable { private Source source; public Proxy(Source source){ this.source = source; } public void method() { before(); source.method(); atfer(); } private void atfer() { System.out.println("after proxy!"); } private void before() { System.out.println("before proxy!"); } } //测试类 public class ProxyTest { public static void main(String[] args) { Source target = new Source(); Sourceable proxy = new Proxy(target); proxy.method(); } }
1)代理模式的应用场景:
如果已有的方法在使用的时候需要对原有的方法进行改进,此时有两种办法:
1)修改原有的方法来适应。这样违反了“对扩展开放,对修改关闭”的原则。
2)就是采用一个代理类调用原有的方法,且对产生的结果进行控制。这种方法就是代理模式。
使用代理模式,可以将功能划分的更加清晰,有助于后期维护!
2)对代理模式的一些重要扩展
用户tom---买--->商品
由于各种原因导致不是很方便购买,所以就找代购
用户tom---找--->代购者zs---买--->商品
那么在代理模式中,用户tom就是目标对象,代购者zs就是代理对象
创建目标对象的类叫目标类或者被代理类
创建代理对象的类叫代理类
3.3、代理模式和装饰器模式的区别
装饰器模式和代理模式在很多情况下,大部分代码都是类似的,但是这俩种设计的意图是不一样的,装饰模式是增强被包装对象的功能,代理模式是控制被代理对象的行为 。
例如:一块代码,如果被描述为使用了装饰模式,那么我们就知道设计的意图是增加被包装对象的功能,
如果被描述为使用了代理模式,那么我们就知道设计的意图是控制被代理对象的行为,虽然这俩种情况下他们的代码结构基本相同。
装饰器模式:能动态的新增或组合对象的行为。
代理模式 :为目标对象提供一种代理以便控制对这个对象的访问。
装饰模式是“新增行为”,而代理模式是“控制访问”。
分析:
装饰模式:对被装饰的对象增加额外的行为
如:杯子生产线,杯子必须可以装水,在生产线上可以给杯子涂颜色,加杯盖,但要保证杯子可以装水。
代理模式:对被代理的对象提供访问控制。
如:客户网上商城订购商品,网上商城是厂家的代理,网上商城可以帮客户完成订购商品的任务,但是商城可以对商品进行控制,
不交钱不给商品,人不在不给商品,也可以赠送你额外的礼品,代金券。
3.4、代理类的分类
代理类可分为两种:
静态代理类:
由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理类:
在程序运行时,运用反射机制动态创建而成。
与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,
因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包下面的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。
CGLib代理(第三方类库)
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib 采用了非常底层的字节码技术,
其原理是通过字节码技术为目标对象创建一个子类对象,并在子类对象中拦截所有父类方法的调用,然后在方法调用前后调用后都可以加入自己想要执行的代码。
需要这种方法只是需要俩个第三方jar包: cglib-3.2.1.jar和asm-5.0.4.jar
同时很多框架已经把这些jar包整合到一起了,比如spring框架的spring-core-3.2.4.RELEASE.jar,这一个jar包就包括上述俩个jar包的大多数功能。
3.5、代码实现
1)静态代理: staticProxy
//公共接口 public interface HelloService { void sayHello(); } //委托类 public class HelloServiceImpl implements HelloService{ public void sayHello() { System.out.println("hello world"); } } //代理类 public class HelloServiceProxy implements HelloService{ private HelloService target; public HelloServiceProxy(HelloService target) { this.target = target; } public void sayHello() { System.out.println("log:sayHello马上要执行了..."); target.sayHello(); } } //测试类 public class Test { public static void main(String[] args) { //目标对象 HelloService target = new HelloServiceImpl(); //代理对象 HelloService proxy = new HelloServiceProxy(target); proxy.sayHello(); } }
2)JDK的动态代理: dynamicProxy
//Student类 public class Student { private long id; private String name; private int age; get/set } //日志类 public class StudentLogger { public void log(String msg){ System.out.println("log: "+msg); } } //Service接口 处理学生的相关业务 public interface IStudentService { void save(Student s); void delete(long id); Student find(long id); } //接口的一个简单实现 public class StudentServiceImpl implements IStudentService { public void delete(long id) { System.out.println("student is deleted..."); } public Student find(long id) { System.out.println("student is found..."); return null; } public void save(Student s) { System.out.println("student is saved..."); } } //InvocationHandler接口的实现类 //JDK动态代理中必须用到的接口实现 public class MyHandler implements InvocationHandler{ private Object target; private StudentLogger logger = new StudentLogger(); public MyHandler(Object target, StudentLogger logger) { this.target = target; this.logger = logger; } public MyHandler(Object target) { this.target = target; } //参数1 proxy 将来给目标对象所动态产生的代理对象 //参数2 method 将来你所调用的目标对象中的方法的镜像 //参数3 args 将来你所调用方法的时候所传的参数 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String msg = method.getName()+"方法被调用了..."; logger.log(msg); Object o = method.invoke(target, args); return o; } } //测试类 public class DProxyTest { public static void main(String[] args) { IStudentService target = new StudentServiceImpl(); ClassLoader loader = target.getClass().getClassLoader(); Class<?>[] interfaces = target.getClass().getInterfaces(); InvocationHandler h = new MyHandler(target); //参数1 loader 目标对象的类加载器 //参数2 interfaces 目标对象所实现的接口 //参数3 h InvocationHandler接口的实现类对象 IStudentService proxy = (IStudentService)Proxy.newProxyInstance(loader, interfaces, h); proxy.delete(1); proxy.save(null); proxy.find(1); System.out.println(proxy.toString()); System.out.println(proxy.getClass()); System.out.println(target.getClass()); } }
3)第三方jar包提供的动态代理(cglib) CglibProxy
//目标的对象 没有实现接口 public class BookService { public void addBook() { System.out.println("添加书籍成功"); } } //产生代理对象的工厂类 public class MyCglibProxyFactory implements MethodInterceptor { public Object getInstance(Class<?> c) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(c); enhancer.setCallback(this); return enhancer.create(); } public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("开始执行方法"); //这句代码最终会执行到我们目标对象中的方法 proxy.invokeSuper(obj, args); System.out.println("方法执行结束"); return null; } } //测试类 public class TestCglibProxy { public static void main(String[] args) { MyCglibProxyFactory cglib=new MyCglibProxyFactory(); BookService bookCglib= (BookService)cglib.getInstance(new BookService().getClass()); bookCglib.addBook(); System.out.println(bookCglib.getClass()); } }
喜欢就点个“推荐”哦!