I insist on that the object must be autonomous, that means it should own its data and behavior that can express its features. It will responsible for managing its data and state. The autonomous object reflects the basic principle of OO, that is "The data and the related behaviors should be encapsulated together." The object which owns the behaviors is smart, it is like it has a general sense, to judge for itself, without other objects informing. Other hand, the object should be an expert who can handle and manage its data.
For example, we need handle the parameters which are submitted by the customer to fetch the parameter objects we want to get in our project. The values of these parameters are placed in the web request. In advance we have get the information of parameter type according to the configuration. We provide three kinds of parameter:
1. Simple Parameter
2. Item Parameter
3. Table Parameter
I have defined the ParameterGraph class which can read the configuration info and create the concrete parameter subclass object by the parameter type. These parameter classes extends the same abstract class ParameterBase, and the ParameterBase implements the Parameter interface. The class diagram is as below:
Because the data of parameters are stored in the web request, so we must parse the request. Essentially, the parameters in the web request are stored in the map, we should distinguish the parameters according to their name, then fill the data into the corresponding parameter object. We think it is the process of collecting the data of request and assembling parameter objects. This behavior is defined in the ReportParameterAction class. At first, I implement this function like this:
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();
}
There are different logic to handle the different kind of parameter. For instance, if we want to collect the TableParameter, we must recoginize the row, column and data cells of table, and let their names to be key of map, and the value of map is the String array in fact. When I was writting these code like this, my intuition tell me that it exists bad smell. Maybe something is going wrong? In the collectParameters() method, there is very terrible branch statement. It will determine the runtime type of parameter to choose the differant processing logic. Why should we do like this? According to the class diagram, the parameter subclasses have the different method to process the value of paramter, such as getValue(), getItems(), getElement(). These methods can not be generized into the abstract super class. So that we must downcast the concrete type to invoke the corresponding method.
Now, let’s consider the issue from two perspective. First, who own the data which be processed by branch statement? These data are belong to the related parameter object except the request as the datasource for parameters. So, according to the OO principle described above, we should encapsulate these processing logic into the corresponding parameter subclass.
Second, we might consider about the abstraction of parameter. Although the different parameter has the different value, and the behaviors of process these value are different also, so we can’t generalize these logic. In fact, if you agree this opinion, it means you don’t really understand the meaning of abstract. Because we focused on the details, but not analyze the interface from the perspective of common features. In the previous description, I have referred to this common feature, that is: whatever value you want to process, you want to fill these value into the parameter object essentially, as for how to fill, it belongs to the scope of implementation details. So we can define such method in the paramter interface:
public interface Parameter {
public void fillData(ParameterRequest request);
}
Note, I play a trick here. Comparing between the fillData() and collectParameters() methods, they accept the different argument. In fillData() method, I provide the ParameterRequest to replace the ServletRequest. What happen?
Two reasons. First, the ServletRequest interface is defined in the Servlet. If Parameter object want to use ServletRequest, it must depends on the Servlet package(Parameter and ReportParameterAction belong to two different packages, we can’t involve the dependency in the package that Parameter belong to). Second, ServletRequest interface is a huge interface, and it is difficult to implement. Consider about this situation: we want to do unit test for Parameter, it is very difficult to mock ServletRequest interface. In fact, we just want to invoke the getParameterValues() operation here. So, I provide the ParameterRequest interface:
public interface ParameterRequest {
public String[] getParameterValues(String name);
}
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);
}
}
}
}
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();
}
Because the parameter itself can process its data from the request, so ReportParameterAction doesn’t consider about these complex situations. The structure of the code becomes more clear, and the responsibilities of these objects becomes more accurate. It improves the extensibility of the program due to we do the correct encapsulation to remove the branch statement. That is a benifit of autonomous object. Please keep in your mind that autonomous principle and expert pattern while you want to assign the responsibilities.