最近看spring源码的时候会看到很多经典的设计模式,索性就把几个常用的经典的设计模式再复习总结遍,常更新帖。
- 观察者模式
- 适配器模式
- 代理模拟
- 装饰器模式
一、观察者模式
是什么:观察者模式是在维护多个对象之间的一对多的依赖关系,也就是说,当一个对象的状态发生了改变时会通知所有依赖于它的对象,好让这些对象对新的变化做出反应。观察者模式既可以有效的维护对象的一致性,又能最大限度的降低对象之间的耦合度。
长什么样:
从上面的UML类图可以看出:有两个接口,一个是主体对象接口,一个是观察者接口。主体对象接口定义了通知的方法,实现它的类Boss中有新增/删除观察者的方法;观察者接口定义了更新的方法,当boss的状态改变后,boss会notify所有的观察者来执行update方法做相应的更新操作。这么说可能还有点抽象,下面直接上代码demo。
怎么做:
public interface BossInterface
{
void notify();
}
public interface Observer
{
void update();
}
public class Boss implements BossInterface
{
private String bossSay;
private List<Observer> observers = new ArrayList<Observer>();
// 增加观察者
public void addObserver(Observer observer)
{
this.observers.add(observer);
}
// 移除观察者
public void deleteObserver(Observer observer)
{
this.observers.remove(observer);
}
//获取Boss状态
public String getBossSay()
{
return bossSay;
}
//设置Boss状态
public void setBossSay(String bossSay)
{
this.bossSay = bossSay;
}
//通知所有的观察者工作
public void notify()
{
foreach (Observer observer in observers)
{
observer.update();
}
}
}
public class Coder implements Observer
{
private Boss boss;
public Coder(Boss boss)
{
this.boss = boss;
}
// 更新状态
public void update()
{
if (boss.getBossSay() == "兄弟们,为了世界和平,一起Hello,world吧!")
{
//开始写代码
}
}
}
public class UI extends Observer
{
private Boss boss;
public UI(Boss boss)
{
this.boss = boss;
}
// 更新状态
public void update()
{
if (boss.getBossSay() == "兄弟们,为了世界和平,一起Hello,world吧!")
{
//开始写UI
}
}
}
public class PM extends Observer
{
private Boss boss;
public PM(Boss boss)
{
this.boss = boss;
}
// 更新状态
public void update()
{
if (boss.getBossSay() == "兄弟们,为了世界和平,一起Hello,world吧!")
{
//开始写项目的各种文档,请程序员、测试、运维、美工吃饭
}
}
}
二、适配器模式
是什么:适配器模式就是将那些原本由于接口不兼容而不能一起工作的类适配成可以在一起工作的类,适配器是一种增强或转换的工具,将那些不符合我们的需求但已经存在的类的接口,在不修改这类接口本身的情况下适配成我们想要的类。
长什么样:
怎么做:
// 已存在的、具有特殊功能、但不符合我们既有的标准接口的类
class ExistingClass {
public void enhanceMethod() {
System.out.println("被适配类具有 特殊功能...");
}
}
// 目标接口,或称为标准接口
interface RequiredInterface {
public void requiredMethod();
}
// 原本存在的类,只提供普通功能
class OriginalClass implements RequiredInterface {
public void requiredMethod() {
System.out.println("普通类 具有 普通功能...");
}
}
// 适配器类,继承了被适配类,同时实现标准接口
class Adapter extends RequiredInterface implements RequiredInterface{
public void requiredMethod() {
super.enhanceMethod();
}
}
三、代理模式
代理模式顾名思义就是使用了一个代理人,有代理就有在代理背后的目标对象,代理模式的意义在你不直接对目标对象进行操作,而是通过代理对象来操作目标对象,那么在代理对象这一层你就可以对目标对象进行很多增强的工作并且不用改变目标对象的代码。在实际工作中也很有指导意义,就是永远别去改别人已经写好的上线测试过了的代码,你可以利用代理模式来扩展目标对象的功能。
举个栗子:你购买了一个第三方的工具,当你在使用上问题的时候,你可能不能直接找到写这个工具的程序猿/媛,但你可以找到售后服务人员或技术支持人员,这个角色充当的就是代理对象,程序猿/媛就是目标对象,你找售后人员反映问题,售后把问题总结后集中反馈给程序猿/媛,他们去修改代码,给出个解决方案,具体的流程如下图所示:
从上图可以看出,用户先去调售后服务(代理对象),她是我们的程序猿/媛(目标对象)的代理,代理对象是对目标对象的扩展,同时代理对象会调用目标对象的方法来完成目的。
代理模式根据其代理的方式分为三种:静态代理,动态代理和Cglib代理
3.1 静态代理
代理方式:代理对象和目标对象必须实现同一个接口或继承同一个父类,通过调用代理对象的相同的方法来调用目标对象的方法。
代码实现:
/**
* 制定好的行业标准
*/
public interface OperationStandard {
void operate();
}
/**
* 目标对象,实现相同的行业标准
*/
public class Coder implements OperationStandard {
public void operate() {
//调整代码,提出解决方案
}
}
/**
* 代理对象,实现相同的行业标准
*/
public class Service implements OperationStandard{
//接收保存目标对象
private Coder target;
public Service(Coder target){
this.target=target;
}
public void operate() {
System.out.println(“增强功能1...");
target.operate();//执行目标对象的方法
System.out.println(“增强功能2...");
}
}
/**
* 用户
*/
public class Customer {
public static void main(String[] args) {
//目标对象
Coder target = new Coder();
//代理对象,把目标对象传给代理对象,建立代理关系
Service proxy = new Service(target);
proxy.save();//执行的是代理的方法
}
}
静态代理虽然简单,但缺点也很明显:目标对象和代理对象都要实现相同的接口,一旦接口需要变动,目标类和代理类都需要维护。这很好理解,售后人员和程序猿/媛之间对项目都有明确的标准的需求或功能文档,程序猿/媛按照标准文档开发,售后人员按照标准文档对外提供服务,一旦公司的这些标准文档变化了,那么需要组织售后人员和程序猿/媛一起进行培训,这样无疑很耗时耗力。
动态代理可以很好的解决这个问题。
3.2 动态代理
动态代理利用JDK的API动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
JDK中生成代理对象的API:java.lang.reflect.Proxy.newProxyInstance
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:
ClassLoader loader,:指定当前目标对象使用类加载器,获取加载器的方法是固定的
Class<?>[] interfaces,:目标对象实现的接口的类型,使用泛型方式确认类型
InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
行业标准和目标类和静态代理是一样的,下面只写代理类的实现:
/**
* 创建动态代理对象
* 动态代理不需要实现接口,但是需要指定接口类型
*/
public class Service{
//维护一个目标对象
private Object target;
public Service(Object target){
this.target=target;
}
//给目标对象生成代理对象
public Object getProxyInstance(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("增强功能1");
//执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("增强功能2");
return returnValue;
}
}
);
}
}
/**
* 用户
*/
public class Customer {
public static void main(String[] args) {
// 目标对象
OperationStandard target = new Coder();
// 【原始的类型 class com.intelligentler.Controller.Coder】
System.out.println(target.getClass());
// 给目标对象,创建代理对象
OperationStandard proxy = (OperationStandard) new Service(target).getProxyInstance();
// class $Proxy0 内存中动态生成的代理对象
System.out.println(proxy.getClass());
// 执行代理对象的方法
proxy.operate();
}
}
这个时候售后人员就厉害了,她不需要依赖标准文档就可以对接不同类型的程序猿/媛,你想让她去代理哪个程序猿/媛都可以,因为她会动态的去调用不同目标对象的执行方法。但动态代理中的目标对象还是要实现标准接口,否则无法使用动态代理。
3.3 Cglib代理
JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现。有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理。
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
Cglib子类代理实现方法:
1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入spring-core-3.2.5.jar即可.
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为final,否则报错
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
代码实现:
/**
* 目标对象,没有实现任何接口
*/
public class Coder {
public void operate() {
//调整代码,提出解决方案
}
}
Cglib代理工厂:ServiceProxyFactory.java
/**
* Cglib子类代理工厂
* 对Coder在内存中动态构建一个子类对象
*/
public class ServiceProxyFactory implements MethodInterceptor{
//维护目标对象
private Object target;
public ServiceProxyFactory(Object target) {
this.target = target;
}
//给目标对象创建一个代理对象
public Object getProxyInstance(){
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("增强功能1");
//执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("增强功能1");
return returnValue;
}
}
/**
* 用户
*/
public class Customer {
@Test
public void test(){
//目标对象
Coder target = new Coder();
//代理对象
Coder proxy = (Coder)new ServiceProxyFactory(target).getProxyInstance();
//执行代理对象的方法
proxy.save();
}
}
Cglib代理中售后人员和程序猿/媛有着无比的默契,沟通不再需要标准文档。
在Spring的AOP编程中:
如果加入容器的目标对象有实现接口,用JDK代理
如果目标对象没有实现接口,用Cglib代理
四、装饰器模式
装饰器模式可以在运行时增强或改变对象的功能。通常给对象增强功能,有三种方式:
1、直接修改对象添加相应的功能
2、派生对应的子类来扩展
3、使用对象组合的方式
显然,直接修改对应的类这种方式并不可取,它需要我们去修改以前的代码,这是程序猿的大忌。适配器模式就是派生对应的子类来扩展对象的功能。在面向对象的设计中,我们应该尽量使用对象组合,而不是对象继承来扩展和复用功能。装饰器模式就是基于对象组合的方式,可以很灵活的给对象添加所需要的功能。
装饰器模式类图
Component:待装饰对象的接口
ConcreteComponent:待装饰对象的具体实现类
Decorator:所有装饰器的父类,它也可以继承Component类,以便可以自己增强自己。
ConcreteDecorator:具体的装饰器类
Java代码实现:
public abstract class Component {
/**
* 待增强的方法
*/
public abstract void operation();
}
public class ConcreteComponent extends Component {
public void operation() {}
}
public abstract class Decorator extends Component {
/**
* 持有组件对象
*/
protected Component component;
/**
* 构造方法,传入组件对象
* @param component 组件对象
*/
public Decorator(Component component) {
this.component = component;
}
public void operation() {
//转发请求给组件对象,可以在转发前后执行一些附加动作
component.operation();
}
}
public class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
private void operationFirst(){ } //在调用父类的operation方法之前需要执行的操作
private void operationLast(){ } //在调用父类的operation方法之后需要执行的操作
public void operation() {
//调用父类的方法,可以在调用前后执行一些增强功能
operationFirst(); //添加的功能
//这里可以选择性的调用父类的方法,如果不调用则相当于完全改写了方法,实现了新的功能
super.operation();
operationLast(); //添加的功能
}
}
public class Client{
public static void main(String[] args){
Component c1 = new ConcreteComponent (); //首先创建需要被装饰的原始对象(即要被装饰的对象)
Decorator decoratorA = new ConcreteDecoratorA(c1); //给对象透明的增加功能A并调用
decoratorA .operation();
Decorator decoratorB = new ConcreteDecoratorB(c1); //给对象透明的增加功能B并调用
decoratorB .operation();
//这里实现了自己装饰自己的功能,装饰器也可以装饰具体的装饰对象,此时相当于给对象在增加A的功能基础上在添加功能B
Decorator decoratorBandA = new ConcreteDecoratorB(decoratorA);
decoratorBandA.operation();
}
}
Java中的IO是明显的装饰器模式的运用。如FileInputStream是InputStream的一个子类,从文件中读取数据流,BufferedInputStream是继承自FilterInputStream的具体的装饰器类,该类提供一个内存的缓冲区类保存输入流中的数据。
BufferedInputStream bis = new BufferedInputStream(new
FileInputStream(file));
参考文献: