设计模式之适配器模式 _ JAVA
适配器模式对于大家来说算是比较常见的用的比较多的模式了,主要功能是将类的接口转换为客户期望的另一个接口,使用适配器避免了由于接口不兼容而无法一起工作
在软件工程中,适配器模式是一种软件设计模式,它允许将现有类的接口用作另一个接口。它通常用于使现有类在不修改源代码的情况下与其他类一起工作。
一些现实生活中的例子
存储卡上有一些图片,你需要把它们传输到电脑上面,为了传输它们,你需要某种与计算机端口兼容的适配器,以便可以将存储卡附加到计算机上
电源适配器,一个三角插头不能连接到一个双孔插座上,它需要使用一个电源适配器,使它与双孔插座兼容
另一个例子是翻译人员将一个人说的话翻译给另一个人
适配器模式有三种类型
类适配器实现适配器的接口,Adapter 类继承 Adaptee(被适配类),同时实现Target 接口,根据需要选择并创建任意一种符合需求的子类,来实现具体功能;
对象适配器不使用继承的方式而是使用直接关联,或者称为委托的方式;
接口适配器的核心在于中间适配器,用于实现默认的接口方法,由子类来决定使用哪些方法,让代码更加清晰整洁;
对象适配器
对象适配器的重点在于对象,是通过在直接包含Adaptee类来实现的,当需要调用特殊功能的时候直接使用Adapter中包含的那个Adaptee对象来调用特殊功能的方法即可。
一个只会使用划艇而不会操作渔船的船长
首先我们有接口 划艇(RowingBoat) 和 渔船(FishingBoat)
public interface RowingBoat { void row(); }
public class FishingBoat { private static final Logger LOGGER = LoggerFactory.getLogger(FishingBoat.class); public void sail() { LOGGER.info("The fishing boat is sailing"); } }
船长类实现 划艇(RowingBoat) 接口,使其能够移动
public class Captain implements RowingBoat { private RowingBoat rowingBoat; public Captain(RowingBoat rowingBoat) { this.rowingBoat = rowingBoat; } @Override public void row() { rowingBoat.row(); } }
现在假设海盗来了,我们的船长需要逃跑,但是只有渔船可以使用,我们需要创建一个适配器来允许船长使用它的划艇技能操作渔船
public class FishingBoatAdapter implements RowingBoat { private static final Logger LOGGER = LoggerFactory.getLogger(FishingBoatAdapter.class); private FishingBoat boat; public FishingBoatAdapter() { boat = new FishingBoat(); } @Override public void row() { boat.sail(); } }
现在 船长可以用渔船来躲避海盗了
Captain captain = new Captain(new FishingBoatAdapter()); captain.row();
类适配器示
类适配器的重点在于类,是通过构造一个继承Adaptee类来实现适配器的功能; 因为Java不支持多继承,所以只能通过接口的方法来实现多继承
// 标准接口 public interface Target { void play(); }
// 已实现play的功能 public class RowingBoat implements Target { private static final Logger LOGGER = LoggerFactory.getLogger(RowingBoat.class); @Override public void play() { LOGGER.info("滑动划艇...."); } }
// 新的功能,摩托艇 public class Motorboat { private static final Logger LOGGER = LoggerFactory.getLogger(Motorboat.class); public void play() { LOGGER.info("驾驶摩托艇...."); } }
适配器类,继承自Motorboat,并调用父类的play方法。配合标准接口实现功能转换
public class Adapter extends Motorboat implements Target{ public Adapter() { } public void play() { super.play(); } }
public class App { public static void main(String[] args) { Target target1 = new RowingBoat(); target1.play(); Target target = new Adapter(); target.play(); } }
接口适配器示例
接口适配器应用场景,假设Target接口中有很多待实现的方法。
public interface Target { void motorboat(); void rowingBoat(); void fishingBoat(); // ...... }
一般我们实现Target接口,默认需要实现接口中所有的方法。即使是我们用不到的。
使用接口适配器可以很好的解决这个问题。定义一个抽象接口类对Target接口的所有方法默认空实现
public abstract class Adapter implements Target { @Override public void motorboat() { } @Override public void rowingBoat() { } @Override public void fishingBoat() { } public void run(){ motorboat(); rowingBoat(); fishingBoat(); } }
子类继承适配器类只需要覆盖需要实现的方法
public class Assembly1 extends Adapter { private static final Logger LOGGER = LoggerFactory.getLogger(Assembly1.class); @Override public void fishingBoat() { LOGGER.info("驾驶渔船..."); } }
复写另外一组方法
public class Assembly2 extends Adapter { private static final Logger LOGGER = LoggerFactory.getLogger(Assembly2.class); @Override public void motorboat() { LOGGER.info("驾驶摩托艇...."); } @Override public void rowingBoat() { LOGGER.info("滑动划艇...."); } }
public class App { public static void main(String[] args) { Adapter a1 = new Assembly1(); a1.run(); Adapter a2 = new Assembly2(); a2.run(); } }
适配器模式的优缺点优
优点
将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无需修改原有结构。
增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一适配者类可以在多个不同的系统中复用。
灵活性和扩展性都非常好,通过使用配置文件,可以很方便的更换适配器,也可以在不修改原有代码的基础上 增加新的适配器,完全复合开闭原则。
缺点
一次最多只能适配一个适配者类,不能同时适配多个适配者。
目标抽象类只能为接口,不能为类,其使用有一定的局限性。
适配者类不能为最终类
适配器模式的实际应用
在SpringMVC中有一些不同类型的controller,并且实现方式也不一样,由于实现方式不一样调用的方式也不一样,如果直接调用controller方法,那么代码应该写成这样
if(mappedHandler.getHandler() instanceof MultiActionController){ ((MultiActionController)mappedHandler.getHandler()).xxx }else if(mappedHandler.getHandler() instanceof XXX){ ... }else if(...){ ... } ...
这样假设如果我们增加一个HardController,就要在代码中加入一行 if (mappedHandler.getHandler() instanceof HardController) 这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 — 对扩展开放,对修改关闭。
因此Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类,让适配器代替controller执行相应的方法。这样在扩展Controller 时,只需要增加一个适配器类就完成了SpringMVC的扩展了,真的是很精巧的做法!
废话不多说还是上代码吧,为了看得清楚,就自己实现一套代码来模拟springMVC, 直接贴Spring源码容易降低关注点。
// 定义一个Adapter接口 public interface HandlerAdapter { public boolean supports(Object handler); public void handle(Object handler); }
// 以下是三种Controller实现 public interface Controller { } public class HttpController implements Controller{ public void doHttpHandler(){ System.out.println("http..."); } } public class SimpleController implements Controller{ public void doSimplerHandler(){ System.out.println("simple..."); } } public class AnnotationController implements Controller{ public void doAnnotationHandler(){ System.out.println("annotation..."); } }
// 下面编写适配器类 public class SimpleHandlerAdapter implements HandlerAdapter { public void handle(Object handler) { ((SimpleController)handler).doSimplerHandler(); } public boolean supports(Object handler) { return (handler instanceof SimpleController); } } public class HttpHandlerAdapter implements HandlerAdapter { public void handle(Object handler) { ((HttpController)handler).doHttpHandler(); } public boolean supports(Object handler) { return (handler instanceof HttpController); } } public class AnnotationHandlerAdapter implements HandlerAdapter { public void handle(Object handler) { ((AnnotationController)handler).doAnnotationHandler(); } public boolean supports(Object handler) { return (handler instanceof AnnotationController); } }
//模拟一个DispatcherServlet public class DispatchServlet { public static ListhandlerAdapters = new ArrayList(); public DispatchServlet(){ handlerAdapters.add(new AnnotationHandlerAdapter()); handlerAdapters.add(new HttpHandlerAdapter()); handlerAdapters.add(new SimpleHandlerAdapter()); } public void doDispatch(){ // 此处模拟SpringMVC从request取handler的对象 // 不论实现何种Controller,适配器总能通过适配得到想要的结果 // HttpController controller = new HttpController(); // AnnotationController controller = new AnnotationController(); SimpleController controller = new SimpleController(); //得到对应适配器 HandlerAdapter adapter = getHandler(controller); //通过适配器执行对应的controller对应方法 adapter.handle(controller); } public HandlerAdapter getHandler(Controller controller){ for(HandlerAdapter adapter: this.handlerAdapters){ if(adapter.supports(controller)){ return adapter; } } return null; } public static void main(String[] args){ new DispatchServlet().doDispatch(); } }
使用了适配器模式的地方还有许多,比如Java中的Arrays.asList()、Collections.list() 等等。感兴趣的小伙伴可以了解一下。