单例模式

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

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

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

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

饿汉模式

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

1
2
3
4
5
6
7
8
public class SingletonHungry {
private static final SingletonHungry singletonHungry = new SingletonHungry();
private SingletonHungry(){}

public static SingletonHungry getSingletonHungry(){
return singletonHungry;
}
}

懒汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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,但是事已至此,实在是懒得改了,就先这样。

1
2
3
4
5
package Proxy;

public interface Method {
public void method();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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,当然为了演示简单起见,我们直接打印在控制台上了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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方法

1
2
3
4
5
package Decorate;

public interface MyMethod {
public void method();
}

然后是对它的实现

1
2
3
4
5
6
7
8
package Decorate;

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

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

1
2
3
4
package Decorate;

public interface MyMethodDecorate extends MyMethod{
}

然后实现装饰器1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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并调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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();
}
}
1
2
3
I decorate it with second decoration!
I decorate it with first decoration!
It is the origin method

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