预计阅读时间:10 分钟
1、Java中的接口
- 接口里面的所有方法都是抽象方法,只有方法定义而不会有任何方法实现,所有属性都是常量
- 接口通常用来定义实现类的外观,可实现不相关类的相同行为,而不需考虑这些类之间的层次关系
- 接口的好处是将外部调用和内部实现隔离开,客户端不用关心具体实现
- 在开发中,优先选择接口而不是抽象类
- 接口把具体的实现和使用接口的客户程序分离开来,从而使得具体的实现和使用接口的客户程序可以分别扩展,而不会相互影响。
2、案例分析
(1)不使用模式的解决方案
// 先定义接口Api(通用的、抽象的、非具体的功能)
public interface Api {
public void test1(String s);
}
// 对接口进行实现
public class Impl implements Api{
public void test1(String s) {
System.out.println("Now In Impl. The input s=="+s);
}
}
//客户端测试使用API
public class Client {
public static void main(String[] args) {
Api api = new Impl();
api.test1("哈哈,不要紧张,只是个测试而已!");
}
}
上述方案存在的问题:客户端在调用时,不仅知道了接口,同时还知道了具体的实现类Impl,没有做到真正的封装隔离。
(2)简单工厂
图中的虚线框好比一个组件的包装边界,表示接口、实现类和工厂类组合成一个组件,在这个封装体里面,只有接口和工厂是对外的。
public class Factory {
public static Api createApi(){
return new Impl();
}
}
public class Client {
public static void main(String[] args) {
//重要改变,没有new Impl()了,取而代之Factory.createApi()
Api api = Factory.createApi();
}
}
(3)简单工厂的核心是选择一个合适的实现类来使用,在多个实现类的场合中,工厂就需要选择的条件或者是参数,参数的来源:
- 来源于客户端,由Client来传入参数
- 来源于配置文件,从配置文件获取用于判断的值
- 来源于程序运行期的某个值,比如从缓存中获取某个运行期的值
public class Factory {
/**
* 具体的创造Api的方法,根据客户端的参数来创建接口
* @param type 客户端传入的选择创造接口的条件
* @return 创造好的Api对象
*/
public static Api createApi(int type){
//根据type来进行选择,当然这里的1和2应该做成常量
Api api = null;
if(type==1){
api = new Impl();
}else if(type==2){
api = new Impl2();
}
return api;
}
}
public class Client {
public static void main(String[] args) {
//注意这里传递的参数,修改参数就可以修改行为
Api api = Factory.createApi(2);
api.test1("哈哈,不要紧张,只是个测试而已!");
}
}
上述方案的不足:需要客户端调用工厂的时候,传入选择参数,那么就要求客户端知道每个参数的含义,也需要理解每个参数对应的功能处理。这就要求必须在一定程度上,向客户暴露一定的内部实现细节。
改进方案:使用配置文件,当有了新的实现类过后,只要在配置文件里面配置上新的实现类就好 了,在简单工厂的方法里面可以使用反射,当然也可以使用IoC/DI来实现
public class Factory {
/**
* 具体的创造Api的方法,根据配置文件的参数来创建接口
* @return 创造好的Api对象
*/
public static Api createApi(){
//直接读取配置文件来获取需要创建实例的类
Properties p = new Properties();
InputStream in = null;
try {
in = Factory.class.getResourceAsStream("FactoryTest.properties");
p.load(in);
} catch (IOException e) {
System.out.println( "装载工厂配置文件出错了,具体的堆栈信息如下:");
e.printStackTrace();
}finally{
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//用反射去创建,那些例外处理等完善的工作这里就不做了 Api api = null;
try {
api = (Api)Class.forName(p.getProperty("ImplClass")).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return api;
}
public class Client {
public static void main(String[] args) {
Api api = Factory.createApi(); api.test1("哈哈,不要紧张,只是个测试而已!");
}
}
3、何时选用简单工厂:
如果想要完全封装隔离具体实现,让外部只能通过接口操作封装体,那么可以选用简单工厂
如果想要把对外创建对象的职责集中管理和控制,可以选用简单工厂
4、相关模式
(1)简单工厂模式和抽象工厂模式
简单工厂是用来选择实现,可以有多个创建对象的方法,多个方法创建的对象之间可以有关系,也可以没有关系
抽象工厂模式是用来选择产品簇的实现的,抽象工厂里多个创建对象的方法之间通常是有关系的,创建的对象通常构成一个产品簇所需要的部件
简单工厂和抽象工厂是类似的,如果抽象工厂退化成只有一个实现,不分层次,那么就相当于简单工厂了
(2)简单工厂模式和工厂方法模式
工厂方法的本质也是用来选择实现的,跟简单工厂的区别在于工厂方法是把选择具体实现的功能延迟到子类去实现。
如果把工厂方法中选择的实现放到父类直接实现,那就等同于简单工厂。
(3)简单工厂和能创建对象实例的模式:简单工厂可以跟其它任何能够具体的创建对象实例的模式配合使用,比如:单例模式、原型模式、生成器模式等等。
尾注
- 上述的总结与思考是基于对《研磨设计模式》这本书的精读与演绎
- 更多及时干货,请关注微信公众号:JAVA万维猿圈