王大虎

王大虎:与规格的示意

王大虎:

基类对子类产生了依赖,然X行递归计算,大家一定会发出这样的疑问:父类怎么可能依赖子类,这还是面向接口编程吗?想想看,我们提出面向接口编程的目的是什么?是为了适应变化,拥抱变化,对于不可能发生变化的部分为什么不能固化呢?与或非操作符号还会增加修改吗?规格书对象之间的操作还有其他吗?思考清楚这些问题后,答案就迎刃而解了。

注意 父类依赖子类的情景只有在非常明确不会发生变化的场景中存在,它不具备扩展性,是一种固化而不可变化的结构。

分析完毕,我们设计出详细的类图,如图37-4所示。

 

图37-4 完整规格书类图

可能大家有很多的疑问,我们先来分析代码,代码分析完毕估计能解决你大部分的疑问。规格书接口如代码清单37-15所示,不再赘述。我们来看组合规格书(CompositeSpecification),它是一个抽象类,实现了与或非的操作,如代码清单37-16所示。

代码清单37-16 组合规格书

 

* * *

 

public abstract class CompositeSpecification implements IUserSpecification { //是否满足条件由实现类实现 public abstract boolean isSatisfiedBy(User user); //and操作 public IUserSpecification and(IUserSpecification spec) { return new AndSpecification(this,spec); } //not操作 public IUserSpecification not() { return new NotSpecification(this); } //or操作 public IUserSpecification or(IUserSpecification spec) { return new OrSpecification(this,spec); } }

 

王大虎:

 

候选对象是否满足条件是由isSatisfiedBy方法决定的,它代表的是一个判断逻辑,由各个实现类实现。三个与或非操作在抽象类中实现,它是通过直接new了一个子类,如此设计非常符合单一职责原则,每个子类都有一个独立的职责,要么完成“与”操作,要么完成“或”操作,要么完成“非”操作。我们先来看“与”操作规格书,如代码清单37-17所示。

代码清单37-17 与规格书

 

* * *

 

public class AndSpecification extends CompositeSpecification { //传递两个规格书进行and操作 private IUserSpecification left; private IUserSpecification right; public AndSpecification(IUserSpecification _left,IUserSpecification _right){ this.left = _left; this.right = _right; } //进行and运算 @Override public boolean isSatisfiedBy(User user) { return left.isSatisfiedBy(user) && right.isSatisfiedBy(user); } }

 

* * *

 

通过构造函数传递过来两个需要操作的规格书,然后通过isSatisfiedBy方法返回两者and操作的结果。或规格书和非规格书与此类似,分别如代码清单37-18、37-19所示。

代码清单37-18 或规格书

 

王大虎:

 

public class OrSpecification extends CompositeSpecification { //左右两个规格书 private IUserSpecification left; private IUserSpecification right; public OrSpecification(IUserSpecification _left,IUserSpecification _right){ this.left = _left; this.right = _right; } //or运算 @Override public boolean isSatisfiedBy(User user) { return left.isSatisfiedBy(user) || right.isSatisfiedBy(user); } }

 

* * *

 

代码清单37-19 非规格书

 

* * *

 

public class NotSpecification extends CompositeSpecification { //传递一个规格书 private IUserSpecification spec; public NotSpecification(IUserSpecification _spec){ this.spec = _spec; } //not操作 @Override public boolean isSatisfiedBy(User user) { return !spec.isSatisfiedBy(user); } }

 

* * *

 

这三个规格书都是不发生变化的,只要使用该框架,三个规格书都要实现的,而且代码基本上是雷同的,所以才有了父类依赖子类的设计,否则是严禁出现父类依赖子类的情况的。大家再仔细看看这三个规格书和组合规格书,代码很简单,但也很巧妙,它跳出了我们面向对象设计的思维,不变部分使用一种固化方式实现。

姓名相同、年龄大于基准年龄、Like格式等规格书都有少许改变,把实现接口变为继承基类,我们以名字相等规格书为例,如代码清单37-20所示。

代码清单37-20 姓名相同规格书

 

王大虎:

 

public class UserByNameEqual extends CompositeSpecification { //基准姓名 private String name; //构造函数传递基准姓名 public UserByNameEqual(String _name){ this.name = _name; } //检验用户是否满足条件 public boolean isSatisfiedBy(User user) { return user.getName().equals(name); } }

 

王大虎
王大虎

 

仅仅修改了黑体部分,其他没有任何改变。另外两个规格书修改相同,不再赘述。其他的User及UserProvider没有任何改动,不再赘述。

我们修改一下场景类,如代码清单37-21所示。

代码清单37-21 场景类

 

* * *

 

public class Client { public static void main(String[] args) { //首先初始化一批用户 ArrayList<User> userList = new ArrayList<User>(); userList.add(new User(“苏国庆”,23)); userList.add(new User(“国庆牛”,82)); userList.add(new User(“张国庆三”,10)); userList.add(new User(“李四”,10)); //定义一个用户查询类 IUserProvider userProvider = new UserProvider(userList); //打印出名字包含”国庆”的人员 System.out.println(“===名字包含国庆的人员===”); //定义一个规格书 IUserSpecification spec = new UserByAgeThan(25); IUserSpecification spec2 = new UserByNameLike(“%国庆%”); for(User u:userProvider.findUser(spec.and(spec2))){ System.out.println(u); } } }

 

* * *

 

在场景类中我们建立了两个规格书,一个是年龄大于25的用户,一个是名字中包含“国庆”两个字的用户,这两个规格书之间的关系是“与”关系,运行结果如下:

 

* * *

 

===名字包含国庆的人员=== 用户名:国庆牛 年龄:82

 

* * *

 

到此为止我们的LINQ已经完成了很大一部分了,SQL语句中的where后面部分已经可以解析了,完全可以再增加年龄相等的规格书、姓名字数规格书等等,你在SQL中使用过的条件在这里都能实现了。功臣还是依赖于三个与或非规格书,有了它们三个栋梁才能组合出一个精彩的条件查询世界。

 

 

37.2 最佳实践

王大虎

我们在例子中多次提到规格两个字,该实现模式就叫做规格模式(Specification Pattern),它不属于23个设计模式,它是其中一个模式的扩展,是哪个模式呢?

我们用全局的观点思考一下,基类代表的是所有的规格书,它的目的是描述一个完整的、可组合的规格书,它代表的是一个整体,其下的And规格书、Or规格书、Not规格书、年龄大于基准年龄规格书等等都是一个真实的实现,也就是一个局部,现在我们又回到了整体和部分的关系了,那这是什么模式?对,组合模式,它是组合模式的一种特殊应用,我们来看它的通用类图,如图37-5所示。

 

图37-5 规格模式通用类图

为什么在通用类图中把方法名称都定义出来呢?是因为只要使用规格模式,方法名称都是这四个,它是把组合模式更加具体化了,放在一个更狭小的应用空间中。我们再仔细看看,还能不能找到其他模式的身影?对,策略模式,每个规格书都是一个策略,它完成了一系列逻辑的封装,用年龄相等的规格书替换年龄大于指定年龄的规格书上层逻辑有什么改变吗?不需要任何改变!

规格模式非常重要,它巧妙地实现了对象筛选功能。我们来看其通用源码,首先看抽象规格书,如代码清单37-22所示。

代码清单37-22 抽象规格书

 

* * *

 

public interface ISpecification { //候选者是否满足要求 public boolean isSatisfiedBy(Object candidate); //and操作 public ISpecification and(ISpecification spec); //or操作 public ISpecification or(ISpecification spec); //not操作 public ISpecification not(); }

 

* * *

 

组合规格书实现与或非的算法,如代码清单37-23所示。

代码清单37-23 组合规格书

 

* * *

 

public abstract class CompositeSpecification implements ISpecification { //是否满足条件由实现类实现 public abstract boolean isSatisfiedBy(Object candidate); //and操作 public ISpecification and(ISpecification spec) { return new AndSpecification(this,spec); } //not操作 public ISpecification not() { return new NotSpecification(this); } //or操作 public ISpecification or(ISpecification spec) { return new OrSpecification(this,spec); } }

 

* * *

 

与或非规格书代码分别如代码清单37-24、37-25、37-26所示。

代码清单37-24 与规格书

 

* * *

 

public class AndSpecification extends CompositeSpecification { //传递两个规格书进行and操作 private ISpecification left; private ISpecification right; public AndSpecification(ISpecification _left,ISpecification _right){ this.left = _left; this.right = _right; } //进行and运算 @Override public boolean isSatisfiedBy(Object candidate) { return left.isSatisfiedBy(candidate) && right.isSatisfiedBy(candidate); } }

 

* * *

 

代码清单37-25 或规格书

 

* * *

 

public class OrSpecification extends CompositeSpecification { //左右两个规格书 private ISpecification left; private ISpecification right; public OrSpecification(ISpecification _left,ISpecification _right){ this.left = _left; this.right = _right; } //or运算 @Override public boolean isSatisfiedBy(Object candidate) { return left.isSatisfiedBy(candidate) || right.isSatisfiedBy(candidate); } }

 

* * *

 

代码清单37-26 非规格书

 

* * *

 

public class NotSpecification extends CompositeSpecification { //传递一个规格书 private ISpecification spec; public NotSpecification(ISpecification _spec){ this.spec = _spec; } //not操作 @Override public boolean isSatisfiedBy(Object candidate) { return !spec.isSatisfiedBy(candidate); } }

 

* * *

 

以上一个接口、一个抽象类、3个实现类只要在适用规格模式的地方都完全相同,不用做任何的修改,大家闭着眼照抄就成,要修改的是下面的规格书——业务规格书,如代码清单37-27所示。

代码清单37-27 业务规格书

 

* * *

 

public class BizSpecification extends CompositeSpecification { //基准对象 private Object obj; public BizSpecification(Object _obj){ this.obj = _obj; } @Override public boolean isSatisfiedBy(Object candidate) { //根据基准对象和候选对象,进行业务判断,返回boolean return false; } }

 

* * *

 

然后就是看怎么使用了,场景类如代码清单37-28所示。

代码清单37-28 场景类

 

* * *

 

public class Client { public static void main(String[] args) { //待分析的对象 ArrayList<Object> list = new ArrayList<Object>(); //定义两个业务规格书 ISpecification spec1 = new BizSpecification(new Object()); ISpecification spec2 = new BizSpecification(new Object()); //规则的调用 for(Object obj:list){ if(spec1.and(spec2).isSatisfiedBy(obj)){ //and操作 System.out.println(obj); } } } }

 

* * *

 

规格模式已经是一个非常具体的应用框架了(相对于23个设计模式),大家遇到类似多个对象中筛选查找,或者业务规则不适于放在任何已有实体或值对象中,而且规则的变化和组合会掩盖那些领域对象的基本含义,或者是想自己编写一个类似LINQ的语言工具的时候就可以照搬这部分代码,只要实现自己的逻辑规格书即可。

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注