铁锤的IT奇妙探险 Java Back-End Coder

设计模式之适配器模式


介绍单例模式和适配器模式的原理和实现。

单例和适配器模式

单例模式

此模式保证某个类在运行期间,只有一个实例对外提供服务,而这个类被称为单例类,单例模式典型代表是 ErrorContext,每个线程中有一个此对象的单例,用于记录该线程的执行环境的错误信息。

public class ErrorContext {
  private static final String LINE_SEPARATOR = System.lineSeparator();
 
  //使用 private 修饰的 ThreadLocal 来保证每个线程拥有一个 ErrorContext 对象
  private static final ThreadLocal<ErrorContext> LOCAL = ThreadLocal.withInitial(ErrorContext::new);
  public static ErrorContext instance() {
    return LOCAL.get();
  }
  // 忽略其他
}

单例模式分为饿汉式和懒汉式

饿汉式

public class Singleton {
    // 声明私有对象
    private static Singleton instance = new Singleton();    
    // 获取实例(单例对象)
    public static Singleton getInstance() {
        return instance;
    }
    private Singleton() {
    }
    // 方法
    public void sayHi() {
        System.out.println("Hi,Java.");
    }
}
class SingletonTest {
    public static void main(String[] args) {
        // 调用单例对象
        Singleton singleton = Singleton.getInstance();
        // 调用方法
        singleton.sayHi();
    }
}

优点是线程安全,因为单例对象在类加载的时候就已经被初始化了,缺点是如果类加载了单例对象(对象被创建了),但是一直没有使用,会造成资源浪费。

懒汉式

public class Singleton {
    // 声明私有对象
    private static Singleton instance;
    // 获取实例(单例对象)
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
    private Singleton() {
    }
    // 方法
    public void sayHi() {
        System.out.println("Hi,Java.");
    }
}
class SingletonTest {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        singleton.sayHi();
    }
}

优点是不会造成资源的浪费,因为在调用的时候才会创建被实例化对象;它的缺点在多线程环境下是非线程是安全

DCL

我们需要既能保证线程安全,又能避免资源浪费的方式,采用双重检测锁

public class Singleton {
    
    //使用volatile关键字是因为new Singleton()非原子操作
    //需要防止CPU指令重排
    private volatile static Singleton instance;
    // 获取实例(单例对象)
    public static Singleton getInstance() {

        //双重检测,避免第一次判断后状态发生改变
        // 第一次判断
        if (instance == null) {

            //用synchronized修饰代码块取代修饰方法
            synchronized (Singleton.class) {
                // 第二次判断
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    private Singleton() {
    }
    // 类方法
    public void sayHi() {
        System.out.println("Hi,Java.");
    }
}

面试:为什么要 double-check?去掉任何一次的 check 行不行?”

  • 去掉第一次check,线程会串行执行
  • 去掉第二次话,现假设有两个线程同时调用 getInstance 方法,由于 singleton 是空的 ,因此两个线程都可以通过第一重的 if 判断;然后由于锁机制的存在,会有一个线程先进入同步语句,并进入第二重 if判断 ,而另外的一个线程就会在外面等待。不过,当第一个线程执行完 new Singleton() 语句后,就会退出 synchronized 保护的区域,这时如果没有第二重 if (singleton == null) 判断的话,那么第二个线程也会创建一个实例,此时就破坏了单例

为什么要用volatile?

  • instance = new Singleton()这一步操作实际上由三步,为了禁止指令重排造成结果错误,使用volatile关键字修饰Singleton。

静态内部类

public class Singleton {
    // 静态内部类
    private static class SingletonInstance {
        private static final Singleton instance = new Singleton();
    }
    // 获取实例(单例对象)
    public static Singleton getInstance() {
        return SingletonInstance.instance;
    }
    private Singleton() {
    }
    // 类方法
    public void sayHi() {
        System.out.println("Hi,Java.");
    }
}

静态内部类只有在调用 getInstance() 方法时,才会装载内部类从而完成实例的初始化工作,因此不会造成资源浪费的问题。当初始化实例时只有一个线程执行,从而保证了多线程下的安全操作。

枚举

public class Singleton {
    // 枚举类型是线程安全的,并且只会装载一次
    private enum SingletonEnum {
        INSTANCE;
        // 声明单例对象
        private final Singleton instance;
        // 实例化
        SingletonEnum() {
            instance = new Singleton();
        }
        private Singleton getInstance() {
            return instance;
        }
    }
    // 获取实例(单例对象)
    public static Singleton getInstance() {
        return SingletonEnum.INSTANCE.getInstance();
    }
    private Singleton() {
    }
    // 类方法
    public void sayHi() {
        System.out.println("Hi,Java.");
    }
}
class SingletonTest {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        singleton.sayHi();
    }
}

不仅是线程安全的,而且只会装载一次,无论是序列化、反序列化、反射还是克隆都不会新创建对象。

适配器模式

适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。

角色

  1. Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。
  2. Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心。
  3. Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法。

类适配器

//将要被适配的类
public class Adaptee {
    public void adapteeRequest() {
        System.out.println("被适配者的方法");
    }
}

//定义一个目标接口
public interface Target {
    void request();
}

//怎么才可以在目标接口中的 request() 调用 Adaptee 的 adapteeRequest() 方法呢?
public class Adapter extends Adaptee implements Target{
    @Override
    public void request() {
        //...一些操作...
        super.adapteeRequest();
    }
}
通过一个适配器类实现Target接口同时继承了Adaptee类然后在实现的request()方法中调用父类的 adapteeRequest()

Target adapterTarget = new Adapter();
adapterTarget.request();
//被适配者的方法

对象适配器

public class Adapter implements Target{
    // 对象适配器(Adapter)将适配者(Adaptee)作为一个属性,而不是继承它
    private Adaptee adaptee = new Adaptee();

    @Override
    public void request() {
        //...
        adaptee.adapteeRequest();
    }
}

应用

AOP的适配器模式
//适配器
class ThrowsAdviceAdapter implements AdvisorAdapter, Serializable {
	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof ThrowsAdvice);
	}
	//根据advice类型获取对应的拦截器
	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		return new ThrowsAdviceInterceptor(advisor.getAdvice());
	}
}


public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable {
    private final List<AdvisorAdapter> adapters = new ArrayList(3);

    public DefaultAdvisorAdapterRegistry() {
        // 这里注册了三个适配器
        this.registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
        this.registerAdvisorAdapter(new AfterReturningAdviceAdapter());
        this.registerAdvisorAdapter(new ThrowsAdviceAdapter());
    }
    
    public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
        List<MethodInterceptor> interceptors = new ArrayList(3);
        Advice advice = advisor.getAdvice();
        if (advice instanceof MethodInterceptor) {
            interceptors.add((MethodInterceptor)advice);
        }

        Iterator var4 = this.adapters.iterator();

        while(var4.hasNext()) {
            AdvisorAdapter adapter = (AdvisorAdapter)var4.next();
            if (adapter.supportsAdvice(advice)) {   // 这里调用适配器方法
                interceptors.add(adapter.getInterceptor(advisor));  // 这里调用适配器方法
            }
        }

        if (interceptors.isEmpty()) {
            throw new UnknownAdviceTypeException(advisor.getAdvice());
        } else {
            return (MethodInterceptor[])interceptors.toArray(new MethodInterceptor[0]);
        }
    }
    // ...省略...
}    

  • while 循环里,逐个取出注册的适配器,调用 supportsAdvice() 方法来判断 Advice 对应的类型,然后调用 getInterceptor() 创建对应类型的拦截器
  • Advisor:Pointcut和Advice的配置器,它包括Pointcut和Advice,是将Advice注入程序中Pointcut位置的代码
  • MethodInterceptor是Advice的子类,Spring使用MethodInterceptor来充当Advice

Similar Posts

Comments