Java-增强对象的三种方式

一:继承

子类继承父类,拥有父类的特性并且可以扩展自己的功能

我们知道,子类继承父类时,可以重写,重载父类的方法,并且可以增加子类自己的属性。

例如;我们定义一个类Man,有如下属性和功能:

class Man{
    private String name;
    private Integer age;
    public Man(String name,Integer age){
        this.name = name;
        this.age = age;
    }
    public void play(){
        System.out.println(name+"在"+age+"岁经常玩");
    }
}

接下来写一个子类SuperMan,如下:

class SuperMan extends Man{
    private Integer height;
    public SuperMan(String name, Integer age,Integer height) {
        super(name, age);
        this.height = height;
    }
    public void superPlay(){
        System.out.println(super.getName()+"在"+super.getAge()+"岁"+height+"cm高时经常玩");
    }
}

测试代码如下:

public class ExtendsTest {
    @Test
    public  void fun1(){
        new Man("durant",18).play();
        new SuperMan("durant",18,170).play();
        new SuperMan("durant",18,170).superPlay();
    }
}

输出结果:

durant在18岁经常玩
durant在18岁经常玩
durant在18岁170cm高时经常玩

可以看到,我们在子类SuperMan中增加了一个属性和方法,相对于父类来说,子类是增强了的;但是相对于调用者来说,这个增强是有限制的,这也是使用继承增强对象的不足之处:

  1. 被增强的对象不能变
  2. 增强的内容不能变

被增强的对象是Man,已经写死到SuperMan里了,不能变,增强的内容也写死到SuperMan里面了,也不能改变

二:装饰者模式

装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能

装饰者模式有如下几种角色:

  • 抽象构件角色(Project):给出一个接口,以规范准备接收附加责任的对象。
  • 具体构件角色(Employe):定义一个将要接收附加责任的类。
  • 装饰角色(Manager):持有一个构件对象的实例,并定义一个与抽象构件接口一致的接口。
  • 具体装饰角色(ManagerA、ManagerB):负责给构件对象“贴上”附加的责任。

例如:我们首先定义一个接口

/**
 * 定义一个接口,读东西
 */
interface Read{
     void read();
}

接下来定义一个被增强的对象,实现了Read接口的对象,如下:

/**
 * 学生读书
 */
class Student implements Read{
    @Override
    public void read() {
        System.out.println("我开始读书了");
    }
}

定义一个装饰角色来增强对象,如下:

class DecorateStudent implements Read{

    // 这里我们定义变量,来接受不同的增强的对象
    private Read read;

    // 构造器要求传入一个被增强的对象
    public DecorateStudent(Read read){
        this.read = read;
    }
    @Override
    public void read() {
        // 在调用被增强的对象的方法前,干点事情
        System.out.println("我打开书本");
        // 调用被增强对象的方法
        read.read();
        // 在调用被增强的对象的方法后,干点事情
        System.out.println("我读完了");
    }
}

最后测试:

public class DecorateTest {
    @Test
    public void fun(){
        Student student = new Student();
        student.read();
        DecorateStudent decorateStudent = new DecorateStudent(student);
        decorateStudent.read();
    }
}

我们分别创建一个原始对象和增强之后的对象,可以看到输出结果如下:

我开始读书了
我打开书本
我开始读书了
我读完了

装饰者模式的特点是:

  1. 被增强的对象可以变
  2. 增强的内容不能变

为什么说增强的内容不能变呢?因为增强的内容被我们写死在增强类中了,也就是输出的额外的两句话;那么为什么说被增强的对象可变呢?

我们可以在调用时换另外一个对象来被增强,只要是实现了Read接口的对象,如下:

class Teacher implements Read{
    @Override
    public void read() {
        System.out.println("我是teacher,我读书");
    }
}
public class DecorateTest {
    @Test
    public void fun(){
        DecorateStudent decorateStudent = new DecorateStudent(new Teacher());
        decorateStudent.read();
    }
}

输出结果:

我打开书本
我是teacher,我读书
我读完了

可以看到,Teacher类也被增强了,但是增强的内容还是刚才的内容;

装饰者模式的优点:

  1. 使用装饰者模式比使用继承更加灵活,因为它选择通过一种动态的方式来扩展一个对象的功能,在运行时可以选择不同的装饰器,从而实现不同的行为。
  2. 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
  3. 具体构件类与具体装饰类可以独立变化,他能是低耦合的。用户可以根据需要来增加新的具体构件类和具体装饰类,在使用时再对其进行各种组合,原有代码无须改变,符合“开闭原则”。

装饰者模式的缺点:

  1. 会产生很多的小对象,增加了系统的复杂性
  2. 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。

装饰者模式和继承的对比:

1:继承

  • 优点:代码结构清晰,而且实现简单
  • 缺点:对于每一个的需要增强的类都要创建具体的子类来帮助其增强,这样会导致继承体系过于庞大。

2:装饰者模式

  • 内部可以通过多态技术对多个需要增强的类进行增强
  • 需要内部通过多态技术维护需要增强的类的实例。进而使得代码稍微复杂。

三:动态代理

动态代理有jdk动态代理和cglib动态代理,我现在要说的是jdk动态代理

首先说下动态代理的特点:

  1. 被增强的对象可以变
  2. 增强的内容可以变

那么动态代理是如何实现的呢?主要是由Proxy类的一个静态方法newProxyInstace来实现的;

查看源码发现这个方法需要三个参数:

  public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
  1. ClassLoader:每个类都会有它的类加载器
  2. interfaces :被代理对象所实现的接口些
  3. InvocationHandler 是个什么东西?

InvocationHadler是一个接口,而该接口下也只有一个方法,如下:

public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

我们举个例子来看一下这个方法:

首先:定义一个接口

interface A{
    public Object aaa(String name, Integer age);
}

接着使用Proxy.newProxyInstance来创建一个代理对象,并调用方法:

public class TestAgent {
    @Test
    public void fun(){
        Object proxyInstance = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{A.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("你好 动态代理");
                return "xxx";
            }
        });
        A a = (A)proxyInstance;
        Object result = a.aaa("hello", 100);
        System.out.println(result);
    }
}

输出结果:

你好 动态代理
xxx

可以看到,当我们调用A的aaa方法时,实际上是调用InvocationHandler里面的invoke方法,其实这里存在着如下对应关系:
这里写图片描述

目标对象,目标对象方法,目标对象参数,目标对象返回值和invoke里面的参数及返回值一一对应。

那么知道了这些之后,我们如何来实现增强对象可变和增强内容可变呢?我们以一个例子来了解一下:

首先定义一个Teacher接口,里面有个teach方法:

public interface Teacher {
     public void teach();
}

然后我们将增强以接口的形式展现,定义两个增强的接口:

// 第一个增强接口,里面一个方法
public interface FirstAdvice {
    void first();
}
// 第二个增强接口,里面一个方法
public interface SecondAdvice {
    void second();
}

接下来定义一个代理工厂类,用来生成代理对象:

public class AgentFactory {
    private Object target;
    private FirstAdvice firstAdvice;
    private SecondAdvice secondAdvice;

    public Object getProxyInstance(){
        ClassLoader classLoader = this.getClass().getClassLoader();
        Class[] interfaces = target.getClass().getInterfaces();
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 如果增强存在,那么调用增强的方法
                if (firstAdvice != null){
                    firstAdvice.first();
                }
                if (secondAdvice != null){
                    secondAdvice.second();
                }
                // 调用目标对象的方法,并返回目标方法的返回值
                return method.invoke(target,args);
            }
        };
        // 返回代理对象
        Object o = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        return o;
    }


    public Object getTarget() {
        return target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public FirstAdvice getFirstAdvice() {
        return firstAdvice;
    }

    public void setFirstAdvice(FirstAdvice firstAdvice) {
        this.firstAdvice = firstAdvice;
    }

    public SecondAdvice getSecondAdvice() {
        return secondAdvice;
    }

    public void setSecondAdvice(SecondAdvice secondAdvice) {
        this.secondAdvice = secondAdvice;
    }
}

测试类:

public class ProxyTest {
    @Test
    public void fun(){
        AgentFactory factory = new AgentFactory();//创建工厂
        // 传入目标对象
        factory.setTarget(new Teacher(){
            @Override
            public void teach() {
                System.out.println("我要教书");
            }
        });
        // 设置增强
        factory.setFirstAdvice(new FirstAdvice() {
            @Override
            public void first() {
                System.out.println("学习知识");
            }
        });
        factory.setSecondAdvice(new SecondAdvice() {
            @Override
            public void second() {
                System.out.println("考取证书");
            }
        });
        Teacher waiter = (Teacher) factory.getProxyInstance();
        waiter.teach();
    }
}

输出结果如下:

学习知识
考取证书
我要教书

可以看到,增强的对象可以任意传入实现了Teacher接口的类的对象,增强的内容可以在调用时指定接口需要实现的内容;

spring的aop就是基于动态代理实现的,理解了动态代理的原理就能更好的去理解aop;

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在Java中,for循环有以下几种方式: 1. 普通for循环 普通for循环是最常用的循环方式,其语法为: ``` for (初始化表达式; 布尔表达式; 更新表达式) { // 循环体 } ``` 其中,初始化表达式用于初始化循环变量,布尔表达式用于判断循环条件是否满足,更新表达式用于更新循环变量的值。循环体中的代码会被重复执行,直到布尔表达式的值为false为止。 2. 增强for循环 增强for循环是一种简化版的for循环,用于遍历数组或集合中的元素,其语法为: ``` for (元素类型 元素变量 : 数组或集合) { // 循环体 } ``` 其中,元素类型指定了数组或集合中元素的类型,元素变量用于接收每个元素的值,数组或集合是需要遍历的对象。循环体中的代码会被重复执行,每次执行时,元素变量会被赋值为数组或集合中的一个元素。 3. while循环 while循环是一种基于布尔表达式的循环方式,其语法为: ``` while (布尔表达式) { // 循环体 } ``` 其中,布尔表达式用于判断循环条件是否满足,循环体中的代码会被重复执行,直到布尔表达式的值为false为止。 4. do-while循环 do-while循环是一种类似于while循环的循环方式,其语法为: ``` do { // 循环体 } while (布尔表达式); ``` 其中,循环体中的代码会被重复执行,直到布尔表达式的值为false为止。与while循环不同的是,do-while循环会先执行一次循环体,然后再判断布尔表达式的值是否为true。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DurantJiang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值