且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

引入间接隔离变化(一)

更新时间:2022-09-17 08:18:28

David Wheeler有一句名言:“计算机科学中的大多数问题都可以通过增加一层间接性来解决。”间接代表着迂回。世间没有哪一条道路是完全笔直的。蜿蜒曲折的道路并非出于美的灵感,不过是因为我们需要绕开路途中的障碍罢了。

我们在设计中遇到的最大障碍,无疑就是变化。若能御变化于实现之外,软件开发就会变得美好。

应对变化的要诀是隔离。设计者需要界定对象的不变部分与可变部分,然后将可变部分隐藏起来,即使发生了变化,也不会影响到外部。这就是封装的含义。正如地壳核心的变化如此的狂暴与迅捷,但对于地面上生活的人类而言,几乎微不可察。然而,一旦地壳的变化冲出地表,就会酿成天大的灾难。变化对软件系统造成的灾难,并不亚于地震或者火山。封装为对象内部的实现设定了一层隔离带,将复杂变化的业务逻辑或者算法策略隐藏在对象之内。只要保证对象的接口不发生变化,调用者与对象内部的实现就可以单独演化了。

当我们发现一个对象需要依赖另一个不稳定的对象,同时,还需要执行复杂的交互逻辑时,就可以考虑引入一个新的对象来封装这些逻辑,从而解除二者之间的耦合,隔离变化。Spring MVC中的ModelAndView对象扮演的正是这一角色。根据MVC模式,控制器需要将模型对象所持有的数据以及数据的变化呈现到视图中。它通过寻找正确的视图对象,完成页面的展现。控制器承担了这一职责,就意味它必须依赖于视图对象。例如这样的代码:

public class CustomerController implements Controller { 
    @Override 
    public View handleRequest( 
            HttpServletRequst request, 
            HttpServletResponse response) throws Exception { 
        Map model = new HashMap(); 
        model.put(“customers”, getCustomerList()); 
        return new InternalResourceView(”/WEB-INF/jsp/customerList.jsp”); 
    } 
}

View具体对象的创建,使得CustomerController与InternalResourceView紧紧地绑定起来,失去了灵活性,导致我们无法***改变View的实现。作为一个灵活的MVC框架,显然很难容忍这二者之间的强耦合。要打破这种耦合关系,就需要封装寻找以及创建视图的职责,并将这一职责放到合适的对象中。这正是引入ModelAndView类的缘由。Controller放心地将所有与View相关的职责转移给ModelAndView,而它只需要悠闲地传递一个视图名称即可。

public class CustomerController implements Controller { 
    @Override 
    public ModelAndView handleRequest( 
            HttpServletRequst request, 
            HttpServletResponse response) throws Exception { 
        Map model = new HashMap(); 
        model.put(“customers”, getCustomerList()); 
        return ModelAndView(”customerList”, model); 
    } 
}

通过字符串类型的名称常量去寻找合适的视图,而不是具体的View对象,就使得Controller冲破了View类型的约束,变得自在而开放。因为封装的作用,Controller对象变得无知,然而,“无知者无畏”,它也不用害怕视图呈现所发生的变化了。引入间接隔离变化(一)隔离变化的另一条途径是寻觅对象的共性,对这些共性进行抽象。我们不必考虑对象实现细节的不同之处,只需要把握对象的共同特征,即可完成接口的定义。接口可以看做是对象的角色。Rebecca认为:“客户访问接口比访问具体类要灵活得多,它们不需要知道具体实现,而只需明了接口中声明的公共角色即可。”【注:Rebecca Wirfs-Brock《对象设计:角色、责任和协作》】角色代表一种功能或职责的扮演,它并非演员本身,只是形象化地以某种形态或语言来表现角色的喜怒哀乐而已。例如,我们需要在项目中指定规则以限定渲染的格式。这个规则可以是数据区间,只要数据在这个区间范围之内,就应该设置为对应的格式;也可以是某种约束条件,当条件满足时,以相应的格式渲染。从实现细节来看,区间与约束是迥然不同的两种实现;可是从抽象的角度看,它们无疑扮演的都是同一种角色,那就是匹配器。只要规则匹配,就应该获得正确的格式。引入间接隔离变化(一)public interface Matcher { 
    public boolean matches(Object value); 
}

public class Range implements Matcher{ 
    private double min; 
    private double max; 
    public Range(double min, double max) { 
        this.min = min; 
        this.max = max; 
    } 
    private boolean in(double data) { 
        //判断data是否在此区间 
    }

    public boolean matches(Object value) { 
        try { 
            return in((double)value); 
        }catch (InvalidCastException) { 
            return false; 
        } 
    } 
}

public class Constraint implements Matcher { 
    private String expected; 
    private boolean ignoreCase; 
    public Constraint(String expected) { 
        this.expected = expected; 
        ignoreCase = true; 
    }

    public Constraint(String expected, boolean ignoreCase) { 
    } 
    public boolean matches(Object value) { 
        if (ignoreCase) { 
            return expected.equalsIgnoreCase(value.toString()); 
        } else { 
            return expected.equals(value.toString()); 
        } 
    } 
}

Matcher接口抽象了区间与约束的共性特征,使得二者在规则中能够友好相处: 
public class Rule { 
    public Rule(Matcher matcher, Format format){} 
    public Matcher getMatcher(){} 
    public void setMatcher(Matcher matcher){} 
    public Format getFormat(){} 
    public void setFormat(Format format){} 
}

如果需要更多的匹配器,只要实现Matcher接口,就可以放入Rule中,作为格式规则的一部分。这种包容变化的能力,正是抽象能够提供的。








本文转自wayfarer51CTO博客,原文链接:http://blog.51cto.com/wayfarer/478189,如需转载请自行联系原作者