适配器模式与Java应用

适配器模式与Java应用

​ 在设计模式中或者说在很多编程场景下经常会使用到适配器,有心或无意。顾名思义,适配器就是将一个类的接口适配(包装/转换)成客户(调用者)希望的另一个接口。适配模式又可以细分为两类:

  • 对象适配器
  • 类适配器

​ 对象适配器和类适配器的区别在于适配器(Adapter)与被适配者(Adaptee)的关系,对象适配器与被适配者关联(成员变量),或者称为委托(方法调用),而类适配器通过继承被适配者来实现。实现细节不同,但最终的目的和效果是一样的,下面以对象适配器为例来进一步认识这个设计模式。


1、代码模型

// 需要被适配的类(已存在的、存在特殊功能但不符合既有的标准接口的类)
class Adaptee {
  public void specialMethod() {
    System.out.println("Invoking specialMethod.");
  }
}
// 目标接口
interface Target {
  public void method1();
  public void method2();
  public void method3();
}
// 适配器类
class Adapter implements Target {
  // 直接关联被适配类
  private Adaptee adaptee;

  // 通过通过构造方法传入需要被适配的对象
  public Adapter(Adaptee adaptee) {
    this.adaptee = adaptee;
  }

  // 通过委托的方式调用被适配对象的特殊方法
  public void method1() {
    this.adaptee.specialMethod();
  }

  public void method2() {
    System.out.println("Invoking method2.");
  }

  public void method3() {
    System.out.println("Invoking method3.");
  }
}

2、JDK中的应用

  • java.io.InputStreamReader(InputStream)
  • java.io.OutputStreamWriter(OutputStream) 
  • java.util.Collections#synchronizedCollection()
  • java.util.Arrays#asList()

InputStreamReader类部分源码:

public class InputStreamReader extends Reader {
  private final StreamDecoder sd;

  public InputStreamReader(InputStream in) {
    super(in);
    try {
      sd = StreamDecoder.forInputStreamReader(in, this, (String)null);
    } catch (UnsupportedEncodingException e) {
      throw new Error(e);
    }
  }

  public int read() throws IOException {
    return sd.read();
  }

  public int read(char cbuf[], int offset, int length) throws IOException {
    return sd.read(cbuf, offset, length);
  }
}

​ InputStreamReader是字节流转换为字符流的桥梁(适配器)。其中,Reader对应Target(接口或抽象类),InputStreamReader对应Adapter,InputStream对应Adaptee。

再看看java.util.Collections#synchronizedCollection()的相关实现(部分源码):

public class Collections {
  public static <T> Collection<T> synchronizedCollection(Collection<T> c) {
    return new SynchronizedCollection<>(c);
  }

  static class SynchronizedCollection<E> implements Collection<E>, Serializable {
    private static final long serialVersionUID = 3053995032091335093L;

    final Collection<E> c;  // Backing Collection
    final Object mutex;     // Object on which to synchronize

    SynchronizedCollection(Collection<E> c) {
      this.c = Objects.requireNonNull(c);
      mutex = this;
    }

    SynchronizedCollection(Collection<E> c, Object mutex) {
      this.c = Objects.requireNonNull(c);
      this.mutex = Objects.requireNonNull(mutex);
    }

    public int size() {
      synchronized (mutex) {return c.size();}
    }
    public boolean isEmpty() {
      synchronized (mutex) {return c.isEmpty();}
    }
    public boolean contains(Object o) {
      synchronized (mutex) {return c.contains(o);}
    }
    public Object[] toArray() {
      synchronized (mutex) {return c.toArray();}
    }
    public <T> T[] toArray(T[] a) {
      synchronized (mutex) {return c.toArray(a);}
    }

    public Iterator<E> iterator() {
      return c.iterator(); // Must be manually synched by user!
    }

    public boolean add(E e) {
      synchronized (mutex) {return c.add(e);}
    }
    public boolean remove(Object o) {
      synchronized (mutex) {return c.remove(o);}
    }

    public boolean containsAll(Collection<?> coll) {
      synchronized (mutex) {return c.containsAll(coll);}
    }
  }
}

​ Java中常用的集合框架中的实现类HashSet、TreeSet、ArrayList、ArrayDeque、LinkedList、HashMap、TreeMap都是线程不安全的,如果有多个线程同时访问它们,且同时有多个线程修改他们的时候,将会出现如读脏数据等错误。Collections给出了解决方案,提供了synchronizedCollection方法来实现线程安全,该方法返回一个线程安全容器,可以理解为适配器。从源码中可以看到,SynchronizedCollection关联现有的collection对象,通过委托的方式调用现有对象的方法,只做了synchronized同步处理。


3、思考和总结

​ 之前在维护一些旧程序时,发现线程安全问题导致程序异常、数据串行,原因是依赖的类库中某个类是非线程安全的,而应用中使用了多线程并发调用了这个类的方法。由于一些原因,无法改动依赖的类库。遇到这种问题,可以有两种方法解决:

  • 同步应用代码块,在调用类库方法的地方加锁。
  • 参考java.util.Collections#synchronizedCollection(),通过委托的方式同步调用类库的方法。

​ 很明显,第二种方法(应用适配器模式)会更好。对于原有代码的改动最小或者几乎没有,只需要实现一个适配器(新增一个类),即可快速优雅的解决问题。

​ 设计模式,归根究底就是一些经验总结,经过了反复使用和验证。还是学生的时候,就把二十多种设计模式看了一遍,当时觉得每个模式都讲的好有道理,好像很厉害,但现在想来,还真的就只是看了而已。后来,参加工作,写过和读过的代码越来越多,才发现能够应用才是比较重要的。例如上述的源码和解决方案,通过适配器,代码简洁优雅,清晰明了。学习,理解和应用,应当是一个持续和连贯的过程。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值