Java面试——设计模式(二)

设计模式

7、适配器模式的概念及Java实现

我们常常在开发中遇到各个系统之间的对接问题,然而每个系统的数据模型或多或少均存在差别,因此可能存在修改现有对象模型的情况,这将影响到系统的稳定。若想在不修改原有代码结构(类的结构)的情况下完成友好对接,就需要用到适配器模式。

适配器模式(Adapter Pattern)通过定义一个适配器类作为两个不兼容的接口之间的桥梁,将一个类的接口转换成用户期望的另一个接口,使得两个或多个原本不兼容的接口可以基于适配器类一起工作。

适配器模式主要通过适配器类实现各个接口之间的兼容,该类通过依赖注入或者继承实现各个接口的功能并对外统一提供服务,可形象地使用图来表示适配器模式。

在适配器模式的实现中有三种角色:Source、Targetable、Adapter。Source是待适配的类,Targetable是目标接口,Adapter是适配器。我们在具体应用中通过Adapter将Source的功能扩展到Targetable,以实现接口的兼容。适配器的实现主要分为三类:类适配器模式、对象适配器模式、接口适配器模式。

7.1、类适配器模式

在需要不改变(或者由于项目原因无法改变)原有接口或类结构的情况下扩展类的功能以适配不同的接口时,可以使用类的适配器模式。适配器模式通过创建一个继承原有类(需要扩展的类)并实现新接口的适配器类来实现。具体的UML设计如图所示。

具体实现如下。

(1)定义Source类:

java 复制代码
public  class  Source  {
    private  final  static  Log  logger  =  LogFactory.getLog(Source.class);
    public void editTextFile() {//text文件编辑
      logger.info("a  text  file  editing");
    }
}

以上代码定义了待适配的Source类,在该类中实现了一个编辑文本文件的方法editTextFile()。

(2)定义Targetable接口:

java 复制代码
public  interface  Targetable  {
    void  editTextFile();
    void  editWordFile();
}

以上代码定义了一个Targetable接口,在该接口中定义了两个方法,editTextFile和editWordFile,其中editTextFile是Source中待适配的方法。

(3)定义Adapter继承Source类并实现Targetable接口:

java 复制代码
public  class  Adapter  extends  Source  implements  Targetable{
    private  final  static  Log  logger  =  LogFactory.getLog(Adapter.class);
    @Override
    public  void  editWordFile()  {
      logger.info("a  word  file  editing");
    }
}

以上代码定义了一个Adapter类并继承了Source类实现Targetable接口,以完成对Source类的适配。适配后的类既可以编辑文本文件,也可以编辑Word文件。

(4)使用类的适配器:

java 复制代码
public  static  void  main(String[]  args)  {
      Targetable  target  =  new  Adapter();
      target.editTextFile();
      target.editWordFile();
  }

在使用适配器时只需定义一个实现了Targetable接口的Adapter类并调用target中适配好的方法即可。从运行结果可以看出,我们的适配器不但实现了编辑Word文件的功能,还实现了编辑文本文件的功能,具体的执行结果如下:

java 复制代码
[INFO]  Source  -  a  text  file  editing
[INFO]  Adapter  -  a  word  file  editing

7.2、对象适配器模式

对象适配器模式的思路和类适配器模式基本相同,只是修改了Adapter类。Adapter不再继承Source类,而是持有Source类的实例,以解决兼容性问题。具体的UML设计如图所示。

具体实现如下。

(1)适配器类的定义如下:

java 复制代码
public  class  ObjectAdapter   implements  Targetable  {
    private  final  static  Log  logger  =  LogFactory.getLog(ObjectAdapter.class);
    private  Source  source;
    public  ObjectAdapter(Source  source){
      super();
      this.source  =  source;
    }
    @Override
    public  void  editTextFile()  {
        this.source.editTextFile();
    }
    @Override
    public  void  editWordFile()  {
        logger.info("a  word  file  editing");
    }
}

以上代码定义了一个名为ObjectAdapter的适配器,该适配器实现了Targetable接口并持有Source实例,在适配editTextFile()的方法时调用Source实例提供的方法即可。

(2)使用对象适配器模式:

java 复制代码
Source  source  =  new  Source();
Targetable  target  =  new  ObjectAdapter(source);
target.editWordFile();
target.editTextFile();

在使用对象适配器时首先需要定义一个Source实例,然后在初始化ObjectAdapter时将Source实例作为构造函数的参数传递进去,这样就实现了对象的适配。执行结果如下:

java 复制代码
[INFO]  ObjectAdapter  -  a  word  file  editing
[INFO]  Source  -  a  text  file  editing

7.3、接口适配器模式

在不希望实现一个接口中所有的方法时,可以创建一个抽象类AbstractAdapter实现所有方法,在使用时继承该抽象类按需实现方法即可。具体的UML设计如图所示。

具体实现如下。

(1)定义公共接口Sourceable:

java 复制代码
public  interface  Sourceable  {
    void  editTextFile();
    void  editWordFile();
}

以上代码定义了Sourceable接口,并在接口中定义了两个方法,editTextFile()和editWordFile()。

(2)定义抽象类AbstractAdapter并实现公共接口的方法:

java 复制代码
public  abstract  class  AbstractAdapter  implements  Sourceable{
    @Override
    public  void  editTextFile()  {
    }
    @Override
    public  void  editWordFile()  {
    }
}

以上代码定义了Sourceable的抽象实现类AbstractAdapter,该类对Sourceable进行了重写,但是不做具体实现。

(3)定义SourceSub1类按照需求实现editTextFile():

java 复制代码
public  class  SourceSub1   extends  AbstractAdapter{
    private  final  static  Log  logger  =  LogFactory.getLog(SourceSub1.class);
    @Override
    public  void  editTextFile()  {
      logger.info("a  text  file  editing");
    }
}

以上代码定义了SourceSub1类并继承了AbstractAdapter,由于继承父类的子类可以按需实现自己关心的方法,因此适配起来更加灵活,这里SourceSub1类实现了editTextFile()。

(4)定义SourceSub2类按照需求实现editWordFile():

java 复制代码
public  class  SourceSub2  extends  AbstractAdapter{
    private  final  static  Log  logger  =  LogFactory.getLog(SourceSub2.class);
    @Override
    public  void  editWordFile()  {
      logger.info("a  word  file  editing");
    }
}

以上代码定义了SourceSub2类,继承了AbstractAdapter并实现了editWordFile()。

(5)使用接口适配器:

java 复制代码
public  static  void  main(String[]  args)  {
      Sourceable  source1  =  new  SourceSub1();
      Sourceable  source2  =  new  SourceSub2();
      source1.editTextFile();
      source2.editWordFile();
    }

使用接口适配器时按照需求实例化不同的子类并调用实现好的方法即可。以上代码的运行结果如下:

java 复制代码
[INFO]  SourceSub1  -  a  text  file  editing
[INFO]  SourceSub2  -  a  word  file  editing

8、装饰者模式的概念及Java实现

装饰者模式(Decorator Pattern)指在无须改变原有类及类的继承关系的情况下,动态扩展一个类的功能。它通过装饰者来包裹真实的对象,并动态地向对象添加或者撤销功能。

装饰者模式包括Source和Decorator两种角色,Source是被装饰者,Decorator是装饰者。装饰者模式通过装饰者可以为被装饰者Source动态添加一些功能。具体的UML设计如图所示。

具体实现如下。

(1)定义Sourceable接口:

java 复制代码
public  interface  Sourceable  {
    public  void  createComputer();
}

以上代码定义了一个Sourceable接口,该接口定义了一个生产电脑的方法createComputer()。

(2)定义Sourceable接口的实现类Source:

java 复制代码
public  class  Source  implements  Sourceable{
    private  final  static  Log  logger  =  LogFactory.getLog(Source.class);
    @Override
    public  void  createComputer()  {
      logger.info("create  computer  by  Source");
    }
}

以上代码定义了Sourceable接口的实现类Source并实现了其createComputer()。

(3)定义装饰者类Decorator:

java 复制代码
public  class  Decorator  implements  Sourceable{
    private  Sourceable  source;
    private  final  static  Log  logger  =  LogFactory.getLog(Decorator.class);
    public  Decorator(Sourceable  source){
      super();
      this.source  =  source;
    }
    @Override
    public  void  createComputer()  {
        source.createComputer();
        //在创建完电脑后给电脑装上系统
        logger.info("make  system.");
    }
}

以上代码定义了装饰者类Decorator,装饰者类通过构造函数将Sourceable实例初始化到内部,并在其方法createComputer()中调用原方法后加上了装饰者逻辑,这里的装饰指在电脑创建完成后给电脑装上相应的系统。注意,之前的Sourceable没有给电脑安装系统的步骤,我们引入装饰者为Sourceable扩展了安装系统的功能。

(4)使用装饰者模式:

java 复制代码
public  static  void  main(String[]  args)  {
      Sourceable  source  =  new  Source();
      Sourceable  obj  =  new  Decorator(source);
      obj.createComputer();
    }

在使用装饰者模式时,需要先定义一个待装饰的Source类的source对象,然后初始化构造器Decorator并在构造函数中传入source对象,最后调用createComputer(),程序在创建完电脑后还为电脑安装了系统。运行结果如下:

java 复制代码
[INFO]  Source  -  create  computer  by  Source
[INFO]  Decorator  -  make  system.

9、代理模式的概念及Java实现

代理模式指为对象提供一种通过代理的方式来访问并控制该对象行为的方法。在客户端不适合或者不能够直接引用一个对象时,可以通过该对象的代理对象来实现对该对象的访问,可以将该代理对象理解为客户端和目标对象之间的中介者。

在现实生活也能看到代理模式的身影,比如企业会把五险一金业务交给第三方人力资源公司去做,因为人力资源公司对这方面的业务更加熟悉,等等。

在代理模式下有两种角色,一种是被代理者,一种是代理(Proxy)​,在被代理者需要做一项工作时,不用自己做,而是交给代理做。比如企业在招人时,不用自己去市场上找,可以通过代理(猎头公司)去找,代理有候选人池,可根据企业的需求筛选出合适的候选人返回给企业。具体的UML设计如图所示。

具体实现如下。

(1)定义Company接口及其实现类HR:

java 复制代码
public  interface  Company  {
  void  findWorker(String  title);
}
public  class  HR  implements  Company  {
    private  final  static  Log  logger  =  LogFactory.getLog(HR.class);
    @Override
    public  void  findWorker(String  title)  {
      logger.info("i need find a worker, title is:"+title);
    }
}

以上代码定义了一个名为Company的接口,在该接口中定义了方法findWorker(),然后定义了其实现类HR,实现findWorker()以负责公司的具体招聘工作。

(2)定义Proxy:

java 复制代码
public  class  Proxy  implements  Company  {
    private  final  static  Log  logger  =  LogFactory.getLog(Proxy.class);
    private  HR  hr;
    public  Proxy(){
      super();
      this.hr  =  new  HR();
    }
    @Override
    public void findWorker(String title) {//需要代理的方法
      hr.findWorker(title);
      //通过猎头找候选人
      String  worker  =  getWorker(title);
      logger.info("find  a  worker  by  proxy, worker  name  is  :"+worker);
  }
  private  String  getWorker(String  title)  {
      Map<String,  String>  workerList  =  new  HashMap<String,  String>()  {
          { put("Java", "张三"); put("Python", "李四"); put("Php", "王五"); }
      };
      return  workerList.get(title);
  }
}

以上代码定义了一个代理类Proxy,用来帮助企业寻找合适的候选人。其中Proxy继承了Company并持有HR对象,在其HR发出招人指令(findWorker)后,由代理完成具体的寻找候选人工作并将找到的候选人提供给公司。

(3)使用代理模式:

java 复制代码
public  static  void  main(String[]  args)  {
    Company  compay  =  new  Proxy();
    compay.findWorker("Java");
 }

在使用代理模式时直接定义一个代理对象并调用其代理的方法即可,运行结果如下:

java 复制代码
[INFO] HR - i need find a worker, title is:Java
[INFO] Proxy - find a worker by proxy, worker name is :张三

10、外观模式的概念及Java实现

外观模式(Facade Pattern)也叫作门面模式,通过一个门面(Facade)向客户端提供一个访问系统的统一接口,客户端无须关心和知晓系统内部各子模块(系统)之间的复杂关系,其主要目的是降低访问拥有多个子系统的复杂系统的难度,简化客户端与其之间的接口。外观模式将子系统中的功能抽象成一个统一的接口,客户端通过这个接口访问系统,使得系统使用起来更加容易。具体的使用场景如图所示。

简单来说,外观模式就是将多个子系统及其之间的复杂关系和调用流程封装到一个统一的接口或类中以对外提供服务。这种模式涉及3种角色。

  • 子系统角色:实现了子系统的功能。
  • 门面角色:外观模式的核心,熟悉各子系统的功能和调用关系并根据客户端的需求封装统一的方法来对外提供服务。
  • 客户角色:通过调用Facade来完成业务功能。

以汽车的启动为例,用户只需按下启动按钮,后台就会自动完成引擎启动、仪表盘启动、车辆自检等过程。我们通过外观模式将汽车启动这一系列流程封装到启动按钮上,对于用户来说只需按下启动按钮即可,不用太关心具体的细节。具体的UML设计如图所示。

具体实现如下。

(1)定义Dashboard类:

java 复制代码
public  class  Dashboard  {
    private  final  static  Log  logger  =  LogFactory.getLog(Dashboard.class);
    public  void  startup(){
      logger.info("dashboard  startup......");
    }
    public  void  shutdown(){
      logger.info("dashboard  shutdown......");
    }
}

以上代码定义了Dashboard类来代表仪表盘,并定义了startup()和shutdown()来控制仪表盘的启动和关闭。

(2)定义Engine类:

java 复制代码
public  class  Engine  {
    private  final  static  Log  logger  =  LogFactory.getLog(Engine.class);
    public  void  startup(){
      logger.info("engine  startup......");
    }
    public  void  shutdown(){
      logger.info("engine  shutdown......");
    }
}

以上代码定义了Engine类来代表发动机,并定义了startup()和shutdown()来控制发动机的启动和关闭。

(3)定义SelfCheck类:

java 复制代码
public  class  SelfCheck  {
    private  final  static  Log  logger  =  LogFactory.getLog(SelfCheck.class);
    public  void  startupCheck(){
      logger.info("  startup  check  finished.");
    }
    public  void  shutdowncheck(){
      logger.info("shutdown  check  finished.");
    }
}

以上代码定义了SelfCheck类来代表汽车自检器,并定义了startupCheck()和shutdowncheck()来控制汽车启动后的自检和关闭前的自检。

(4)定义门面类Starter:

java 复制代码
public  class  Starter  {
  private  final  static  Log  logger  =  LogFactory.getLog(Starter.class);
  private  Dashboard  dashboard;
  private  Engine  engine;
  private  SelfCheck  selfCheck;
  public  Starter(){
      this.dashboard  =  new  Dashboard();
      this.engine  =  new  Engine();
      this.selfCheck  =  new  SelfCheck();
  }
  public  void  startup(){
      logger.info("car  begine  startup");
      engine.startup();
      dashboard.startup();
      selfCheck.startupCheck();
      logger.info("car  startup  finished");
  }
  public  void  shutdown(){
      logger.info("car  begine  shutdown");
      selfCheck.shutdowncheck();
      engine.shutdown();
      dashboard.shutdown();
      logger.info("car  shutdown  finished");
  }
 }

以上代码定义了门面类Starter,在Starter中定义了startup方法,该方法先调用engine的启动方法启动引擎,再调用dashboard的启动方法启动仪表盘,最后调用selfCheck的启动自检方法完成启动自检。

(5)使用外观模式:

java 复制代码
public  static  void  main(String[]  args)  {
Starter  starter  =  new  Starter();
starter.startup();
System.out.println("*******************");
starter.shutdown();
}

在使用外观模式时,用户只需定义门面类的实例并调用封装好的方法或接口即可。这里调用starter的startup()完成启动,运行结果如下:

java 复制代码
[INFO]  Starter  -  car  begine  startup
[INFO]  Engine  -  engine  startup......
[INFO]  Dashboard  -  dashboard  startup......
[INFO]  SelfCheck  -   startup  check  finished.
[INFO]  Starter  -  car  startup  finished
*******************
[INFO]  Starter  -  car  begine  shutdown
[INFO]  SelfCheck  -  shutdown  check  finished.
[INFO]  Engine  -  engine  shutdown......
[INFO]  Dashboard  -  dashboard  shutdown......
[INFO]  Starter  -  car  shutdown  finished

11、桥接模式的概念及Java实现

桥接模式(Bridge Pattern)通过将抽象及其实现解耦,使二者可以根据需求独立变化。这种类型的设计模式属于结构型模式,通过定义一个抽象和实现之间的桥接者来达到解耦的目的。

桥接模型主要用于解决在需求多变的情况下使用继承造成类爆炸的问题,扩展起来不够灵活。可以通过桥接模式将抽象部分与实现部分分离,使其能够独立变化而相互之间的功能不受影响。具体做法是通过定义一个桥接接口,使得实体类的功能独立于接口实现类,降低它们之间的耦合度。

我们常用的JDBC和DriverManager就使用了桥接模式,JDBC在连接数据库时,在各个数据库之间进行切换而不需要修改代码,因为JDBC提供了统一的接口,每个数据库都提供了各自的实现,通过一个叫作数据库驱动的程序来桥接即可。下面以数据库连接为例介绍桥接模式,具体的UML设计如图所示。

具体实现如下。

(1)定义Driver接口:

java 复制代码
public  interface  Driver  {
    void  executeSQL();
}

以上代码定义了Driver接口,在该接口中定义了一个执行SQL语句的方法,用于处理不同数据库的SQL语句。

(2)定义Driver接口的MySQL实现类MysqlDriver:

java 复制代码
public  class  MysqlDriver  implements  Driver{
    private  final  static  Log  logger  =  LogFactory.getLog(MysqlDriver.class);
    @Override
    public  void  executeSQL()  {
      logger.info(  "execute  sql  by  mysql  driver");
    }
}

以上代码定义了Driver的实现类MysqlDriver,并基于MySQL实现了其执行SQL语句的方法。

(3)定义Driver接口的Oracle实现类OracleDriver:

java 复制代码
public  class  OracleDriver  implements  Driver{
    private  final  static  Log  logger  =  LogFactory.getLog(OracleDriver.class);
    @Override
    public  void  executeSQL()  {
      logger.info(  "execute  sql  by  oracle  driver");
    }
}

以上代码定义了Driver的实现类OracleDriver,并基于Oracle实现了其执行SQL语句的方法。

(4)定义DriverManagerBridge:

java 复制代码
public   abstract  class  DriverManagerBridge  {
    private  Driver  driver;
    public  void  execute(){
      this.driver.executeSQL();
    }
    public  Driver  getDriver()  {
      return  driver;
    }
    public  void  setDriver(Driver  driver)  {
      this.driver  =  driver;
    }
}

以上代码定义了抽象类DriverManagerBridge,用于实现桥接模式,该类定义了Driver的注入,用户注入不同的驱动器便能实现不同类型的数据库的切换。

(5)定义MyDriverBridge:

java 复制代码
public     class  MyDriverBridge  extends  DriverManagerBridge  {
    public  void  execute()  {
      getDriver().executeSQL();
    }
}

在以上代码中,MyDriverBridge用于实现用户自定义的功能,也可以直接使用DriverManagerBridge提供的功能。

(6)使用桥接模式:

java 复制代码
public static void main(String[] args) {
    DriverManagerBridge  driverManagerBridge  =  new  MyDriverBridge()  ;
    //设置MySQL驱动
    driverManagerBridge.setDriver(new  MysqlDriver());
    driverManagerBridge.execute();
    //切换到Oracle驱动
    driverManagerBridge.setDriver(new  OracleDriver());
    driverManagerBridge.execute();
}

在以上代码中使用了桥接模式,定义了一个DriverManagerBridge,然后注入不同的驱动器,以实现在不同类型的数据库中实现驱动的切换和数据库SQL语句的执行。具体的执行代码如下:

java 复制代码
[INFO]  MysqlDriver  -  execute  sql  by  mysql  driver
[INFO]  OracleDriver  -  execute  sql  by  oracle  driver

12、组合模式的概念及Java实现

组合模式(Composite Pattern)又叫作部分整体模式,主要用于实现部分和整体操作的一致性。组合模式常根据树形结构来表示部分及整体之间的关系,使得用户对单个对象和组合对象的操作具有一致性。

组合模式通过特定的数据结构简化了部分和整体之间的关系,使得客户端可以像处理单个元素一样来处理整体的数据集,而无须关心单个元素和整体数据集之间的内部复杂结构。

组合模式以类似树形结构的方式实现整体和部分之间关系的组合。下面以实现一个简单的树为例介绍组合模式。具体的UML设计如图所示。

具体实现如下。

(1)定义TreeNode:

java 复制代码
public  class  TreeNode  {
    private  String  name;
    private  TreeNode  parent;
    private  Vector<TreeNode>  children  =  new  Vector<TreeNode>();
    public  TreeNode(String  name){
      this.name  =  name;
    }
    public  String  getName()  {
      return  name;
    }
    public  void  setName(String  name)  {
      this.name  =  name;
    }
    public  TreeNode  getParent()  {
      return  parent;
    }
    public  void  setParent(TreeNode  parent)  {
      this.parent  =  parent;
    }
    //添加子节点
    public  void  add(TreeNode  node){
      children.add(node);
    }
    //删除子节点
    public  void  remove(TreeNode  node){
      children.remove(node);
    }
    //获取子节点
    public  Enumeration<TreeNode>  getChildren(){
      return  children.elements();
    }
}

以上代码定义了TreeNode类来表示一个树形结构,并定义了children来存储子类,定义了方法add()和remove()来向树中添加数据和从树中删除数据。

(2)使用TreeNode:

java 复制代码
public  static  void  main(String[]  args)  {
      TreeNode  nodeA  =  new  TreeNode("A");
      TreeNode  nodeB  =  new  TreeNode("B");
      nodeA.add(nodeB);
      logger.info(JSON.toJSONString(nodeA));
    }

以上代码演示了TreeNode的使用过程,定义了nodeA和nodeB,并将nodeB作为nodeA的子类,具体运行结果如下:

java 复制代码
[INFO] CompositeDemo - {"children":[{"children":[], "name":"B"}], "name":"A"}

从以上代码中可以看到一棵包含了nodeA和nodeB的树,其中nodeB为nodeA的子节点。

相关推荐
JAVA面经实录9171 小时前
MySQL问题定位与性能优化完整知识体系
java·jvm·数据库·mysql·性能优化
Rain的Java大神之路1 小时前
Docker搭建Redis集群完全指南
java·运维·redis·后端·docker·容器·架构
Raas1001 小时前
大模型网关和API网关区别是什么?MAI Gateway统一AI流量治理
java·大数据·运维·人工智能·gateway·企业级·ai网关
BioRunYiXue1 小时前
RACE技术全攻略:从全长克隆到靶基因验证
java·javascript·网络·人工智能·科技·算法·eclipse
AC赳赳老秦1 小时前
个保法下数据处理:OpenClaw 自动过滤公开数据中的个人信息,保障采集分析合规性
java·python·sqlite·json·php·deepseek·openclaw
kymjs张涛1 小时前
DeepSeek Harness源码分析:非 AI 开发者的 Agent 学习总结
前端·后端·面试
rannn_1111 小时前
Java 后端面试题总结:点赞功能怎么实现?
java·后端·面试
CodeStats2 小时前
【Java进程通信】Java进程通信系统完全指南:从ProcessBuilder底层原理到多语言实战
java·前端·python·进程·ai编程·processbuilder
绿智校园2 小时前
物联网平台选型指南:为什么底层协议自研比云端SDK接入更可靠?
java·物联网·struts