设计模式整理

8 min

单例模式

单例模式是设计模式中最简单的一种设计模式,常用于数据库连接池、Spring默认的bean等

单例模式是一种创建型模型,这个词我们后面还会提到。

一个类负责创建自己的对象,但是需要确保只有一个对象被创建了,这个类提供了一种访问它对象的唯一方式。同时需要隐藏自己的构造方法。

一般单例模式都是支持两种实现思路,一种是饿汉式,一种是懒汉式,饿汉式就是在类加载时就实例化一个对象,而懒汉式则是需要第一次使用的时候才实例化对象,但是懒汉式的实现方式一般需要通过双重校验也就是double cheack的方式来实现。

饿汉模式

饿汉式的实现是比较简单的,也就是在类加载的时候就实例化对象,但是存在一个问题,这个类加载了但是我没有使用,会有额外的空间浪费。

public class SingletonHungry {
    private static final SingletonHungry singletonHungry = new SingletonHungry();
    private SingletonHungry(){}
    
    public static SingletonHungry getSingletonHungry(){
        return singletonHungry;
    }
}

懒汉模式

public class Singleton {
    /**
     * 该成员变量也就是唯一实例化的对象
     */
    private static volatile Singleton singleton;

    /**
     * 同时为了防止被初始化,需要将构造函数私有化
     */
    private Singleton(){}

    /**
     * 提供静态方法用来获取唯一单例
     * @return 单例对象
     */
    public static Singleton getSingleton(){
        if(singleton==null){
            /*这里需要使用类级别的锁而不是直接锁住singleton的原因是
            *如果singleton==null,那么就会抛出NullPointerException
            *异常原因是:Cannot enter synchronized block because 			
            *"Singleton.singleton" is null
            */
            synchronized (Singleton.class){
                if (singleton==null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getSingleton();
        Singleton singleton2 = Singleton.getSingleton();
        System.out.println(singleton1==singleton2);
    }
}

代理模式

代理模式的核心是,允许通过代理对象控制对被代理对象的访问。主要用于在访问对象时添加额外的功能。

代理模式在Java中可以分为静态代理和动态代理,本质上的目的都是一致的。

只不过静态代理需要手动编写代理类,代理类需要实现与目标相同的接口,在内部需要持有一个目标对象的引用。

动态代理则是运行时动态生成的,不需要为每个对象手动编写代理类,但是目标对象至少必须实现一个接口。

接下来我们就来尝试以对方法加log的任务为例,实现两种代理。

共用代码

两种代码都需要有一个接口和对接口的实现,所以在这里定义一个接口和并给出它的一个实现。

其实写到动态代理那块的时候,我才意识到这个Method接口的取名并不太妙,因为Java反射包里就有一个类叫Method,但是事已至此,实在是懒得改了,就先这样。

package Proxy;

public interface Method {
    public void method();
}
package Proxy;

import java.util.concurrent.TimeUnit;

public class MethodImpl implements Method{
    @Override
    public void method() {
        System.out.println("Dealing with method");
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("Finished dealing");
    }
}

静态带来

这时候我们就实现一个方法,来代理这个MethodImpl,目的是要给它加上执行时间的log,当然为了演示简单起见,我们直接打印在控制台上了

package Proxy;

import java.util.Date;

public class StaticProxy implements Method{
    private Method target;

    public StaticProxy(Method target){
        this.target = target;
    }

    @Override
    public void method() {
        Date dateStart = new Date();
        target.method();
        Date dateFinished = new Date();
        long timeCost = dateFinished.getTime()-dateStart.getTime();
        System.out.println("Cost time:"+timeCost);
    }

    public static void main(String[] args) {
        Method staticProxy = new StaticProxy(new MethodImpl());
        staticProxy.method();
    }
}

静态代理其实存在一个问题,灵活性较低,因为每当需要为一个新的服务类型提供代理时,都需要手动去编写相应的代理类。

但是Java为我们提供了动态代理类的实现方式。

动态代理

动态代理的实现其实是依赖Java反射包下的Proxy类的newProxyInstance()方法,需要实现一个InvocationHandler接口,并重写invoke方法

package Proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Date;

public class MyDynamicProxy {

    static class MyDynamicProxyHandler implements InvocationHandler{
        Object target;
        public MyDynamicProxyHandler(Object target){
            this.target = target;
        }
        @Override
        public Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args) throws Throwable {
            Date dateStart = new Date();
            Object res = method.invoke(target,args);
            Date dateFinished = new Date();
            long timeCost = dateFinished.getTime()-dateStart.getTime();
            System.out.println("Cost time:"+timeCost);
            return res;
        }
    }

    public static Object getProxy(Object method){
        return Proxy.newProxyInstance(
                method.getClass().getClassLoader(), method.getClass().getInterfaces(),new MyDynamicProxyHandler(method)
        );
    }

    public static void main(String[] args) {
        Method staticProxy = (Method) MyDynamicProxy.getProxy(new MethodImpl());
        staticProxy.method();
    }
}

装饰者模式

装饰者模式与代理模式看起来似乎有一点相似,但是两者的侧重点不同。

  • 代理模式更侧重于控制对对象的访问,可能包括权限验证、日志记录、延迟加载等功能,而不会改变原有对象的行为。
  • 装饰者模式则专注于在不改变对象接口的前提下,动态地为其添加新的行为或功能,更加注重功能的扩展性和灵活性。

我们做一个简单的实现就可以理解了。

首先还是定义一个接口,我希望它能提供method方法

package Decorate;

public interface MyMethod {
    public void method();
}

然后是对它的实现

package Decorate;

public class OriginMethod implements MyMethod{
    @Override
    public void method() {
        System.out.println("It is the origin method");
    }
}

接下来我们要实现装饰器,首先是装饰器要继承原始接口

package Decorate;

public interface MyMethodDecorate extends MyMethod{
}

然后实现装饰器1

package Decorate;

public class DecorateMethod implements MyMethodDecorate{
    MyMethod originMethod;

    public DecorateMethod(MyMethod originMethod){
        this.originMethod = originMethod;
    }

    @Override
    public void method() {
        System.out.println("I decorate it with first decoration!");
        originMethod.method();
    }
}

然后实现装饰器2并调用

package Decorate;

public class DecorateMethod2 implements MyMethodDecorate{
    MyMethod originMethod;

    public DecorateMethod2(MyMethod originMethod){
        this.originMethod = originMethod;
    }

    @Override
    public void method() {
        System.out.println("I decorate it with second decoration!");
        originMethod.method();
    }

    public static void main(String[] args) {
        MyMethod originMethod = new OriginMethod();
        originMethod = new DecorateMethod(originMethod);
        originMethod = new DecorateMethod2(originMethod);
        originMethod.method();
    }
}
I decorate it with second decoration!
I decorate it with first decoration!
It is the origin method

装饰器模式和代理模式的核心差距就在于,装饰器把它装饰成一个功能更多的,更加多样化的内容,而代理模式的核心是控制对代理方法的访问。