您的位置:知识库 » 软件设计

对象的自治与智能化

作者: 张逸  发布时间: 2011-10-03 10:38  阅读: 1606 次  推荐: 0   原文链接   [收藏]  

  我一直强调对象是自治的,这意味着它应该拥有能表达自身对象特性的数据与行为,对自己的数据与状态负责,对于该数据而言,对象是自给自足的。对象的自治体现了OO最基本的原则,那就是“数据与行为应该封装在一起”。拥有行为能力的对象,就好比拥有了意识,拥有了智能,它可以自行判断,而无需别人通知。不错,这事实上就是“好莱坞原则”的体现,但我更喜欢将其称之为对象的“专家模式”。这种专家模式与现实社会何其相似,“专业的事情就交给专家去做吧!”软件系统的对象应该各司其职,各尽其职,这样才能产生合理的职责分配,从而形成完善的协作方式。

  还是用案例来说话。在我们的项目中,需要对客户发出的Web请求进行处理,获得我们需要的参数。参数的值放在Request中,而我们事先已经根据配置文件,获得了参数的类型信息。根据项目需要,我们将参数划分为三种:
  1、单一参数(SimpleParameter);
  2、元素项参数(ItemParameter);
  3、表参数(TableParameter);

  因为参数的属性是在配置文件中已经配好,我定义了ParameterGraph对象。它能够读取参数的配置信息,并根据参数的类型创建不同的参数对象。这些参数类共同继承了一个抽象的父类ParameterBase,并实现了Parameter接口,如下图所示:

  由于参数的数据放在客户端发出的Web请求中,因此,我们需要对Web请求进行解析。由于Web请求存储的参数值事实上是存放在一个Map中,我们需要根据参数的name来甄别这些请求值,并根据一定的判断逻辑,将Web请求传来的值填充到Parameter对象中。这是一个收集参数的过程,它被定义在ReportParameterAction类中。最初,我是这样实现的:

private Map<String,Parameter> collectParameters(ServletRequest request, ParameterGraph parameterGraph) {
for (Parameter para : parameterGraph.getParmaeters()) {
Map<String,Parameter> paraMap = new HashTable<String, Parameter>();
if (para instanceOf SimpleParameter) {
String[] values = request.getParameterValues(para.getName());
para.setValues(values);
paraMap.put(para.getName(),para);
} else {
if (para instanceOf ItemParameter) {
ItemParameter itemPara = (ItemParameter)para;
for (Item item : itemPara.getItems()) {
String[] values = request.getParameterValues(item.getName());
item.setValues(values);
}
paraMap.put(itemPara.getName(),itemPara);
}else {
TableParameter tablePara = (TableParameter)para;
String[] rows = request.getParameterValues(para.getRowName());
String[] columns = request.getParameterValues(para.getColumnName());
String[] dataCells = request.getParameterValues(para.getDataCellName());

int columnSize = columns.size;
for (int i=0; i < rows.size;i++) {
for (int j=0;j < columns.size;j++) {
        TableParameterElement element = new TableParameterElement();
element.setRow(rows.get(i));
element.setColumn(columns.get(j);
element.setDataCell(dataCells[columnSize * i + j]);
tablePara.addElement(element);
}
paraMap.put(tablePara.getName(),tablePara);
}
}
}
return parameterGraph.getParameters();
}

  对于不同类型的参数,有着不同的处理逻辑。例如,对于TableParameter而言,它在客户端浏览器的表单中,事实上表现为一个表,然后以行、列以及数据单元格的名称作为Map的key值,而value则是一个字符串数组。当我写出这样的代码时,我的直觉告诉我,这样的代码是丑陋的,一定存在某些问题。那么问题出在哪里呢?首先,在这个方法中,存在分支语句,它会判断parameter对象的运行时类型,以此决定不同的处理逻辑。为什么要这样做,从上面的类图可以看出,对于不同的Parameter对象,值是不相同的,即拥有各自不同的方法,例如getValue()、addItem()与addElement()等。而这些方法是不同抽象到它们共同的父类或接口中,因此必须转换为具体类型,再进行处理。

  现在,让我们从两个角度来思考问题。首先,阅读分支语句的执行逻辑,它所要处理的数据是属于谁的?除了提供参数数据源的request对象,这些数据都是属于各自参数对象的。例如,针对TableParameter,虽然看起来是在创建TableParameterElement对象,但实质上该对象本身就属于TableParameter的一个组成元素。ItemParameter与SimpleParameter同样如此。根据前面所述的“数据与行为应该封装在一起”原则,我们似乎应该将各个分支的处理逻辑封装到各自的Parameter类中。事实上,ReportParameterAction类只关心对参数的收集,并不需要知道参数收集的具体实现。因此,无论是从封装还是职责分配的角度来看,这里的事先都存在问题。

  让我们再站在抽象的角度思考问题。前面提到不同参数有不同的值,且这些值分别属于不同的类型,操作方式也不相同,因而无法抽象。事实上,这里违背了抽象的原则,就是过于关注实现细节,而没有从共性特征的角度去分析接口。在前面的分析中,实质上我已经隐隐约约提到了这个共同特征:即无论设置什么值,其本质都是将数据填充到参数中,至于如何填充,则属于实现细节的范畴。因此,我们可以在Parameter接口中定义如下方法:

public interface Parameter {
public void fillData(ParameterRequest request);
}

  注意,我在fillData()方法中玩了一个花招。我们可以比较之前的collectParameters()方法,它接收的参数为ServletRequest对象,现在fillData()方法的参数变成了ParameterRequest,发生了什么事?

  原因有二。其一,ServletRequest类是由Servlet提供的,Parameter要使用它,就必须依赖于Servlet包,这实在有些得不偿失。其二,ServletRequest接口是一个庞大的接口,它很难被实现。设想我们如果要对Parameter进行单元测试,则很难模拟ServletRequest,而事实上,这里仅仅需要调用ServletRequest接口的getParameterValues()方法即可。正因为如此,我引入了ParameterRequest接口,它仅定义了一个getParameterValues()方法:

public interface ParameterRequest {
public String[] getParameterValues(String name);
}

  对于这样一个接口,我们很容易模拟它的实现,而对于调用者ReportParameterAction而言,也很容易将ServletRequest对象转为ParameterRequest的实现。好了,这里的设计事实上体现了对象的“间接”以及单一职责原则,通过引入间接的接口来解除耦合。不过,这应该是另外一个故事了。让我们继续回过头来看参数的处理。我们将之前collectParameters()方法的实现转移到各个Parameter子类的fillData()实现中:

public class SimpleParameter extends ParameterBase {
public void fillData(ParameterRequest request) {
this.setValues(request.getParameterValues(this.getName()));
}
}
public class ItemParameter extends ParameterBase {
public void fillData(ParameterRequest request) {
for (Item item : this.getItems()) {
String[] values
request.getParameterValues(item.getName());
item.setValues(values);
}
}
}
public class TableParameter extends ParameterBase {
public void fillData(ParameterRequest request) {
String[] rows =
request.getParameterValues(this.getRowName());
String[] columns =
request.getParameterValues(this.getColumnName());
String[] dataCells =
request.getParameterValues(this.getDataCellName());

int columnSize = columns.size;
for (int i=0; i < rows.size;i++) {
for (int j=0;j < columns.size;j++) {
TableParameterElement element =
new TableParameterElement();
element.setRow(rows.get(i));
element.setColumn(columns.get(j);
element.setDataCell(dataCells[columnSize * i + j]);
this.addElement(element);
}
}
}
}

  现在,这些Parameter的子类就是自治的对象,它能够处理属于自己的数据,而不需要外界(调用者)来告知它,它充分享有智能判断的能力。让我们看看修改后的客户端代码:

private Map<String,Parameter> collectParameters(ServletRequest request, ParameterGraph parameterGraph) {
for (Parameter para : parameterGraph.getParmaeters()) {
Map<String,Parameter> paraMap = new HashTable<String, Parameter>();
para.fillData(new ParameterRequest() {
public String[] getParameterValues(String name) {
return request.getParameterValues(name);
}
);
}
return parameterGraph.getParameters();
}

  现在,Parameter对象能够根据自己的类型对从Request中取出来的数据进行处理。ReportParameterAction的collectParameters()方法就不用操心这些烦人的处理逻辑,代码结构变得更加清晰,尤其是职责的划分更为准确。这种对参数职责有效的封装方式,还有利于参数类型的扩展。倘若增加了新的参数类型,只需要定义继承自ParameterBase类的子类即可,不会影响到任何客户调用代码。这就是自治对象的好处。记住,当我们需要分配职责时,就应该考虑自治原则与专家模式,并设身处地站在该对象的角度去思考问题,想一想,如果你就是这个对象,应该完成哪些职责,而哪些职责又应该委派给别的对象。

0
0
标签:面向对象

软件设计热门文章

    软件设计最新文章

      最新新闻

        热门新闻