设计模式笔记--新模式

 本章挑选了5个大家时常使用,但又经常忽视的新模式进行讲解,即规格模式、对象池模式、雇工模式、黑板模式、空对象模式。 

1、规格模式  

规格类 :它是对一批对象的说明性描述,它依照基准判断候选对象是否满足条件。 
设计模式笔记--新模式_第1张图片


基类代表的是所有的规格书,它的目的是描述一个完整的、可组合的规格书,它代表的是一个整体,其下的And规格书、Or规格书、Not规格书等都是一个真实的实现,也就是一个局部,现在我们又回到了整体和部分的关系了,那这是什么模式?对,组合模式,它是组合模式的一种特殊用

为什么在通用类图中把方法名称都定义出来呢?是因为只要使用规格模式,方法名称都是这四个,它是把组合模式更加具体化了,放在一个更狭小的应用空间中。

我们再仔细看看,还能不能找到其他模式的身影?对,策略模式,每个规格书都是一个策略,它完成了一系列逻辑的封装,用年龄相等的规格书替换年龄大于指定年龄的规格书上层逻辑有什么改变吗?不需要任何改变!

规格模式非常重要,它巧妙地实现了对象筛选功能  

通用源码  

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


组合规格书实现与或非的算法, 代码清单38-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);
}
}


与或非规格书代码分别如代码清单38-24至代码清单38-26所示。
代码清单38-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);
}
}


代码清单38-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);
}
}


代码清单38-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个实现类只要在适用规格模式的地方都完全相同,不用 做任何的修改,大家闭着眼照抄就成,   


要修改的是下面的规格书——业务规格书  
public class BizSpecification extends CompositeSpecification {
//基准对象
private Object obj;
public BizSpecification(Object _obj){
this.obj = _obj;
}
@Override
public boolean isSatisfiedBy(Object candidate) {
//根据基准对象和候选对象,进行业务判断,返回boolean
return false;
}
}


场景类  
public class Client {
public static void main(String[] args) {
//待分析的对象
ArrayList list = new ArrayList();
//定义两个业务规格书
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);
}
}
}
}



实例  筛选用户信息
设计模式笔记--新模式_第2张图片

设计模式笔记--新模式_第3张图片


在该类图中建立了一个规格书接口,它的作用就是定制各种各样的规格,比如名字相等的规格UserByNameEqual、年龄大于基准年龄的规格UserByAgeThan等,然后在用户操作类中采用该规格进行判断。

User类,如代码清单38-1所示
public class User {
//姓名
private String name;
//年龄
private int age;
public User(String _name,int _age){
this.name = _name;
this.age = _age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//用户信息打印
@Override
public String toString(){
return "用户名:" + name+"\t年龄:" + age;
}
}

规格书接口是对全体规格书的声明定义,  规格书接口只定义一个方法,判断候选用户是否满足条件。 带与或非的规格书接口    
public interface IUserSpecification {
//候选者是否满足要求
public boolean isSatisfiedBy(User user);
//and操作
public IUserSpecification and(IUserSpecification spec);
//or操作
public IUserSpecification or(IUserSpecification spec);
//not操作
public IUserSpecification not();
}
注意 父类依赖子类的情景只有在非常明确不会发生变化的场景中存在,它不具备扩展 性,是一种固化而不可变化的结构。  
 

组合规格书  
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了个子类,如此设计非常符合单一职责原则,每个子类都有一个独立的职责,要么完成“与”操作,要么完成“或”操作,要么完成“非”操作  

我们先来看“与”操作规格书,如代码清单38-17所示。
代码清单38-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操作的结果。或规格书和非规格书与此类似,分别如代码清单38-18、代码清单38-19所示。
代码清单38-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);
}
}

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


姓名相同规格书  
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);
}
}


大于基准年龄的规格书  
public class UserByAgeThan implements IUserSpecification {
//基准年龄
private int age;
//构造函数传递基准年龄
public UserByAgeThan(int _age){
this.age = _age;
}
//检验用户是否满足条件
public boolean isSatisfiedBy(User user) {
return user.getAge() > age;
}
}


看用户操作类  ,用户操作接口
public interface IUserProvider {
//根据条件查找用户
public ArrayList findUser(IUserSpecification userSpec);
}


用户操作接口实现类
public class UserProvider implements IUserProvider {
//用户列表
private ArrayList userList;
//传递用户列表
public UserProvider(ArrayList _userList){
this.userList = _userList;
}
//根据指定的规格书查找用户
public ArrayList findUser(IUserSpecification userSpec) {
ArrayList result = new ArrayList();
for(User u:userList){
if(userSpec.isSatisfiedBy(u)){//符合指定规格
result.add(u);
}
}
return result;
}
}



场景类 
public class Client {
public static void main(String[] args) {
//首先初始化一批用户
ArrayList userList = new ArrayList();
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);
}
}
}



2、对象池模式  

对象池模式,或者称为对象池服务,其意图如下:
通过循环使用对象,减少资源在初始化和释放时的昂贵损耗[1]  

注意 这里的“昂贵”可能是时间效益(如性能),也可能是空间效益(如并行处理),在大多的情况下,“昂贵”指性能。  

简单地说,在需要时,从池中提取;不用时,放回池中,等待下一个请求。  


设计模式笔记--新模式_第4张图片

对象池提供两个公共的方法:checkOut负责从池中提取对象,checkIn负责把回收对象(当然,很多时候checkIn已经自动化处理,不需要显式声明,如连接池),对象池代码如代码清单38-31所示。

代码清单38-31 对象池示例代码
public abstract class ObjectPool {
//容器,容纳对象
private Hashtable pool = new Hashtable();
//初始化时创建对象,并放入到池中
public ObjectPool() {
pool.put(create(), new ObjectStatus());
}
//从Hashtable中取出空闲元素
public synchronized T checkOut() {
//这是最简单的策略
for (T t : pool.keySet()) {
if (pool.get(t).validate()) {
pool.get(t).setUsing();
return t;
}
}
return null;
}
//归还对象
public synchronized void checkIn(T t) {
pool.get(t).setFree();
}
class ObjectStatus {
//占用
public void setUsing() {
}
//释放
public void setFree() {
//注意:若T是有状态,则需要回归到初始化状态
}
//检查是否可用
public boolean validate() {
return false;
}
}
//创建池化对象
public abstract T create();
}

这是一个简单的对象池实现,在实际应用中还需要考虑池的最小值、最大值、池化对象状态(若有的话,需要重点考虑)、异常处理(如满池情况)等方面,特别是池化对象状态,若是有状态的业务对象则需要重点关注。  

把对象池化的本意是期望一次性初始化所有对象,减少对象在初始化上的昂贵性能开销,从而提高系统整体性能。然而池化处理本身也要付出代价,因此,并非任何情况下都适合采用对象池化。

通常情况下,在重复生成对象的操作成为影响性能的关键因素时,才适合进行对象池化。但是若池化所能带来的性能提高并不显著或重要的话,建议放弃对象池化技术,以保持代码的简明,转而使用更好的硬件来提高性能为佳。

对象池技术在Java领域已经非常成熟,只要做过企业级开发的人员,基本都用过C3P0、DBCP、Proxool等连接池,也配置过minPoolSize、maxPoolSize等参数,这是对象池模式的典型应用。在实际开发中若需要对象池,建议使用common-pool工具包来实现,简单、快捷、高效。  



3、雇工模式  

雇工模式也叫做仆人模式(Servant Design Pattern),其意图是:
雇工模式是行为模式的一种,它为一组类提供通用的功能,而不需要类实现这些功能,它是命令模式的一种扩展。  


设计模式笔记--新模式_第5张图片

在类图中,IServiced是用于定义“一组类”所具有的功能,其示例代码如代码清单38-35所示。
代码清单38-35 通用功能
public interface IServiced {
//具有的特质或功能
public void serviced();
}


针对不同的服务对象具备不同的服务内容,也就是具体的功能实现IServiced接口即可,示例代码如代码清单38-36所示。
代码清单38-36 定义具体功能
public class Serviced1 implements IServiced {
public void serviced(){
}
}p
ublic class Serviced2 implements IServiced{
public void serviced(){
}
}


功能定义完毕后,我们需要由一个雇工来执行这些功能。简单地说,就是需要有一个执
行者,可以把一组功能聚集起来,示例代码如代码清单38-37所示。  
代码清单38-37 雇工类
public class Servant {
//服务内容
public void service(IServiced serviceFuture){
serviceFuture.serviced();
}
}


在整个雇工模式中,所有具有IServiced功能的类可以实现该接口,然后由雇工类Servant
进行集合,完成一组类不用实现通用功能而具有相应职能的目的。  

会发现雇工模式是命令模式的一种简化,但它更符合我们实际的需要,更容易引入开发场景中  


4、黑板模式  

黑板模式(Blackboard Design Pattern)是观察者模式的一个扩展,知名度并不高,但是我们使用的范围却非常广。 
允许消息的读写同时进行,广泛地交互消息。  

简单地说,黑板模式允许多个消息读写者同时存在,消息的生产者和消费者完全分开。
这就像一个黑板,任何一个教授(消息的生产者)都可以在其上书写消息,任何一个学生(消息的消费者)都可以从黑板上读取消息,两者在空间和时间上可以解耦,并且互不干扰。  

黑板模式确实是消息的广播,主要解决的问题是消息的生产者和消费者之间的耦合问题,它的核心是消息存储(黑板),它存储所有消息,并可以随时被读取。当消息生产者把消息写入到消息仓库后,其他消费者就可以从仓库中读取。当然,此时消息的写入者也可以变身为消息的阅读者,读写者在时间上解耦。对于这些消息,消费者只需要关注特定消息,不处理与自己不相关的消息,这一点通常通过过滤器来实现。  

黑板模式一般不会对架构产生什么影响,但它通常会要求有一个清晰的消息结构。黑板模式一般都会提供一系列的过滤器,以便消息的消费者不再接触到与自己无关的消息。在实际开发中,黑板模式常见的有两种实现方式。
● 数据库作为黑板
利用数据库充当黑板,生产者更新数据信息,不同的消费者共享数据库中信息,这是最常见的实现方式。该方式在技术上容易实现,开发量较少,熟悉度较高。缺点是在大量消息和高频率访问的情况下,性能会受到一定影响。
在该模式下,消息的读取是通过消费者主动“拉取”,因此该模式也叫做“拉模式”。
● 消息队列作为黑板
以消息队列作为黑板,通过订阅-发布模型即可实现黑板模式。这也是黑板模式被淡忘的一个重要原因:消息队列(Message Queue)已经非常普及了,Java开发的已经没有几个不知道消息队列的。
在该模式下,消费者接收到的消息是被主动推送过来的,因此该模式也称为“推模式”。

提示 黑板模式不做详细讲解,因为我们现在已经在大量使用消息队列,既可以做到消息的同步处理,也可以实现异步处理,相信大家已经在开发中广泛使用了,它已经成为跨系统交互的一个事实标准了。  



5、空对象模式  

空对象模式(Null Object Pattern)是通过实现一个默认的无意义对象来避免null值出现,
简单地说,就是为了避免在程序中出现null值判断而诞生的一种常用设计方法。  

举个简单的例子来说明,我们写一个听动物叫声的模拟程序,如代码清单38-38所示。
代码清单38-38 动物叫声
//定义动物接口
public interface Animal {
public void makeSound();
}


定义一个小狗
class Dog implements Animal{
public void makeSound(){
System.out.println(“Wang Wang Wang!”);
}
}


然后再定义一个人来听动物的叫声,如代码清单38-39所示。
代码清单38-39 听动物叫声的人
public class Person {
//听到动物叫声
public void hear(Animal animal){
if(animal !=null){
animal.makeSound();
}
}
}


也许你觉得程序没有什么问题,输入参数animal是应该做空值判断。

但是,我们这样思考:在一个完整的系统中,animal对象是如何产生?什么原因会产生null值?如果我们能够控制住null值的产生,是不是就可以去掉这个空值判断了?

那这样,程序是不是更易读更简单?好,我们就编写一个更完美的程序,增加一个NullAnimal类,如代码清单38-40所示。
代码清单38-40 增加一个NullAnimal
class NullAnimal implements Animal{
public void makeSound(){
}
}

增加了NullAnimal类后,在Person类中就不需要"animal!=null"这句话了,因为我们提供了一个实现接口的所有方法,不会再产生null对象。想象一个Web项目中,animal对象可能由MVC框架映射产生,我们只要定义一个默认的映射对象是NullAnimal,就可以解决空值判断的问题,提升代码的可读性。这就是空对象模式(一些项目组把它作为编码规范的一部分),非常简单,但非常实用。

空对象模式是通过空代码实现一个接口或抽象类的所有方法,以满足开发需求,简化程序。它如此简单,以至于我们经常在代码中看到和使用,对它已经熟视无睹了,而它无论是事前规划或事后重构,都不会对我们的代码产生太大冲击,这也是我们“藐视”它的根本原  

你可能感兴趣的:(Android之设计模式,设计模式,android,新模式)