结构性设计模式(代理、适配器、装饰器)

结构型模式主要关注对象之间的关系,主要是为了改变代码结构来达到解耦的目的,代码容易维护和拓展

七种设计模式

  1. 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
  2. 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
  3. 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。
  4. 装饰(Decorator)模式:动态地给对象增加一些职责,即增加其额外的功能。
  5. 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
  6. 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
  7. 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性

代理模式(Proxy)

代理模式介绍

由于某些原因需要给某个对象一个代理以控制该对象访问,这时,访问对象不再是直接引用目标对象,代理对象作为访问对象和目标对象之间的中介

代理模式类图UML

代理模式的角色:

抽象主题(Subject):通过接口或者抽象类声明真实主题和代理对象实现的业务方法

真实主题(RealSubject):实现了抽象主题中的具体业务,是代理对象所代表的真实对象最终要引用的对象

代理(Proxy):实现了与真实主题相同的接口,其内部包含了对真实主题的引用,可以访问、控制或者扩展真实主题的功能

代码实现:

根据代理的创建时期,代理模式分为静态代理和动态代理

静态代理:由开发人员创建代理类或者特定工具自动生成源代码在对其进行编译,在程序运行前代理类class文件已经存在

动态代理:在程序运行时,通过反射机制动态创建而成

静态代理代码:

/**
 * 抽象主题
 */
public interface Subject {
    void Request();
}

/**
 * 真实主题
 */
public class RealSubject implements Subject{
    @Override
    public void Request() {
        System.out.println("访问真实主题方法...");
    }
}

/**
 * 静态代理
 */
public class StaticProxy implements Subject{
    //真实主题的引用
    private Subject realSubject;

    @Override
    public void Request() {
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        System.out.println("访问真实主题之前的预处理...");
        //调用真实主题的实现
        realSubject.Request();
        System.out.println("访问真实主题之后的后续处理...");

    }

}

    public static void main(String[] args) {
        StaticProxy staticProxy = new StaticProxy();
        staticProxy.Request();
    }

动态代理:

Java中动态代理有两种实现形式:JDK自带的动态代理和通过CGLib实现代理

JDK动态代理(通过接口)

/**
 * 接口:
 * 声明真实主题和代理对象的方法
 */
public interface IUser {
    public void talk();
}

/**
 * 真实主题
 */
public class RealUser implements IUser{
    @Override
    public void talk() {
        System.out.println("真实主题的实现的talk方法");
    }
}

/**
 * 代理辅助类
 * 要产生动态代理对象,需要当前类实现invocationHandler接口
 * 需要实现invoke方法
 */
public class UserProxy implements InvocationHandler {
    //提供一个真实主题的引用
    private IUser realUser;

    //通过构造函数传递引用
    public UserProxy(IUser user) {
        this.realUser = user;
    }


    /**
     * 是InvocationHandler接口中方法需要实现
     * @param proxy :就是动态代理生成的代理类对象
     * @param method :就是要调用的方法
     * @param args :该method方法的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("在调用真实主题之前扩展一些功能");
        //调用真实主题
        method.invoke(realUser,args);
        System.out.println("在调用真实主题之后扩展一些功能");
        return null;
    }
}

调用产生代理对象

public class JDKProxyTest {
    public static void main(String[] args) {

        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

        /**
         * 通过Proxy.newProxyInstance 方法调用产生代理对象,该方法有三个参数
         * ClassLoader loader:指定类加载器,一般指向当前类的加载器即可
         * Class<?>[] interfaces,指定当前被代理的接口
         * InvocationHandler h:代理辅助类对象
         *
         */
        IUser user = (IUser)Proxy.newProxyInstance(JDKProxyTest.class.getClassLoader(), new Class[]{IUser.class}, new UserProxy(new RealUser()));
        user.talk();
    }
}

执行结果:

 通过日志分析:动态代理对象的user.talk()方法的调用实际上是调用到辅助类的invoke方法

在调用时添加监控,查看产生的代理对象

        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

调用查看生成了一个代理类$Proxy0.class,代理类如下:

通过Proxy0类的定义看出,代理类实现了IUser接口,和静态代理模式下的代理类完全一样,因此在调用user.talk()时,根据多态原理,调用代理对象的talk方法,在Proxy()中,看到其重写了talk方法,当调用talk方法时,调用传入了 invocationHandler的对象,并调用其invoke方法

CGLib动态代理(通过继承)

JDK自带的动态代理的缺点是显而易见的,如果目标对象实现中没有接口的类,就无法选用Proxy和InvocationHandler机制来实现动态代理,这是就可以使用CGLib库来实现代理

通过拦截父类中所有的方法来判断是否需要产生动态代理,处理过程:

1、查找父类中所有非final 的public类型的方法的定义

2、将这些方法的定义转化为字节码

3、将组成的字节码转化为响应的代理的class对象

4、实现MethodInterceptor接口,用来处理对对象类上所有方法的请求(MethodInterceptor接口和JDK动态代理中InvocationHandler的功能和角色是一样的)

CGLib采用底层的字节码技术,通过字节码技术为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。

Spring中AOP技术正式基于代理来实现的,Spring中AOP对于JDK的动态代理和CGLib动态代理均有实现

CGlib动态代理需要依赖第三方的库

        <!--CGLib动态代理库-->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.2</version>
        </dependency>

父类:

public class CGLibSuper {
    public void doing() {
        System.out.println("CGLibSuper.doning");
    }
}

代理辅助类:

/**
 * CGLib代理辅助类
 * 实现methodInterceptor接口的intercept方法
 */
public class CGLibProxy implements MethodInterceptor {
    //cglib中加强器,用来创建动态代理
    Enhancer enhancer = new Enhancer();


    //设置要创建的动态父类
    public <T> T getProxy(Class<T> clazz) {
        //设置回调,相当于对于代理类上所有的方法的调用
        enhancer.setSuperclass(clazz);
        
        //所有的都会调用callback,需要实现intercept方法进行拦截
        enhancer.setCallback(this);

        //通过字节码动态创建子类实例
        return  (T)enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("CGLibProxy.intercept start");
        //拦截到父类方法,调用父类方法
        Object o1 = methodProxy.invokeSuper(o, objects);
        System.out.println("CGLibProxy.intercept end");
        return o1;
    }
}

要通过CGLib形式产生动态代理,在代理辅助类中需要实现MethodInterceptor接口,并实现其intercept方法

代理类产生:


   public static void main(String[] args) {
        CGLibProxy cgLibProxy = new CGLibProxy();
        //动态产生的代理类
        CGLibSuper proxy = cgLibProxy.getProxy(CGLibSuper.class);
        proxy.doing();
    }

代理模式再探究:

优点:

1.代理模式在客户端与目标对象之间起到一个中介和保护目标对象的作用

2.代理对象可以扩展目标对象的功能

3.代理模式能将客户端和目标对象分离,在一定程度上降低系统的耦合性,增加程序的扩展性

缺点:

1.代理模式会造成系统设计中类的数量增加

2.在客户端和目标对象之间增加了一个代理对象,会造成请求速度减慢

适用场景:

当无法或不想直接引用某个对象时,可以通过代理对象简介访问,使用代理可以保护目标对象也可以增强目标对象(增加新的功能)

Java应用

Spring中AOP技术

AOP是通过动态代理,在运行期间对业务方法进行增强。

基于XML和基于注解形式

对于代理中目标对象在AOP中称之为切入点(针对的是方法级别的切入)

增加的新功能称之为增强/通知,有5种类型

通过产生新的类来增加新的功能,而不是改变原有的类

理解基于XML实现AOP技术

给定切面类,即切入点类

public class UserService1 {
    @Autowired
    @Resource
    private MailService mailService;


    private List<User12> users = new ArrayList<User12>();


    public User12 login(String name, String password) {
        for (User12 user : users) {
            if (user.getName().equalsIgnoreCase(name) && user.getPasswd().equals(password)) {
                mailService.sendLoginMail(user);
                return user;
            }
        }
        throw new RuntimeException("login failed.");
    }

    public User12 getUser(long id) {
        for (User12 user : users) {
            if (user.getId() == id) {
                return user;
            }
        }
        return null;
    }

    public User12 register(String name, String password, String address) {
        System.out.println("注册操作....");
        User12 user = new User12(users.size()+1, name, password, address);
        users.add(user);
//        mailService.sendRegistrationMail(user);
        return user;
    }
}

增强类:增加新的功能

/**
 * 增强类
 */
@Aspect
public class LogService {
    //前置增强
    @Before(value = "execution(* com.tulun.service.UserService1.register(..))")
    public void before(){
        System.out.println("我是前置增强(注解)...");
    }

    //环绕通知
    public void  around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知之前...");
        //指定被增强的方法
        joinPoint.proceed();
        System.out.println("环绕通知之后...");
    }
}

XML配置文件

<bean id="userService" class="com.tulun.service.UserService1"/>

    <!--将增强也交给IOC容器-->
    <bean id="logService" class="com.tulun.service.LogService"/>

    <!--开启AOP操作-->
    <aop:aspectj-autoproxy/>

    <!--配置AOP操作-->
    <aop:config>
        <!--配置切入点:使用表达式-->
        <aop:pointcut id="ponitcut1" expression="execution(* com.tulun.service.UserService1.register(..))"/>

        <!--配置切面-->
        <!--aop:aspect ref:指定的是增强类-->
        <aop:aspect ref="logService">
            <!--配置增强:前置增强-->
            <!--method属性:指定的增强类中的方法 pointcut-ref属性:指定的是切入点 -->
            <!--<aop:after method="before" pointcut-ref="ponitcut1"/>-->

            <!--环绕通知-->
            <aop:around method="around" pointcut-ref="ponitcut1"/>
        </aop:aspect>
    </aop:config>

mybatis框架

StudentMapper studentMapper = sqlsession.getMapper(StudentMapper.class);

采用动态代理技术实现:JDK动态代理

适配器模式(Adapter)

将一个类的接口转化为客户所期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以通过适配器使那些类能一起工作

适配器模式分为类结构适配器和对象结构适配器

类适配器是使用继承形式实现

对象适配器是使用组合的形式实现

适配器类图UML

 

适配器模式包含角色:

适配者(Adaptee):它是被访问和适配的现存组件中的组件接口

目标接口(target):当前系统所期望的接口,可以是抽象类或者接口

适配器(Adapter):转换器,通过继承或者引用适配器的对象,把适配者接口转换为目标接口,让客户端按照目标接口格式访问适配者

适配器模式代码实战

手机电源的适配,将已存在的USB充电口(适配者)适配为TypeC充电口(目标接口)

/**
 * 存在一个USB充电口
 * 该充电口已经实现其特定的USB接口的能力
 */
public class USB {
    void isUSB(){
        System.out.println("USB充电口");
    }
}

/**
 * TypeC接口
 * 即需要的目标接口
 */
public interface TypeC {
    public void isTypeC();
}

/**
 * 类适配器
 * 核心类:将USB适配成TypeC
 */
public class ClassAdapter extends USB implements TypeC {

    @Override
    public void isTypeC() {
        //TypeC接口的实现是通过适配USB来实现的
        System.out.println("客户期望的充电口:TypeC");
        super.isUSB();
    }


    public static void main(String[] args) {
        TypeC typeC = new ClassAdapter();
        typeC.isTypeC();
    }
}

/**
 * 对象适配器
 * 将被适配的对象作为适配器的属性,通过构造函数获取实例
 */
public class ObjectAdapter implements TypeC{
    //持有适配者对象的引用
    private USB usb;

    //通过构造函数将适配者对象进行实例化
    public ObjectAdapter(USB usb) {
        this.usb = usb;
    }

    @Override
    public void isTypeC() {
        usb.isUSB();
    }

    public static void main(String[] args) {
        TypeC typeC = new ObjectAdapter(new USB());
        typeC.isTypeC();
    }
}

类适配器是通过继承形式

对象适配器是通过组合:将适配者作为适配器类中的对象引用

适配器模式再探究

优点:

1、复用了现存的类,开发人员不需要修改原有的代码而是重用现有的适配者类

2、将目标类和适配者类进行解耦,解决了目标类和适配者类接口不一致的问题

3、在很多的业务场景符合开闭原则(对修改关闭,对扩展开放)

缺点:

在实际业务场景重可能会增加系统的复杂性,另外,增加代码阅读难度,降低了代码可读性适配器使系统代码变得凌乱

适用场景:

对于系统已经存在的功能和类无法满足需求时,需要通过适配器进行适配

使用第三方提供的组件,但组件接口定义和自己需要的接口定义不同时,可以通过适配器进行适配

Java中应用

IO流的使用

适配器角色对一个InputStreamReader

被适配的角色/适配者角色:InputStrean

目标角色:Reader

目标角色还有DateInputStream、BufferInputStream

可以理解IO流的适配是通过对象适配器来实现的

装饰器模式(Decorator)

装饰器模式介绍

指在不改变现有对象结构的情况下,给对象增加一些职责或者是额外的功能

装饰器模式类图UML

 装饰器模式的角色:

抽象构件(Component):定义一个抽象的接口来声明公有的方法名

具体构件(ConcreteComponent):实现抽象构件,通过装饰角色为其添加了一些职责

抽象装饰角色(Decorator):实现抽象构件,并包含具体构件的对象,可以通过其子类扩展具体构件的功能

具体装饰角色(ConcreteDecorator):实现抽象装饰的相关方法,并给具体构件添加新的职责

装饰器模式代码实现

/**
 * Component:
 * 统一接口,也是装饰类和被装饰类的基本类型。
 */
public interface Component {
    void method();
}

/**
 *ConcreteComponent:
 * 为具体实现类,也是被装饰类,他本身是个具有一些功能的完整的类。
 */
public class ConcreteComponent implements Component {
    public void method() {
        System.out.println("原来的方法");
    }
}

/**
 * Decorator:
 * 是装饰类
 * 实现了Component接口
 */
public abstract class Decorator implements Component {
    //内部维护一个ConcreteComponent实例
    protected Component component;
    //通过构造函数实例化ConcreteComponent实例
    public Decorator(Component component) {
        super();
        this.component = component;
    }
    //Component接口提供的方法
    public void method() {
        component.method();
    }
}

/**
 * 具体装饰器类A
 * 继承自装饰器类
 */
public class ConcreteDecoratorA  extends Decorator{
    public ConcreteDecoratorA(Component component) {
        super(component);
    }
       public void methodA(){
        System.out.println("装饰器A拓展的功能");
    }

    @Override
    public void method() {
        System.out.println("针对该方法添加一层A包装");
        super.method();
        System.out.println("A包装结束");
    }
}

/**
 * 具体装饰器类B
 * 继承自装饰器类
 */
public class ConcreteDecoratorB extends Decorator{
    public ConcreteDecoratorB(Component component) {
        super(component);
    }
    public void methodB(){
        System.out.println("装饰器B拓展的功能");
    }
    @Override
    public void method() {
        System.out.println("针对该方法添加一层B包装");
        super.method();
        System.out.println("B包装结束");
    }

    public static void main(String[] args) {
        Component component =new ConcreteComponent();//原来的对象
        component.method();//原来的方法
        System.out.println("------------------------------");
        ConcreteDecoratorA concreteDecoratorA = new ConcreteDecoratorA(component);//装饰成A
        concreteDecoratorA.method();//原来的方法
        concreteDecoratorA.methodA();//装饰成A以后新增的方法
        System.out.println("------------------------------");
        ConcreteDecoratorB concreteDecoratorB = new ConcreteDecoratorB(component);//装饰成B
        concreteDecoratorB.method();//原来的方法
        concreteDecoratorB.methodB();//装饰成B以后新增的方法
        System.out.println("------------------------------");
        concreteDecoratorB = new ConcreteDecoratorB(concreteDecoratorA);//装饰成A以后再装饰成B
        concreteDecoratorB.method();//原来的方法
        concreteDecoratorB.methodB();//装饰成B以后新增的方法
        System.out.println("------------------------------");
    }
}

装饰器模式再探究

优点:

装饰器是继承的一种补充,比继承更加灵活,在不改变原有对象的情况下,动态给一个对象扩充功能

装饰器模式是完全遵守开闭原则

缺点:

装饰器模式会增加很多子类,使程序复杂度增加了

适用场景:

当需要给一个现有的类增加新的职责,又不能采用生成子类的方法进行扩展是

当对象的功能要求可以动态地添加,也可以动态的撤销时

Java中应用

Java中IO流

InputStream相当于是conponent抽象构件

FileInputStream、PipedInputStream等相当于是具体构件(继承自InputStream并实现方法)

FilterInputStream相当于是抽象装饰器(Decorator)(也是继承自InputStream,内部持有一个InputStream实例引用)

BufferedInputStream等相当于是具体装饰器,扩展了新的功能,比如当前的额缓冲功能

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值