首先,澄清两个概念。用户程序——服务使用者,服务程序——服务提供者。
用户程序怎么使用服务程序提供的服务呢?最直接的方法如下:在JAVA中的用户类中直接创建一个服务类,然后调用服务类提供的方法;在C中用户模块直接调用服务模块的函数。
举个例子,Client需要使用Impl提供的print功能来输出一个字符串。
[JAVA CODE]
xxx/Impl.java:
package xxx;
public class Impl {
public void print(Strings) {
System.out.println(“NowIn Impl. The input s==“ + s);
}
}
yyy/Client.java:
package yyy;
import xxx.Impl;
public class Client {
public static voidmain(String[] args) {
Impl impl = new Impl();
impl.print(“简单的测试程序!”);
}
}
[C CODE]
xxx/inc/impl.h
#ifndef_IMPL_H_
#define_IMPL_H_
#include<stdio.h>
extern voidprint(char *str);
#endif /* _IMPL_H_ */
xxx/src/impl.c
#include “impl.h”
void print(char*str)
{
printf(“Now In impl. The input str==%s”, str);
return;
}
yyy/src/client.c
#include “impl.h”
int main(argc,char **argv)
{
print(“简单的测试程序!”);
return 0;
}
上述代码有什么问题么?对于简单的应用,或者很稳定的应用来说,是没有什么问题的。但是,请考虑,假如有个用户程序用了一段时间,发现当前服务程序提供的服务不好用,他想换一种,怎么办?
方案一:直接修改当前服务实现的代码,对于只有一个用户程序的情况是没有问题,但是假如还有其他用户程序在使用,且不想改变现有实现呢?
方案二:重新添加一个服务来满足需求,该用户程序使用新的实现。此时,用户程序就需要修改。
以上两个方案都不符合”开闭原则”。那么,还有没有其他方案呢?能否在扩展当前功能的情况下不修改原有代码呢?
造成以上问题的原因就在于用户程序和服务程序之间产生了严重耦合,用户程序知道了服务程序的实现(方法\函数)。
那么,怎么能够解除耦合呢?
面向接口编程!而不是面向实现编程。用户程序完全不需要知道服务程序是怎么实现的,只需要和服务程序之间建立一份“契约”,明确服务程序需要提供的功能即可。此后,不管服务程序使用方法/函数名为print还是print_str,用户程序都不需要关心。
[JAVA CODE]
xxx/Api.java:
package xxx;
/**
* 某个接口(通用的、抽象的、非具体的功能)
*/
public interface Api {
/**
*
* 某个具体的功能方法的定义
* 功能是把传入的字符串打印出来
* @ param s 任意想要打印输出的字符串
*/
public void print(Strings);
}
xxx/Impl.java:
package xxx;
/**
* 对接口的实现
*/
public class Impl implements Api {
public void print(Strings) {
System.out.println(“NowIn Impl. The input s==“ + s);
}
}
yyy/Client.java:
package yyy;
import xxx.Api;
import xxx.Impl;
/**
* 客户端: 测试使用API接口
*/
public class Client {
public static voidmain(String[] args) {
Api api = new Impl();
api.print(“简单的 接口-实现 测试程序!”);
}
}
[C CODE]
xxx/inc/api.h
#ifndef _API_H_
#define _API_H_
typedef void(*PRINT_FUNC)(char *str);
typedef struct tagApi_S
{
PRINT_FUNC printFunc;
}Api_S,*API_S_Handle;
#endif /* _API_H_ */
xxx/inc/impl.h
#ifndef _IMPL_H_
#define_IMPL_H_
#include"api.h”
#include<stdio.h>
externAPI_S_Handle impl_get_opt(void)
#endif /* _IMPL_H_ */
xxx/src/impl.c
#include"impl.h”
void print(char*str)
{
printf("Now In impl. The inputstr==%s", str);
return;
}
Api_S g_api =
{
.printFunc = print;
};
API_S_Handleimpl_get_opt(void)
{
return &(g_api);
}
yyy/src/client.c
#include"api.h"
#include"impl.h"
int main(argc,char **argv)
{
API_S_Handle handle = impl_get_opt();
/* 省略错误处理 */
handle->printFunc("简单的 接口-实现 测试程序!”);
return 0;
}
好了,这次是面向接口编程了。但是,问题解决了么?没有!用户程序还是要从一定程度上知道服务程序的实现,即JAVA代码中的Api api = newImpl();和C代码中的API_S_Handle handle =impl_get_opt();还是和具体实现产生了耦合。当要使用AnotherImpl来替换Impl时,还是要修改现有代码。
那么,能不产生耦合么?怎么让用户程序在只知道接口而不知道实现的情况下工作?这就是简单工厂要解决的问题。
先看代码。
[JAVA CODE]
xxx/Api.java:
package xxx;
/**
* 某个接口(通用的、抽象的、非具体的功能)
*/
public interface Api {
/**
*
* 某个具体的功能方法的定义
* 功能是把传入的字符串打印出来
* @ param s 任意想要打印输出的字符串
*/
public void print(Strings);
}
xxx/Impl.java:
package xxx;
/**
* 对接口的实现
*/
public class Impl implements Api {
public void print(Strings) {
System.out.println(“NowIn Impl. The input s==“ + s);
}
}
xxx/ Factory.properties:
ImplClass = xxx.Impl
xxx/ Factory.java:
package xxx;
import java.util.Properties;
import java.io.InputStream;
import java.io.IOException;
import java.lang.Class;
import java.lang.InstantiationException;
import java.lang.IllegalAccessException;
import java.lang.ClassNotFoundException;
/**
* 工厂类,用来创建Api对象
*/
public class Factory{
/**
* 具体创建Api对象的方法,根据配置文件的参数来创建接口
* @return 创建好的Api对象
*/
public static ApicreateApi() {
// 直接读取配置文件来获取要创建实例的类
Properties p =new Properties();
InputStream in =null;
try {
in =Factory.class.getResourceAsStream("Factory.properties");
p.load(in);
} catch (IOExceptione) {
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 (IllegalAccessExceptione) {
e.printStackTrace();
}
catch(ClassNotFoundException e) {
e.printStackTrace();
}
return api;
}
}
yyy/Client.java:
package yyy;
import xxx.Api;
import xxx.Factory;
/**
* 客户端: 测试使用API接口
*/
public classClient {
public static void main(String[] args) {
Api api = Factory.createApi();
api.print("简单的 接口-实现 测试程序!");
}
}
[C CODE]
xxx/inc/api.h
#ifndef _API_H_
#define _API_H_
typedef void(*PRINT_FUNC)(char *str);
typedef struct tagApi_S
{
PRINT_FUNC printFunc;
}Api_S,*API_S_Handle;
#endif /* _API_H_ */
xxx/inc/impl.h
#ifndef_IMPL_H_
#define_IMPL_H_
#include"api.h”
#include<stdio.h>
externAPI_S_Handle impl_get_opt(void)
#endif /* _IMPL_H_ */
xxx/src/impl.c
#include"impl.h”
void print(char*str)
{
printf("Now In impl. The inputstr==%s", str);
return;
}
Api_S g_api =
{
.printFunc = print;
};
API_S_Handleimpl_get_opt(void)
{
return &(g_api);
}
xxx/src/factory.h
externAPI_S_Handle create_api(void);
xxx/src/factory.c
#include"api.h"
#include"factory.h"
#include"impl.h"
API_S_Handlecreate_api(void)
{
/* 暂且忽略选择实现的机制 */
API_S_Handle handle;
/* 假设选择了Impl实现 */
handle = impl_get_opt();
return handle;
}
yyy/src/client.c
#include"api.h"
#include"factory.h"
int main(argc,char **argv)
{
API_S_Handle handle = create_api();
/* 省略错误处理 */
handle->printFunc("简单的 接口-实现 测试程序!”);
return 0;
}
现在,用户程序使用简单工厂来实例化接口,不用关注具体的实现了。在简单工厂中可以选择具体的实现,在这里不深入讨论选择实现的机制,有一种更好的选择方式就是使用容器,将选择延迟到容器中,感兴趣的同学们可以查阅相关资料(关键词包括“依赖倒置”、“控制反转”、“Spring容器”等)。
使用简单工厂,有什么好处呢?下面以JAVA代码为例,演示一下在另一个用户程序中要将服务程序修改为由AnotherImpl来提供实现的步骤。
[JAVA CODE]
首先,新增AnotherImpl。
xxx/ AnotherImpl.java:
package xxx;
/**
* 对接口的实现
*/
public class AnotherImpl implements Api {
public void print(Strings) {
System.out.println("NowIn AnotherImpl. The input s==" + s);
}
}
其次,修改配置文件。
xxx/ Factory.properties:
# ImplClass = xxx.Impl
ImplClass = xxx.AnotherImpl
再次,没有啦!
最后,测试,OK,满足需求!
通过以上示例,相信各位同学已经理解了简单工厂的主要作用:封装隔离和选择实现。封装隔离的目的是让用户程序不必关注服务程序的实现,这也是面向接口编程的意义所在;选择实现的目的是为了满足不同用户程序的不同需求,体现向扩展开放、向修改封闭的原则。