Java常见设计模式(中)


前言

设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解,本文主要列举了常用的几种设计模式(中篇)。

代理模式

代理模式的定义与特点

代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

代理模式的主要优点有:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

其主要缺点是:

  • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
  • 增加了系统的复杂度;

代理模式的结构与实现

代理模式的结构比较简单,主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问,下面来分析其基本结构和实现方法。

1. 模式的结构

代理模式的主要角色如下。

  1. 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  2. 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  3. 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能

代理模式结构图

2. 模式的实现

代理模式的实现代码如下:

抽象主题
interface Subject
{
    void Request();
}
//真实主题
class RealSubject implements Subject
{
    public void Request()
    {
        System.out.println("访问真实主题方法...");
    }
}
//代理
class Proxy implements Subject
{
    private RealSubject realSubject;
    public void Request()
    {
        if (realSubject==null)
        {
            realSubject=new RealSubject();
        }
        preRequest();
        realSubject.Request();
        postRequest();
    }
    public void preRequest()
    {
        System.out.println("访问真实主题之前的预处理。");
    }
    public void postRequest()
    {
        System.out.println("访问真实主题之后的后续处理。");
    }
}
   public static void main(String[] args)
    {
        Proxy proxy=new Proxy();
        proxy.Request();
    }

结果:

访问真实主题之前的预处理。
访问真实主题方法...
访问真实主题之后的后续处理。

代理模式的应用实例

interface Manager {
    void doSomething();
}



class Admin implements Manager{

    @Override
    public void doSomething() {
        System.out.println("来自Admin方法doSomething!");
    }

}

class AdminProxy implements Manager{

    Admin admin;

    public AdminProxy(Admin admin){
        this.admin = admin;
    }

    @Override
    public void doSomething() {
        System.out.println("----开始了----");
        admin.doSomething();
        System.out.println("----结束了----");
    }

}

public static void main(String[] args) {
        AdminProxy ap = new AdminProxy(new Admin());
        ap.doSomething();

    }

结果:

----开始了----
来自Admin方法doSomething!
----结束了----

动态代理

代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中,代理类(Proxy)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。

JDK动态代理

代理步骤:

(1)定义一个事件管理器类实现InvocationHandler接口,并重写invoke(代理类,被代理的方法,方法的参数列表)方法。
(2)实现被代理类及其实现的接口,
(3)调用Proxy.newProxyInstance(类加载器,类实现的接口,事务处理器对象);生成一个代理实例。
(4)通过该代理实例调用方法。

实现代码:

//1. 抽象主题  
public interface Moveable {  
    void move()  throws Exception;  
}  
//2. 真实主题  
public class Car implements Moveable {  
    public void move() throws Exception {  
        Thread.sleep(new Random().nextInt(1000));  
        System.out.println("汽车行进中…");  
    }  
}


//3.事务处理器  
public class TimeHandler implements InvocationHandler {  
    private Object target;  

    public TimeHandler(Object target) {  
        super();  
        this.target = target;  
    }  

    /** 
     * 参数: 
     *proxy 被代理的对象 
     *method 被代理对象的方法 
     *args 方法的参数  
     *Object 方法返回值 
     */  
    public Object invoke(Object proxy, Method method, Object[] args)  
            throws Throwable {  
        long startTime = System.currentTimeMillis();  
        System.out.println("汽车开始行进…");  
        method.invoke(target, args);  
        long stopTime = System.currentTimeMillis();  
        System.out.println("汽车结束行进…汽车行驶时间:" + (stopTime - startTime) + "毫秒!");  
        return null;  
    }  

}  

    public static void main(String[] args) throws Exception {
        Car car=new Car();
        InvocationHandler h=new TimeHandler(car);
        Moveable moveable=(Moveable)Proxy.newProxyInstance(car.getClass().getClassLoader(),car.getClass().getInterfaces(),h);
        moveable.move();
    }

结果:

汽车开始行进…
汽车行进中…
汽车结束行进…汽车行驶时间:19毫秒!

在测试代码中,Proxy.newProxyInstance()方法需要3个参数:类加载器(要进行代理的类)、被代理类实现的接口,事务处理器。所以先实例化Car,实例化InvocationHandler的子类TimeHandler,将各参数传入Proxy的静态方法newProxyInstance()即可获得Car的代理类,前面的静态代理,代理类是我们编写好的,而动态代理则不需要我们去编写代理类,是在程序中动态生成的。

cglib动态代理

Java只允许单继承,而JDK生成的代理类本身就继承了Proxy类,因此,使用JDK实现的动态代理不能完成继承式的动态代理,但是我们可以使用cglib来实现继承式的动态代理。

public class Train {
    public void move(){
        System.out.println("火车行驶中…");
    }
}

public class CGLibProxy implements MethodInterceptor {

    private  Object target;

    public Object getProxy(Object target)
    {
        this.target=target;
        Enhancer enhancer=new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
       return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //代理类调用父类的方法
        System.out.println("日志开始");
        methodProxy.invokeSuper(o,objects);
        System.out.println("日志结束");
        return null;
    }
}

    public static void main(String[] args) {

      CGLibProxy proxy=new CGLibProxy();
        Train res = (Train) proxy.getProxy(new Train());
        res.move();
    }

结果:

日志开始
火车行驶中…
日志结束

分析:

动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理。在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样对每一个方法或方法组合进行处理。Proxy 确实很强大,但是仅支持 interface 代理。Java 的单继承机制注定了这些动态代理类们无法实现对 class 的动态代理。好在有cglib为Proxy提供了弥补,它允许类在不实现接口的前提下而实现动态代理。

装饰模式

装饰模式的定义与特点

装饰(Decorator)模式的定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。

装饰(Decorator)模式的主要优点有:

  • 采用装饰模式扩展对象的功能比采用继承方式更加灵活。
  • 可以设计出多个不同的具体装饰类,创造出多个不同行为的组合。

其主要缺点是:装饰模式增加了许多子类,如果过度使用会使程序变得很复杂。

装饰模式的结构与实现

通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰模式的目标。下面来分析其基本结构和实现方法。

1. 模式的结构

装饰模式主要包含以下角色。

  1. 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象。
  2. 具体构件(Concrete Component)角色:实现抽象构件,通过装饰角色为其添加一些职责。
  3. 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
  4. 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。

2. 模式的实现

装饰模式的实现代码如下:

//抽象构件角色
interface  Component
{
    public void operation();
}

//具体构件角色
class ConcreteComponent implements Component
{
    public ConcreteComponent()
    {
        System.out.println("创建具体构件角色");       
    }   
    public void operation()
    {
        System.out.println("调用具体构件角色的方法operation()");           
    }
}

//抽象装饰角色
class Decorator implements Component
{
    private Component component;   
    public Decorator(Component component)
    {
        this.component=component;
    }   
    public void operation()
    {
        component.operation();
    }
}

//具体装饰角色
class ConcreteDecorator extends Decorator
{
    public ConcreteDecorator(Component component)
    {
        super(component);
    }   
    public void operation()
    {
        super.operation();
        addedFunction();
    }
    public void addedFunction()
    {
        System.out.println("为具体构件角色增加额外的功能addedFunction()");           
    }
}

  public static void main(String[] args)
    {
        Component p=new ConcreteComponent();
        p.operation();
        System.out.println("---------------------------------");
        Component d=new ConcreteDecorator(p);
        d.operation();
    }

结果:

创建具体构件角色
调用具体构件角色的方法operation()
---------------------------------
调用具体构件角色的方法operation()
为具体构件角色增加额外的功能addedFunction()

装饰模式的应用实例

首先定义一个接口Human

public interface Human {
    public void run();
}

其次定义一个被装饰的类Man

public class Man implements Human {
    @Override
    public void run() {
        System.out.println("人会跑步");
    }
}

然后定义一个装饰的抽象类


public abstract class AbstractDecorator implements Human{
    //持有被装饰类的引用 
    private Human human;

    //构造函数注入被装饰者
    public AbstractDecorator(Human human) {
        this.human = human;
    }

    //调用被装饰类的方法
    @Override
    public void run() {
        human.run();
    }
}

最后定义一个装饰的实现类


public class ManDecorator extends AbstractDecorator {
    public ManDecorator(Human human) {
        //调用父类的构造方法
        super(human);
    }

    //装饰类增加的功能
    private void fly() {
        System.out.println("人也许飞哦");
    }

    //增强了功能的run方法
    @Override
    public void run() {
        super.run();
        fly();
    }
}

主方法:

public static void main(String[] args) {
        //创建被装饰的类
        Human human = new Man();

        //创建装饰的类,并添加被装饰类的引用
        Human superMan = new ManDecorator(human);

        //执行增强后的run方法
        superMan.run();
    }

结果:

人会跑步
人也许飞哦

装饰模式的应用场景

  • 当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
  • 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰模式却很好实现。
  • 当对象的功能要求可以动态地添加,也可以再动态地撤销时。

装饰模式在 Java语言中的最著名的应用莫过于 Java I/O 标准库的设计了。例如,InputStream 的子类 FilterInputStream,OutputStream 的子类 FilterOutputStream,Reader 的子类 BufferedReader 以及 FilterReader,还有 Writer 的子类 BufferedWriter、FilterWriter 以及 PrintWriter 等,它们都是抽象装饰类。

例如:为 FileReader 增加缓冲区而采用的装饰类 BufferedReader

BufferedReader in= new BufferedReader(new FileReader("filename.txt"));
String s=in.readLine();

文章作者: jackey
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 jackey !
评论
  目录