一:继承
子类继承父类,拥有父类的特性并且可以扩展自己的功能
我们知道,子类继承父类时,可以重写,重载父类的方法,并且可以增加子类自己的属性。
例如;我们定义一个类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中增加了一个属性和方法,相对于父类来说,子类是增强了的;但是相对于调用者来说,这个增强是有限制的,这也是使用继承增强对象的不足之处:
- 被增强的对象不能变
- 增强的内容不能变
被增强的对象是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();
}
}
我们分别创建一个原始对象和增强之后的对象,可以看到输出结果如下:
我开始读书了
我打开书本
我开始读书了
我读完了
装饰者模式的特点是:
- 被增强的对象可以变
- 增强的内容不能变
为什么说增强的内容不能变呢?因为增强的内容被我们写死在增强类中了,也就是输出的额外的两句话;那么为什么说被增强的对象可变呢?
我们可以在调用时换另外一个对象来被增强,只要是实现了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:装饰者模式
- 内部可以通过多态技术对多个需要增强的类进行增强
- 需要内部通过多态技术维护需要增强的类的实例。进而使得代码稍微复杂。
三:动态代理
动态代理有jdk动态代理和cglib动态代理,我现在要说的是jdk动态代理
首先说下动态代理的特点:
- 被增强的对象可以变
- 增强的内容可以变
那么动态代理是如何实现的呢?主要是由Proxy类的一个静态方法newProxyInstace来实现的;
查看源码发现这个方法需要三个参数:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
- ClassLoader:每个类都会有它的类加载器
- interfaces :被代理对象所实现的接口些
- 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;