NotOnlyJava

http://www.ibm.com/developerworks/cn/java/j-lo-serial/
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

用抽象实现代码解耦

Posted on 2013-09-05 20:29  NotOnlyJava  阅读(779)  评论(0编辑  收藏  举报

编程到接口:
GOF在著名的《设计模式》中写到:编程到接口,而不是编程到类。
编程到接口的最大好处就是剥离了功能与实现,使得我们在开发时只关注某个接口具有哪些功能,不用关心这些功能是怎么实现。

现实问题:
在最近的一次重构中,遇到一个问题:我们把对象添加到JTree里,但是JTree所显示的字符不友好,因为JTree默认调用对象的toString()来显示,而对象的toString()通常不适合显示给用户。

解决方案1:
为Jtree添加cellrender,代码如下:
<nowiki>
import java.awt.Component;

import javax.swing.JTree;
import javax.swing.tree.DefaultTreeCellRenderer;

 

public class MyTree extends JTree{

private static final long serialVersionUID = 1L;

public MyTree() {
this.setCellRenderer(new MyCellRender());
}

private static class MyCellRender extends DefaultTreeCellRenderer{

private static final long serialVersionUID = 1L;

/**
* 重写getTreeCellRendererComponent
* 以便JTree能显示友好的对象属性
*/
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean sel, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
if(value instanceof xxClass)
{
//获取xxClass的xx属性显示给用户
value = ((xxClass)(value)).getXXX();
}
return super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf,row, hasFocus);
}


}
}

</nowiki>
不可否认,这种方案解决了对象显示的不友好问题,但是却带来了以下问题:
1、误用了TreeCellRenderer,JDK的文档如此描述TreeCellRenderer:
定义针对显示树节点的对象的要求。有关实现显示自定义图标的树单元格渲染器的示例
TreeCellRenderer的功能是实现JTree节点的渲染,用来实现如checkbox这般特殊的
显示效果,而实例代码显然混淆了TreeCellRenderer的用法。我们的系统中大量存在
这种处理。
2、这个TreeCellRenderer 严重与具体的类耦合,缺乏扩展性
if(value instanceof xxClass)
{
//获取xxClass的xx属性显示给用户
value = ((xxClass)(value)).getXXX();
}
当我们树里的对象变化时,我们的代码会变成这样:
<nowiki>
if(value instanceof Object)
{
//获取xxClass的xx属性显示给用户
value = ((xxClass)(value)).getXXX();
}else if(value instanceof XXX)
{
//获取xxClass的xx属性显示给用户
value = ((xxClass)(value)).getXXX();

}else if(value instanceof XXX)
{
//获取xxClass的xx属性显示给用户
value = ((xxClass)(value)).getXXX();

}else if(value instanceof XXX)
{
//获取xxClass的xx属性显示给用户
value = ((xxClass)(value)).getXXX();

}


........
</nowiki>
在TreeCellRenderer 里决定对象是怎么显示通常是不明智的设计,这就好比肯德基规定只能用美元来
买汉堡,虽然能实现交易但显然很牵强,事实上,快餐店点不关心你用哪种货币来购买,只要你给足额的
钱即可,这样肯德基开到中国,开到日本,开到俄罗斯也无需关心这个国家是否流通美元。我们的程序
也应当具备这种扩展性,界面不关心对象究竟显示什么属性给用户,界面也不知道具体的对象究竟该显示
什么,那么这个显示值谁知道呢?当然是对象本身,不管有多少对象,有多少种对象,对象自身知道该怎么
显示给用户。要怎么实现这种解耦与扩展呢?显然,需要对对象的显示功能进行抽象,我们可以把这种功能
抽象为一个接口,这个接口只具备一种功能,那就是为UI提供友好的显示值:
<nowiki>
/**
* 可显示接口,主要用来转换一个在UI里的显示
*
*/
public interface IDisplay
{
/**
* 返回该对象的可显示label
* @return
*/
String getDisplayLabel();
}
</nowiki>
所有的UI界面面向这个接口编程,获取对象的显示属性,这样新增的对象只要实现这个接口,就可以友好的在UI显示
而无需改动我们的UI代码。这就是strategy模式的一种应用,当然叫templatemethod也没错,我们用这种方式重构
实例里问题,正如前文所述,JTree里对象的显示不应该依赖与toString()也不应该用TreeCellRenderer 来实现
观察JTree的代码,发现JTree显示一个对象主要调用以下方法来获取改对象的toString()
<nowiki>
public String convertValueToText(Object value, boolean selected,
boolean expanded, boolean leaf, int row,
boolean hasFocus) {
if(value != null) {
String sValue = value.toString();
if (sValue != null) {
return sValue;
}
}
return "";
}
</nowiki>
显然我们只要覆写该方法即可实现友好的显示:
<nowiki>
/**
* 能友好显示的树
*
*/
public class DisplayTree extends JTree
{

private static final long serialVersionUID = 1L;

/**
* 重写该方法避免加到树的对象显示依赖与toString()
*/
@Override
public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus)
{
if(value instanceof IDisplay)
{
IDisplay dis = (IDisplay) value;
return dis.getDisplayLabel();
}else if(value instanceof DefaultMutableTreeNode){
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
Object obj = node.getUserObject();
if(obj instanceof IDisplay)
{
IDisplay dis = (IDisplay) obj;
return dis.getDisplayLabel();
}else{
return super.convertValueToText(value, selected, expanded, leaf, row, hasFocus);
}
}
return super.convertValueToText(value, selected, expanded, leaf, row, hasFocus);
}


}
</nowiki>
方法里我们面向IDisplay接口编程,我们不关心JTree的node里到底是什么,我们只关心他是否具备可显示的功能,如果有则调用
getDisplayLabel();
这样就不会出现大量的if esle 新加一个对象到JTree也无需修改UI代码,假如我需要在这棵树上显示一个VPN对象,那么
可以这样编码:
<nowiki>
public class VPNBean implements IDisplay
{
。。。
@Override
public String getDisplayLabel()
{
return vpnName; }
}
</nowiki>
我要再显示一个Group对象,我也只需要这样编码:
<nowiki>
public class ExternalGroup implements IDisplay
{
。。。。
@Override
public String getDisplayLabel()
{
return (groupName==null?"":groupName)+"(ID:"+getGroupNumber()+")";
}
}
</nowiki>
。。。。
你会发现,这一切把JTree与具体的对象解耦了,同时扩展也容易了。即使你是老代码里的对象
implements IDisplay不会对你的代码造成任何破坏,实现了对老代码的兼容。

在我们开发时,应尽可能的对功能度进行抽象,并尽可能的面向这些抽象编程,这样代码较易维护、扩展与兼容。

完整的代码:

import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;

/**
 * 能友好显示的树
 * 
 * Create Date: 2013-8-23<br>
 * Description :
 */
public class DisplayTree extends JTree
{

    private static final long serialVersionUID = 1L;

    /**
     * 重写该方法避免加到树的对象显示依赖与toString()
     */
    @Override
    public  String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus)
    {
        if(value instanceof IDisplay)
        {
            IDisplay dis = (IDisplay) value;
            return dis.getDisplayLabel();
        }else if(value instanceof DefaultMutableTreeNode){
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
            Object obj = node.getUserObject();
            if(obj instanceof IDisplay)
            {
                IDisplay dis = (IDisplay) obj;
                return dis.getDisplayLabel();
            }else{
                return super.convertValueToText(value, selected, expanded, leaf, row, hasFocus);
            }
        }
        return super.convertValueToText(value, selected, expanded, leaf, row, hasFocus);
    }


}


/**
 * 可显示接口,主要用来转换一个在Swing里的显示
 * 
 * Create Date: 2013-8-23<br>
 * Description :
 */
public interface IDisplay
{
    /**
     * 返回该对象的可显示label
     * @return
     */
     String getDisplayLabel();
}