1、继承
- java继承的来源
众所周知,java是基于c++/c开发的一门语言,底层采用c/c++实现,核心思想是面向对象编程,可以说java将面向对象编程发挥到了极致,在java的世界有这样一句话:“一切事物皆对象”。java延续了c++中面向对象编程思想,与c++有很大的相似之处,但与c++又有不同,它摒弃了c++类之间多重继承,却允许接口间多重继承,解决了c++多重继承间出现的许多问题。除此之外还有许多不同,在这里不作讨论。
- 继承的应用场景
继承最基本是用于代码复用、扩展功能模块等,例如开发完成一款应用软件后,随着市场的扩大,需要扩大业务范围,添加新功能、按照传统的开发需要将软件源码修改,由于所有代码都写在一块,维护和修改具有一定难度。使用继承后可以在原有基础上添加扩展功能,新功能代码与源代码分离,便于维护编写。
2、装饰者模式
装饰者模式是继承的一个特例。它来源于这样一个场景:对于某一个类,随着需求不断变化,里面的某些方法无法满足开发者的需求,但又不得不使用这个方法,那怎么办?那我们只好写一个类继承这个类,重写对应方法来满足需求。这种模式称为装饰者模式。一般具有两个要素:装饰类,被装饰对象。除此之外,被装饰对象所属类与装饰类提供构造方法将被装饰对象传入进而将这个对象传给父类初始化,装饰类重写对应方法满足自己需求。装饰类与被装饰对象所属类的唯一区别就是满足需求的方法实现方式不一样,在其它文档中提到被装饰类与装饰者类要具有共同的超类,在java中 所有的类都继承于Object,所以可以将它们的超类看做Object类。例如:
//电话类具有打电话、发短信、彩铃功能
public class call{
boolean fee=ture;//存存电话费信息
boolean gprs=true;//存储流量信息
public void call(){
system.out.print("打电话!");
}
public void sendMsg(){
system.out.print("发短信!");
}
public void sendRing(){
system.out.print("发彩铃!");
}
}
电话类具有打电话、发短信、发彩铃的功能,但是存在着样一个问题,假如我没话费, 打不了传统电话,但是我有流量,那能不能使用流量打电话呢?这就可以使用装饰者模式实现实现如下:
//装饰者模式实现,继承call类
public class Decorator extends Call{
Call call;//保存传过来的call对象
public decorator(Call call ){
super(call);//将call传给父类初始化
this.call=call;
}
@Override//重写call方法实现需求
public void call(){
if(call.fee==false && call.gprs){
system.out.print("用流量打电话!");//有流量没话费
}else{
system.out.print("用话费打电话!");//有话费
}
}
}
除此之外,我们还可以在装饰了里添加其它功能,但这不属于装饰类的功能,添加功能一般交给继承去做,装饰类只管修改父类某些方法内功能,这也是它与继承的不同。在java中,所有类都是Object的子类,装饰者与被装饰者具有共同的超类Object
3、代理
代理是代替某一实现类执行其功能的一种技术,在代理的同时需要完成额外的业务,不然代理类就和被代理类一样。代理类存在的意义:完成主要功能的同时完成附加功能。以上面打电话为例:在打电话时一般都自带铃声,那这个功能如何实现?则可以使用代理实现。,打电话是主要功能,铃声是附加功能。
1、代理分类
代理分为静态代理和动态代理
静态代理:是自己在先写好的代理,在运行的时候加载运行,因为是事先写好的,所以静态代理只能为一个类代理,其它类需要代理则重新写一份。其适用于代理比较少的情况,
动态代理:是在运行时动态生成的一种代理,可以针对不同实现类生成不同代理,灵活性高,适用于代理较多的时候,动态代理又分为JDK代理和cglib代理,前者是JKD自带的API,只为实现接口的类生成代理,后者是第三方包,可以为任何实现类生成代理,抽象类除外,讨论抽象了没有意义。
2、代理实现
实现代理主要关注:代理对象(实现类),代理类(主要关注)、边缘业务类(额外业务)、代理对象提供生成代理对象的的信息,底层通过反射生成代理对象,代理类:根据代理对象提供的信息生成代理对象并返回代理对象。边缘业务类封装实现边缘业务的方法,提供边缘业务实现
代理了编写:写一个类提供获取代理的方法,方法返回代理对象,参数为被代理对象、边缘业务对象。
以打电话为例,实现如下:
//JKD代理
public class JDKProxy {
//编写获取代理方法
public Object getProxy(Call call,BorderTask task) {
Object proxy=Proxy.newProxyInstance(call.getClass().getClassLoader(), call.getClass().getInterfaces(),new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
task.run();//边缘业务
Object result=method.invoke(call, args);//回调执行核心业务
return result;//返回执行结果
}
});
return proxy;//返回代理方法
}
}
使用JDK代理生成代理对象有以下注意点:
1)Proxy的newProxyInstance方法第一个参数为代理对象加载器,参数二为代理对象实现接口,第三个为回调对象(代理对象调用核心业务方法时会被其拦截,拦截后先让边缘业务执行,最后执行核心业务,核心业务采用回调的方法执行),最后返回结果
2)必须返回结果,否则没有输出,task.run()实现电话铃音的功能
3)代理是代理对实现象接口的另一个实现。但不单单是实现,因为运行时它会调用被代理对象执行核心业务,以此与被代理对象建立联系,
使用cglib实现:
//使用cglib实现
public class CglibProxy {
public Object getProxy(Call call,BorderTask task) {
//创建增强器(代理)
Enhancer han=new Enhancer();
//设置接口可以不设
han.setInterfaces(call.getClass().getInterfaces());
//设置父类
han.setSuperclass(call.getClass());
//设置回调器
han.setCallback(new InvocationHandler() {
@Override
public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
task.run();//运行边缘业务
Object result=arg1.invoke(arg0, arg2);//运行核心业务
return result;
}
});
}
}
从上图可知,与JDK代理相比cglib将父类、接口、回调器都的设置都分开了,而JDK将它结合在newProxyInstance方法中,由于cglib存在父类设置,所以它能够为没有借口的实现类创建代理。
3、代理的优势
通过代理可以实现对对象方法执行进行控制,向方法内添加其它执行步骤(边缘业务)的同时不改变源码。使用代理可以实现类之间的解耦,
4、面向切面编程
面向切面编程是一种编程思想,可以实现对对象的细粒度控制,同时还可以将低类之间的耦合度,便于代码维护管理与开发。假如将程序的执行看做一个圆柱体,由上至下执行,则面向切面编程就是将它横切开来,然后从横切面添加需要的功能(边缘业务),添加功能后不影响程序的执行。面向切面编程使用AspectJ实现,但不仅限于这一种实现方式,但其实现核心依然是代理。
4.1、面向切面编程有以下几个概念
切面:实体化为封装边缘业务的类,使用这个类将执行体切开;
切入点:一个程序执行的时候不止一个类,需要从哪个类切入进去,哪个类就是切入点;
连接点:进入切入点(类)后有许多方法,执行核心业务的方法就是连接点;
通知:执行的边缘业务,或者说执行核心业务外还需要执行的其它业务。根据执行时机的不同分为5种:前置通知、后置通知、最终通知(在执行finally后执行)、异常通知(出现异常后执行)。环绕通知,环绕通知比较特殊,它能改变执行体的执行结果,也可以独自担任前四种通知的功能,经常独自使用。
使用面向切面变程需要注意以下几个方面:
1、分清核心业务与边缘业务。面向切面编程,目的在于不改变核心业务实现的情况下添加边缘业务的功能。所以需要分清两者,才能轻松地找到切入的对象。
2、实现顺序。就我个人来说,实现顺序为:先实现核心业务功能,后添加切片功能。这样利于代码的编写,添加切片功能相对容易,同时利于测试。
4.2面向切片编程的实现
假如存在这样一个场景:需要从控制层调用数据层对象对数据库插入用户数据,在操作完成前后需要打印对数据库操作的日志。
分析:核心业务------从控制层调用数据层对象操作数据,边缘业务-----造对数据库操作前后打印日志
实现流程:核心业务实现-----控制层类UserController、数据层类UserDao接口,UserDaoImp接口实现类,边缘业务----日志打印类Print;可以采用xml、注释的方式,由于注释简单方便,采用注释实现。
控制层类UserController
package controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import dao.UserDao;
@Controller
public class UserController {
@Autowired
UserDao userdao;
//调用数据层对象操作数据
public void insert() {
userdao.insertUser("张三");
}
}
数据层UserDao及UaerDaoImp
接口UserDao:
package dao;
public interface UserDao {
public void insertUser(String name);
}
UserDaoImp类
package dao;
import org.springframework.stereotype.Repository;
@Repository
public class UserDaoImp implements UserDao{
public void insertUser(String name) {
System.out.println("插入用户数据:"+name);
}
}
以上是核心业务代码:编写测试单元,运行结果如下
边缘业务代码编写:
package common;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/*
* 用户切面,打印操作数据库日志
* */
@Component
@Aspect
public class UserAspect {
//定义全局切面为UserDaoImp
@Pointcut("within(dao.UserDaoImp)")
public void pointCut() {
}
@Before("poointCut()")
public void beforeMethod() {
System.out.println("开始插入");
}
@After("pointCut()")
public void afterMethod() {
System.out.println("插入结束");
}
}
编写测试运行结果为:
从结果可以知道我们将日志功能加入对用户的操作中,实现日志的输出,结合代码可以知道我们仅仅使用一个切片类运用切片技术,将日志的输出功能添加到对用户数据的操作中,而不更改源代码。同时,以为可以动态取消目标类的日志输出或给其它功能添加日志输出。这也是面向切片编程的优点-------降低了代码之间的耦合度,便于代码的编写与维护动,态改变功能而不设计主要代码的更改。
5、继成、装饰者模式、代理、面向切片编程之间区别与联系
联系:
1)、它们都能在完成主要功能(核心业务)的前提下完成附加业务(边缘业务),以核心业务为主要目标,附加业务为次要目标,进行实现。
2)、实现步骤:先完成核心业务,再运用各自的解决方案完成附加业务。
3)、都可以降低代码之间耦合度,不同技术解耦程度不一样,面向切片技术解耦程度相对最高
不同:
1)、完成附加业务侧重点不同。继承侧重于大模块的附加业务一般不需要修改源码,在原有功能基础上添加功能模块,水平层次小于等于原有层次。同时,其它几种技术能实现的功能它也能完成,但代码之间的耦合性较大,维护困难,当业务改动时需修改源码工作量大。装饰者模式侧重于对源码修改从而添加边缘业务,使用继承的方法重写技术。运用重写技术实现边缘业务的添加及源码的修改。代理是在不修改、覆盖源码的情况下实现边缘业务的添加,侧重于动态添加边缘业务,继承与装饰者模式事先将代码编写完毕在运行,而代理技术可以在运行时动态添加边缘业务,代理的实现原理:运用反射技术在运行期间动态获取(核心业务类)目标类的信息,在创建代理类运用方法回调实现核心业务的基础下完成边缘业务的添加,返回执行结果。代理分为静态代理和动态代理。静态代理为事先编写好的代理,只能为一个类代理,无法实现代码的重用。适用于小型少代理的项目。动态代理分为JDK代理和cglib代理,JDK代理由Java提供的API实现缺点是只能为接口代理,无法为实例类代理,cglib代理由第三方cglib包实现,可以为接口与实例类代理。面向切片编程侧重点与代理一样,只是实现方式有所不同,面向切片编程是自己写切片类运用切片技术将边缘业务织入核心业务。实现原理是将切片类中实现边缘业务的方法在编译期、类加载期、运行期织入目标对象生成代理,最后运行代理得出结果。
2),运用技术层层递进,对对象控制粒度逐渐变细,控制更加精确。装饰者类利用继承实现,控制粒度为对象的方法;代理运用反射,方法回调、继承实现(cglib代理)控制粒度为对象方法的执行语句(在核心业务的执行期间可以添加任意执行语句)。面向切片技术运用代理技术实现,控制粒度为对象方法执行实际,可以在边缘业务执行前、后执行,业务可以不执行核心业务(利用通知实现)。