什么是适配器模式
概念:适配器模式是指通过一个适配器的中间环节使得一个类的方法被包装为另一个类的方法。如图:
我们希望调用接口A的方法,当没有接口A提供,而只有与它相似的接口B可以使用,这时就需要使用到适配器。在工作中,可能需要维护一些比较老旧的代码,为了适应新的需求,且不去重写代码的情况下,就可以加上适配器这个中间环节,使得过时的代码满足客户的需求。适配器模式在Java中具体体现为方法的封装,令一个类实现接口B并实例化(被适配对象),再令适配器类实现接口A,在实例化适配器的时候把被适配对象传入。组织代码使得被适配对象的方法调用处于适配器的方法内部,最终实现所需要的功能,客户只是对适配器的方法进行调用,而实际调用的是被适配者的方法,客户是不知道这件事的,因为最终实现的功能和直接使用A类对象的效果是一致的。
可以理解为如下伪代码,表面上被调用的方法和实际调用的方法被适配器改变了。
A方法1(){
B方法1();
}
A方法2(){
B方法2();
}
A方法3(){
B方法3();
}
对象适配器和类适配器:
对象适配器:让接口B的子类对象被适配器(实现接口A)封装,对外提供接口A中的方法。
类适配器:把上述接口换为具体的类A和类B,其他相同。
比较分析:对象适配器的方法比较稳定,只能是接口A的方法,而类适配器可以有方法的重写。类适配器弹性好,对象适配器稳定性高,比较可靠。
写一个适配器(数组实现队列)
队列接口(明确了呈现给用户的方法):
public interface Queue <E>{
/**
* 将指定的元素插入此队列(如果立即可行且不会违反容量限制),在成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException。
* @return
*/
public abstract boolean add(E e);
/**
* 获取,但是不移除此队列的头。
* @return
*/
public abstract E element();
/**
* 将指定的元素插入此队列(如果立即可行且不会违反容量限制),当使用有容量限制的队列时,此方法通常要优于 add(E),后者可能无法插入元素,而只是抛出一个异常。
* @param e
* @return
*/
public abstract boolean offer(E e);
/**
* 获取但不移除此队列的头;如果此队列为空,则返回 null。
* @return
*/
public abstract E peek();
/**
* 获取并移除此队列的头,如果此队列为空,则返回 null。
* @return
*/
public abstract E poll();
/**
* 获取并移除此队列的头。
* @return
*/
public abstract E remove();
}
实现了接口的子类(适配器),ArrayList作为被适配的类:
public class MyQueue<E> implements Queue<E>{
private ArrayList<E> array;//被适配者
private int maxLength;
public MyQueue(ArrayList<E> array,int maxLength){
this.array=array;
this.maxLength=maxLength;
}
/**
* 将指定的元素插入此队列(如果立即可行且不会违反容量限制),在成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException。
* @return
*/
public boolean add(E e){
if(array.size()<maxLength){
array.add(e);
return true;
}else{
throw new IllegalStateException("Queue full");
}
}
/**
* 获取,但是不移除此队列的头。
* @return
*/
public E element() {
if(array.size()>0)
return array.get(0);
else
throw new IllegalStateException("Queue is empty");
}
/**
* 将指定的元素插入此队列(如果立即可行且不会违反容量限制),当使用有容量限制的队列时,此方法通常要优于 add(E),后者可能无法插入元素,而只是抛出一个异常。
* @param e
* @return
*/
public boolean offer(E e) {
if(array.size()<maxLength){
array.add(e);
return true;
}else{
return false;
}
}
/**
* 获取但不移除此队列的头;如果此队列为空,则返回 null。
* @return
*/
public E peek() {
if(array.size()>0)
return array.get(0);
else
return null;
}
/**
* 获取并移除此队列的头,如果此队列为空,则返回 null。
* @return
*/
public E poll() {
if(array.size()>0)
return array.remove(0);
else
return null;
}
/**
* 获取并移除此队列的头。
* @return
*/
public E remove() {
if(array.get(0)!=null)
return array.remove(0);
else
throw new IllegalStateException("Queue is empty");
}
}
测试类:
public static void main(String[] args){
ArrayList<String> array=new ArrayList<String>();
MyQueue<String> mq=new MyQueue<>(array, 20);
mq.add("123");
mq.add("222");
System.out.println(mq.element());//获取但是不移除
mq.remove();//获取并移除此队列的头,为空抛异常。
mq.remove();
System.out.println(mq.peek());
mq.peek();//获取但不移除此队列的头;如果此队列为空,则返回 null
mq.add("555");
System.out.println(mq.poll());//获取并移除此队列的头,如果此队列为空,则返回 null
System.out.println(mq.poll());
System.out.println(mq.offer("456"));//将指定的元素插入此队列。
System.out.println(mq.element());//获取,但是不移除此队列的头。
}
输出:
队列的几个方法分析比较:
add()和offer()方法比较:add在队列慢满时会抛出异常,而offer在队列满时只是返回一个false。
remove()和poll()方法比较:两种都是获取并移除首元素,remove()方法在队列为空时会抛出异常,而poll()方法在队列对空时会返回一个null。
peek()和poll():peek()获取首元素后并不会移除它,其他两者相同。
适配器模式和装饰者模式的区别:适配器模式经常和装饰者模式比较,它们的区别是适配器模式只涉及到一层引用的包装且适配器的适配对象是处于同一类型(层次)的类;装饰者模式涉及到了多层的包装,多层的引用传递,在层之间代码逻辑比较简单。
外观模式的概念及其和适配器模式的区别:外观模式是指对子系统的一些方法进行封装,得到一个方法供客户调用,它的好处就是调用方便,可以形成多种对外接口,同时也不影响直接对子系统进行访问,故而外观模式可以有多种外观,复杂功能被封装为简单的方法。它和适配器模式的一个区别是外观模式是针对不同层次的代码的封装,而适配器是指让一个对象实现和它相似的对象的功能。