分层的意义

分层结构在计算机里应用非常广泛,在大学的教材就曾写过,J2EE也自称是三层的分层结构。层状结构的精髓在于符合了关注点分离的基本原则,每一层只关注于这一层的要解决的问题。层状结构的每一层都只依赖于它的下层,而不依赖于上层,这样只要保证每一层对上层提供的接口的兼容性,就可以比较容易的替换掉这一层,扩展性更好。就比如硬件实现基本的计算能力,操人系统完成任务调度内存管理等,而应用程序则利用操作系统提供的接口操作硬件完成具体的任务,正是一个典型的分层结构。正是这样的结构才能保证了硬件,系统软件和应用软件各自较自由的独立发展。

伪分层

要真正的实现分层结构,并必得控制好依赖关系。上层只能依赖下层,下层不能依赖上层,如果违反了这一规则就不能算是分层的结构。这个规则看起来简单,但是在实现的时候仍然需要多付出些努力,有的时候为了方便就忘记了这些约束,结果就做出来了伪分层的结构。就拿传统的java web应用举例,一般我们都说java web应用采用三层结构,从上到下依次是表现层,业务层和数据访问层,应该是表现层依赖业务层,业务层依赖数据访问层。但是在实际开发过程中,一不小心就打破了这一约束。比如下面的示例代码: OrderForm.java

package view; 
public class OrderForm {
  //...
}

 OrderAction.java

public class OrderAction { 
  //... 
  public void onSubmit(OrderForm form) { 
     this.orderService.saveOrder(form); 
  } 
}

OrderService.java

package domain; 

import view.OrderForm; 
import dao.OrderDao; 

public class OrderService { 
  private OrderDao orderDao; 
  public void saveOrder(OrderForm form) { 
    //... 
  } 

  public OrderForm findOrder(Long orderId) { 
    return this.orderDao.findById(orderId); 
  } 
}

 OrderDao.java

package dao; 
public class OrderDao { 
  public OrderForm findById(Long orderId) { 
    //... 
    return orderForm; 
  } 
}

这样的代码好像是分了层,有表现有业务逻辑,有数据访问,而且还分出不同的包,把代码放到不同的包里面,但这样的代码并不是分层结构,因为OrderService类不仅依赖了dao,还依赖了OrderForm,在这里打破了分层结构的约束。 上面的示例代码非常简单,所以一般程序员一眼就能看出问题。但是,随着规模的扩大,项目的推进,新功能的不断添加,如果没有有效的控制方法,总有一天等你回过头来看程序,你会发现你的代码可能比上面的例子还要糟糕。你会看到展示层代码里有业务层和数据访问层代码,而数据访问层里面也会有展示层和业务层代码,尽管代码还是被放在不同的包里面,还起了XXView, XXController, XXAction, XXService, XXDao等等这样的名字,但是你会发现你小小的一个改动,可以轻易的影响到所有层次,代码变得越来越难以维护,新功能越来越难以添加进来。于是有些人开始抱怨分层也不好嘛!却不知道那样的代码早已不再是分层结构,而是拌面结构。 上面的代码要改成分层结构并不困难,只需要稍微多写一些代码,比如: OrderForm.java

package view; 
public class OrderForm {
  //...
}

OrderAction.java 

pakcage view; 
public class OrderAction { 
  public void onSubmit(OrderForm form) { 
    Order order = new Order; 
    order.setXX(form.getXX()); 
    //... 
    this.orderService.saveOrder(order); 
  } 
}

Order.java 

package domain; 
public class Order { 
  //... 
}

OrderService.java 

package domain;

import dao.OrderDao;

public class OrderService {
    private OrderDao orderDao;

    public void saveOrder(Order order) { ...}

    public Order findOrder(Long orderId) {
        OrderTable orderTable = this.orderDao.findById(orderId);
        return new Order(orderTable);
    }
}

OrderTable.java 

package dao; 
public class OrderTable {
  //...
}

OrderDao.java

package dao; 
public class OrderDao { 
    public OrderTable findOrder(Long orderId) { 
        //... 
        return orderTable; 
    } 
}

在现实的开发中,情况往往比这个要复杂得多,但是只要用心想一想,总能找到解决方法。

避免伪分层

怎样才能避免伪分层呢?如果所有代码只有你一个写,那么你规划好之后坚持原则,就不难避免这个问题。但如果是多人合作的项目,如果是一个有着比较长的生命周期,被很多人维护的程序,不采取些措施,是不可能实现的。详经设计+代码审核或许能解决这个问题,但是人力成本太高了。让所有开发人员一起学习一起觉悟,虽然有这个必要,但是要让所有人都觉悟,也是有些困难。在实际工作中,我正在采取的方法是把以前放在一个工程里的代码分拆到不同的工程中,让工程间满足设定好的分层的依赖关系。这样如果有人想在业务逻辑工程中调用展示层的类,就会有编译错误,因为业务逻辑工程不依赖于展示层工程,没办法在编译期调用它。这样促使程序员在完成功能的时候思考,应该把哪些功能放到哪个工程里,怎么调用它们,没有编译错误,还能完成工作。 尽管这样做给开发带来的额外的约束,有时候会更加麻烦,但是我相信,坚持下去,它的真正的价值是会体现出来了。

如何分层

就一层,那就是不分层;分层太多,开发难度高代价大。我觉得如果分出来的一个层能满足依赖关系的约束,能在这一层内处理好一类问题,那这个分层就是有意义的。归根到底分层的意义在于把关注点分离。 上层下层是相对的,但是一般的实践是让易变的依赖于不易变的。比如,页面上表单改一改是经常的,但是数据库里的那些表结构,往往很少变。 有的时候,如果知道某一个层在将来要被替换掉,那就可以做一个更严格的分层,就是只允许这一层的直接上层依赖这一层,而不允许上层的上层依赖本层。这样做就可以保证,替换这一层时,只会影响到直接上层。