在web开发的工作,mvc也是比较常用的开发模式,这种开发模式通常将代码分成三层(控制层、业务逻辑层、数据库访问层),无论是较早的SSH、SSM、还是现在比较流行的spring boot框架,都提倡面向接口编程,这样可以降低层与层之间的耦合。这已经成为了一种范式,大家都去遵守,写着写着就有点像八股文。在利用面向对象的语言进行大型系统的设计需要考虑各个对象之间的交互的问题,接口本质是一种规范和约束,反映了系统设计者对系统的抽象理解。换一种说法,面向接口编程,写业务的不需要遵守。当设计和实现分离的时候,面向接口编程是一种解决问题的很好方式,但是当设计和分离是一个人的时候,就显得非常没有必要。下面分别讨论这两种情况:
1)框架设计
这种情况下,设计和实现是分离的。面向接口的设计,能保证框架的扩展性。比如设计物流运输系统,针对运输这个过程,系统设计人员经过抽象,将物流公司的运输行为分解为三个过程:接单、送达,结算,用户评价,那么可以定义一个接口约束这种行为:
public interface Transport {
//接单
void receiveOrder();
//运达
void arrive();
//结算
void pay();
//用户反馈
void userComment();
}
业务系统中有各种配送方式,比如外包(A运输公司、B运输公司等)、自己配送,直接实现上面的接口,然后在里面写具体的业务实现。然后将这种实现交由spring管理,框架在执行业务的时候,根据参数从spring中取出相应的实现,然后执行相应的业务逻辑。
public class TransportExecutor {
public void transport(int selectedType){
if(selectedType==1){
//外包
//模拟随机分配订单到运输公司
int flag=new Random().nextInt(10);
//根据flag读取配置,取出component的name
String companyName= PropertyUtil.getCompanyByFlag(flag);
Transport transport=SpringBeanUtil.getBeanByName("name");
transport.receiveOrder();
transport.arrive();
transport.pay();
transport.userComment();
}else{
//自己运输
}
}
}
另外面向接口编程在mybatis中应用也比较普遍,mybatis中的mapper的接口其实就是一个标记,mybatis为每个接口都生成了一个动态代理对象,业务层中或者dao层调用mapper接口中的方法时,其实是调用生成的动态代理对象,在代理对象相应的方法中完成sql语句的执行。
sqlsession.getMapper(class)
-->
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
-->
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//knownMappers.get(type) 获取代理工厂,knowMapper加载配置文件的时候初始化,为每个mapper接口创建一个代理工厂
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//通过代理工厂,生产一个mapper的实例,里面的invoke方法封装了具体的信息,见下面分析
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
--->
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
--->
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//mapperProxy实现了InvocationHandler方法
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
---> mapperProxy中invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//判断接口是否有实现类,若没有执行下面逻辑 此处不能有,不然后续无法执行sql
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
2)业务开发
面对很简单的业务模型(crud),在应用内各层(controller、service、dao)之间相互调用,不存在定义和实现分离的情况,这个时候就没必要再面向接口编程。这样可以减少没有必要的工作量。大家统一约束好(业务层类命名直接以service结尾,不用接口和实现分离),需要调用其他人写的业务方法,直接注入实现即可,其他人修改实现,不会影响你的调用。
@Service
public class TransportService {
@Autowired
//此处直接注入实现类
private UserLoginService userLoginService;
public void transport(int selectedType){
userLoginService.checkLoin();//调用实现类中的方法
}
}