接口和工厂

1 基本信息

摘要:本文说明在Java API设计中,面向接口编程的思想,以及接口和工厂的关系。

作者:陈光耀

2 正文

  现在的java API的设计中,提倡面向接口的编程,即在API的设计中,参数的传递和返回建议使用接口,而不是具体的实现类,如一个方法的输入参数类型应该使用Map接口,而不是HashMap或Hashtable等具体的实现类。这样做的好处是,程序容易扩展。如果使用Map作为参数,用户可以使用任何实现Map接口的类作为参数,而不是仅仅限制使用HashMap或Hashtable作为参数,使程序的实现更加灵活。

  接口(Java的Interface),只定义了一些抽象的方法(也可以定义一些常量,但不鼓励这么做),没有具体的实现,这些抽象方法的具体实现,必须由实现(implements)接口的具体类来实现。接口反映了系统设计人员对系统的抽象理解。实际上,接口只是定义了一个规范和约束,要求实现类必须遵循这些规范和约束,大家编程时都要按照这个规范进行编程。

  使用接口进行编程时,API的使用者只使用接口进行调用,而不管接口是如何实现的,这样你就可以随意更换接口的实现厂商(provider)。假如,你觉得厂商的接口实现不好,可以换一个厂商的实现类,甚至自己来实现,但确基本上不需要更改已经写好的应用程序(因为应用程序只对接口进行调用,而所有的实现类的接口都是相同的)。

  如java.sql包定义了很多jdbc的接口,大家在进行jdbc编程时都只需要使用Connection、PreparedStatment、ResultSet等接口,而不需要考虑这些接口的具体实现类是什么。每种数据库,如Oracle、SQLServer、DB2的JDBC驱动则是由各个厂商提供具体实现类,来实现统一的JDBC API接口规范。

  面向接口的编程中,工厂(Factory)的使用恐怕是最常用的,接口和工厂的关系是密不可分的,工厂,简单的说,就是生产接口实现类实例的类。可以说,在面向接口的编程中,工厂方法发挥了极其重要的作用。

  在开发应用时,如何对接口进行调用,如何实例化接口对象?你无法通过类似于new Inteface1()等来进行实例化,因为接口是不能实例化的(接口即Java的Interface不是类,所有方法都是抽象的,所以不能实例化,能够实例化的只能是接口的实现类)。实例化接口类对象主要有两种方法:

1) 直接new 接口的实现类的实例,类似于
Map map=new HashMap(); //HashMap实现了Map接口
Interface1 instance=new ConcreteClass1();   //ConcreteClass1实现了Inteface1接口

  在一个API内部,这样写无可厚非,因为对调用者是屏蔽的,你今后将HashMap改为hashtable,不会影响到API调用者的程序。但是对于一个API调用的程序来说,这样就不太好了。如下面的调用(出租车公司增加一种高级轿车):
Car car=new BenzCar(); //BenzCar实现了Car接口
carRentCompany.addSuperCar(car);

  虽然可以这样,但这与面向接口的编程的原则不符,因为这要求你必须要知道接口的实现类,如果你有天想换一个实现类(如将奔驰车换成宝马车),就麻烦了,你必须找到你以前的应用程序源代码,将每一个调用接口实现类的地方都改过来,工作量比较大了。

  还有一种更好的方法,就是使用工厂。

2) 使用工厂,如
Car car=CarFactory.getSuperCar();
carRentCompany.addSuperCar(car);

  由工厂来提供实例化方法,你不必关心实例化类是哪一个。当然你必须知道工厂类是哪一个,更换接口实现的时候,你只需要修改工厂的方法就可以了,其余的都不需要更改了。如上面的场景,将奔驰车换成宝马车,只要更改CarFactory.getSuperCar()方法就可以了,用户调用API的程序不需要修改。

  举个jdbc调用的例子:

Oracle的jdbc调用:
    ... ...
    Class.forName("oracle.jdbc.driver.OracleDriver";);
    Connection conn=DriverManager.getConnection(jdbcurl,username,password);

SQLServer的jdbc调用:
    ... ...
    Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");
    Connection conn=DriverManager.getConnection(jdbcurl,username,password);

MySQL的jdbc调用:
    ... ...
    Class.forName("com.mysql.jdbc.Driver");
    Connection conn=DriverManager.getConnection(jdbcurl,username,password);

  这里,DriverManager就是工厂,通过getConnection()方法来生产出数据库连接对象Connection, 用户使用Connect就可以实现JDBC编程了。

  但是,DriverManager是怎么知道使用的哪个数据库,使用哪个提供商的JDBC驱动来生产Connectin的吗?答案在这里,原因就在Class.forName()的过程中,Class.forName()是将一个类初始化,这里,调用不同厂商提供jdbc驱动时,使用了不同的jdbc驱动类的类名,该方法会初始化化该Driver类,该类在初始化时(static{}方法中)会初始化一个自己的Driver类实例,并将实例注册到DriverManager中(的一个Vector中),再通过DriverManager中的getConnection()方法找到Driver实例(通过jdbcurl),再调用Driver实例的getConnection()方法。Driver实例也是一个生产Connection接口实例的工厂。DriverManager实际上是一个保存了各个jdbc驱动的一个缓存管理类,缓存了各个驱动程序,并调用各个jdbc的驱动程序生产Connection对象。

  可以看出,只需要更改Driver的类名,提供不同的jdbcUrl和用户、密码,就可以生产出一个数据库连接(Connection接口类的实例)。

  建议:如果你使用配置文件来提供工厂类的名字,程序通过调用配置文件来读取工厂的类名,更换接口实现就更简单了,你只需要改配置文件就可以了,而不需要更改程序。

接口和工厂之间关系形象比喻:

  接口就好比是产品规格,如螺母和螺钉的产品规格,规定了不同的大小尺寸,左旋等,工厂好比生产这些螺母和螺钉的加工企业,而接口的实例好比不同工厂生成出的螺母、螺钉的产品。每一个不同的工厂虽然生成出的不同螺母和螺钉产品,但都必须遵守这个产品规格,这样不同工厂生产出来的不同的螺母和螺钉才可以拧在一起使用,你也可以随意更换不同工厂生产出来的螺母或螺钉,而不会出现问题。

你可能感兴趣的:(java基础)