设计模式--行为型模式(上)

模板方法模式(Template Method Pattern):

定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。模板方法是一种类行为型模式。
模板方法模式是一种基于继承的代码复用技术,它是一种类行为型模式。

模板方法结构:

 

模式分析:

对于所有子类都相同的基本方法可在父类提供具体实现,否则在父类声明为抽象方法或钩子方法,由不同的子类提供不同的实现。

抽象父类:

abstract class AbstractClass   {  
    //模板方法
    public void TemplateMethod()  {
        PrimitiveOperation1();
        PrimitiveOperation2();
        PrimitiveOperation3(); 
    } 
    //基本方法—具体方法
    public void PrimitiveOperation1()  {  //实现代码  }
    //基本方法—抽象方法
    public abstract void PrimitiveOperation2();
    //基本方法—钩子方法
    public virtual void PrimitiveOperation3()   {  }
} 

子类:

class ConcreteClass extands  AbstractClass   {  
     public override void PrimitiveOperation2()   
     {  
         //实现代码
     }  
  
     public override void PrimitiveOperation3()   
     {  
         //实现代码  
     }
}

模板方法实例:

实例:银行业务办理流程
在银行办理业务时,一般都包含几个基本步骤,首先需要取号排队,然后办理具体业务,最后需要对银行工作人员进行评分。

无论具体业务是取款、存款还是转账,其基本流程都一样。现使用模板方法模式模拟银行业务办理流程。

结构分析:

 

代码实现:

//=====================
//抽象父类实现了两个固定方法排队取号,反馈评分,还有一个未实现方法transact
//因为业务不同,手续不同,不可一概而论,然后把三个方法当做同意流程打包进process()方法
public abstract class BankTemplateMethod
{
    public void takeNumber()
    {
        System.out.println("取号排队。");
    }
    
    public abstract void transact();
    
    public void evaluate()
    {
        System.out.println("反馈评分。");
    }

    public void process()
    {
        this.takeNumber();
        this.transact();
        this.evaluate();
    }
} 

//=====================
//转账业务,只要实现转账业务即可
public class Transfer extends BankTemplateMethod
{
    public void transact()
    {
        System.out.println("转账");        
    }
}


//====================
//存款取款也是一样
public class Deposit extends BankTemplateMethod
{
    public void transact()
    {
        System.out.println("存款");        
    }
}

//========================
public class Withdraw extends BankTemplateMethod
{
    public void transact()
    {
        System.out.println("取款");        
    }
}

//=========================
public class Client
{
    public static void main(String a[])
    {
        BankTemplateMethod bank;
        bank=new Transfer() ;//客户要取款
        bank.process();
        System.out.println("---------------------------------------");
    }
}

 

模板方法模式的注意事项和细节
1) 基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
2) 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
3) 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
4) 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
5) 一般模板方法都加上final关键字, 防止子类重写模板方法.
6) 模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤 ,这一系列的步骤基本相同,但其个别步骤在实现时 可能不同,通常考虑用模板方法模式来处理。

 

命令模式:

类似于开关与电灯排气扇,一个开关在安装之后可能用来控制电灯,也可能用来控制排气扇或者其他电器设备。他们直接没有直接关系。开关与电器之间通过电线建立连接,

如果开关打开,则电线通电,电器工作;反之,开关关闭,电线断电,电器停止工作。

 

 

我们可以将开关理解成一个请求的发送者,用户通过它来发送一个“开灯”请求,而电灯是“开灯”请求的最终接收者和处理者,在图中,开关和电灯之间并不存在直接耦合关系

它们通过电线连接在一起,使用不同的电线可以连接不同的请求接收者,只需更换一根电线,相同的发送者(开关)即可对应不同的接收者 (电器)、

在软件开发中也存在很多与开关和电器类似的请求发送者和接收者对象,例如一个按钮,它可能是一个“关闭窗口”请求的发送者,而按钮点击事件处理类则是该请求的接收者。

此时,我们特别希望能够以一种松耦合的方式来设计软件,使得请求发送者与请求接收者能够消除彼此之间的耦合,让对象之间的调用关系更加灵活,可以灵活地指定请求

接收者以及被请求的操作。命令模式为此类问题提供了一个较为完美的解决方案。

命令模式可以将请求发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。

 

 结构模式:

 

模式分析:

命令模式的本质是对请求进行封装,一个请求对应于一个命令,将发出请求的职责和执行请求的职责分割开。
每一个请求都对应一个操作:发送者发出请求,要求执行一个操作;接收者收到请求,并执行操作。
命令模式允许请求者和接收者相互独立,使得请求者不必知道接收者的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。
命令模式的关键在于引入了抽象命令类,请求发送者针对抽象命令类编程,只有实现了抽象命令类的具体命令才与请求接收者相关联。

在最简单的抽象命令类中只包含了一个抽象的execute()方法,其典型代码如下所示:

public abstract class Command
{
    public abstract void execute();
} 

具体命令类继承了抽象命令类,它与请求接收者相关联,实现了在抽象命令类中声明的execute()方法,并在实现时调用接收者的请求响应方法action(),

不同的具体命令类提供了execute()方法的不同实现,并调用不同接收者的请求处理方法。其典型代码如下所示:

public class ConcreteCommand extends Command
{
    private Receiver receiver;       //维持一个对请求接收者对象的引用
    public void execute()
    {
        receiver.action();         //调用请求接收者的业务处理方法
    }
} 

对于请求发送者即调用者而言,将针对抽象命令类进行编程,可以通过构造注入或者设值注入的方式在运行时传入具体命令类对象,

并在业务方法中调用命令对象的execute()方法,其典型代码如下所示:

public class Invoker
{
    private Command command;       //维持一个对命令对象的引用
    public Invoker(Command command)                        //构造注入 
    {          this.command=command;          }
    public void setCommand(Command command)       //设值注入 
    {          this.command=command;          }
    public void call()    //业务方法,用于调用命令类的方法
    {         command.execute();         }
} 

请求接收者Receiver类具体实现对请求的业务处理,它提供了action()方法,用于执行与请求相关的操作,其典型代码如下所示:

public class Receiver
{
    public void action()
    {
        //具体操作
    }
} 

命令模式实例:

实例一:电视机遥控器
电视机是请求的接收者,遥控器是请求的发送者,遥控器上有一些按钮,不同的按钮对应电视机的不同操作。抽象命令角色由一个命令接口来扮演,

有三个具体的命令类实现了抽象命令接口,这三个具体命令类分别代表三种操作:打开电视机、关闭电视机和切换频道。显然,电视机遥控器就是一个典型的命令模式应用实例。

实例结构:

 

 

 代码示例:

//==============
//基础的抽象命令
public interface AbstractCommand
{
    public void execute();
}

//===================
//具体命令的实现,开关电视,换台,命令实现的时候就固定好了接收者,使用接收者的方法
public class TVOpenCommand implements AbstractCommand
{
    private Television tv;
    public TVOpenCommand()
    {
        tv = new Television();
    }
    public void execute()
    {
        tv.open();
    }
}

//===============
public class TVChangeCommand implements AbstractCommand
{
    private Television tv;
    public TVChangeCommand()
    {
        tv = new Television();
    }
    public void execute()
    {
        tv.changeChannel();
    }
}

//==============
public class TVCloseCommand implements AbstractCommand
{
    private Television tv;
    public TVCloseCommand()
    {
        tv = new Television();
    }
    public void execute()
    {
        tv.close();
    }
}

//===================
//接收者,在这里就是电视机类,实现方法供命令类使用
public class Television
{
    public void open()
    {
        System.out.println("打开电视机!");
    }
    
    public void close()
    {
        System.out.println("关闭电视机!");        
    }
    
    public void changeChannel()
    {
        System.out.println("切换电视频道!");
    }
}

//===============
//开关控制器,只负责调用命令,不知道命令的具体是怎么实现的,如果命令改了,想去控制空调了,它也不一定用改
public class Controller
{
    private AbstractCommand openCommand,closeCommand,changeCommand;
    
    public Controller(AbstractCommand openCommand,AbstractCommand closeCommand,AbstractCommand changeCommand)
    {
        this.openCommand=openCommand;
        this.closeCommand=closeCommand;
        this.changeCommand=changeCommand;
    }
    
    public void open()
    {
        openCommand.execute();
    }
    
    public void change()
    {
        changeCommand.execute();
    }    

    public void close()
    {
         closeCommand.execute();    
    }
}

//====================
//客户端的调用
public class Client
{
    public static void main(String a[])
    {
        AbstractCommand openCommand,closeCommand,changeCommand;
        
        openCommand = new TVOpenCommand();
        closeCommand = new TVCloseCommand();
        changeCommand = new TVChangeCommand();
        
        Controller control = new Controller(openCommand,closeCommand,changeCommand);
        
        control.open();
        control.change();
        control.close();
    }
}

命令模式的注意事项和细节
1) 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的方法就可以让接收者工作,

而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作

也就是说:”请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。

2) 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令

3) 容易实现对请求的撤销和重做

4) 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在在使用的时候要注意

5) 空命令也是一种设计模式,它为我们省去了判空的操作。

6) 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS命令)订单的撤销/恢复、触发-反馈机制。

 

访问者模式:

模式动机:

(英文是对应着下面结构图中的位置)

对于系统中的某些对象,它们存储在同一个集合中(ObjectStucture中的List),且具有不同的类型,而且对于该集合中的对象(List中的Element),

可以接受一类称为访问者(Visitor)的对象来访问,而且不同的访问者其访问方式有所不同,访问者模式为解决这类问题而诞生。

访问者模式(Visitor Pattern):表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

访问者模式是一种对象行为型模式。

模式结构:

 

 

 

访问者模式中对象结构存储了不同类型的元素对象,以供不同访问者访问。

访问者模式包括两个层次结构,一个是访问者层次结构,提供了抽象访问者和具体访问者,一个是元素层次结构,提供了抽象元素和具体元素。

相同的访问者可以以不同的方式访问不同的元素,相同的元素可以接受不同访问者以不同访问方式访问。

在访问者模式中,增加新的访问者无须修改原有系统,系统具有较好的可扩展性。

典型的抽象访问者类代码如下所示:

public abstract class Visitor
{
    public abstract void visit(ConcreteElementA elementA);
    public abstract void visit(ConcreteElementB elementB);
    public void visit(ConcreteElementC elementC)
    {
        //元素ConcreteElementC操作代码
    }
} 

典型的具体访问者类代码如下所示:

public class ConcreteVisitor extends Visitor
{
    public void visit(ConcreteElementA elementA)
    {
        //元素ConcreteElementA操作代码
    }
    public void visit(ConcreteElementB elementB)
    {
        //元素ConcreteElementB操作代码
    }
} 

典型的抽象元素类代码如下所示:

public interface Element
{
    public void accept(Visitor visitor);
} 

典型的具体元素类代码如下所示:

public class ConcreteElementA implements Element
{
    public void accept(Visitor visitor)
    {
        visitor.visit(this);
    }
    
    public void operationA()
    {
        //业务方法
    }
} 

典型的对象结构类代码如下所示:

public class ObjectStructure
{
    private ArrayList list=new ArrayList();
    public void accept(Visitor visitor)
    {
        Iterator i=list.iterator();
        
        while(i.hasNext())
        {
            ((Element)i.next()).accept(visitor);    
        }
    }
    public void addElement(Element element)
    {
        list.add(element);
    }
    public void removeElement(Element element)
    {
        list.remove(element);
    }
} 

访问者模式的实例:

实例:购物车

顾客在超市中将选择的商品,如苹果、图书等放在购物车中,然后到收银员处付款。在购物过程中,顾客需要对这些商品进行访问,以便确认这些商品的质量,

之后收银员计算价格时也需要访问购物车内顾客所选择的商品。此时,购物车作为一个ObjectStructure(对象结构)用于存储各种类型的商品,而顾客和收银

员作为访问这些商品的访问者,他们需要对商品进行检查和计价。不同类型的商品其访问形式也可能不同,如苹果需要过秤之后再计价,而图书不需要。

使用访问者模式来设计该购物过程。

代码示例://=================

//Product产品时购物车(ObjectStructure对象结构)中的Element元素,可以被Visitor访问
public interface Product
{
    void accept(Visitor visitor);
}

//==================
//水果与书籍,是产品的实现
public class Apple implements Product
{
  public void accept(Visitor visitor)//注意这里的转换,产品接受访问者的访问,内部实现就是访问者访问了对象
{ visitor.visit(
this);
} }
//================ public class Book implements Product { public void accept(Visitor visitor) { visitor.visit(this); } } //===================== //购物车,里面有List存放这些产品,能对产品进行增删操作 //这里类似于组合模式,在购物车这个层面上被访问,实际访问的是产品 public class BuyBasket { private ArrayList list=new ArrayList(); public void accept(Visitor visitor) { Iterator i=list.iterator(); while(i.hasNext()) { ((Product)i.next()).accept(visitor); } } public void addProduct(Product product) { list.add(product); } public void removeProduct(Product product) { list.remove(product); } } //====================== //Vistor抽象类,使用多态,访问不同产品,实现也要不同 //相对的不同vistor访问产品,发生的事件也不同,顾客访问就是要买,收银员访问是为了卖 public abstract class Visitor { protected String name; public void setName(String name) { this.name=name; } public abstract void visit(Apple apple); public abstract void visit(Book book); } //================== //顾客实现 public class Customer extends Visitor { public void visit(Apple apple) { System.out.println("顾客" + name + "选苹果。"); } public void visit(Book book) { System.out.println("顾客" + name + "买书。"); } } //======================== //收银员实现 public class Saler extends Visitor { public void visit(Apple apple) { System.out.println("收银员" + name + "给苹果过秤,然后计算其价格。"); } public void visit(Book book) { System.out.println("收银员" + name + "直接计算书的价格。"); } } //============== //用户端代码 public class Client { public static void main(String a[]) { Product b1=new Book(); Product b2=new Book(); Product a1=new Apple(); Visitor visitor; BuyBasket basket=new BuyBasket(); basket.addProduct(b1); basket.addProduct(b2); basket.addProduct(a1); visitor=new Customer(); visitor.setName("张三"); basket.accept(visitor); } }

 

访问者模式的注意事项和细节
 优点
1) 访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高
2) 访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据结构相对稳定的系统
 缺点
1) 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的, 这样造成了具体元素变更比较困难
2) 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素
3) 因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的.

 

迭代器模式:

在现实生活中,我们都通过遥控器来操控电视机,进行开机、关机、换台、改变音量等操作时都无须直接操作电视机。
我们可以将电视机看成一个存储电视频道的集合对象,通过遥控器可以对电视机中的电视频道集合进行操作,如返回上一个频道、跳转到下一个频道或者跳转至指定的频道。遥控器为我们操作电视频道带来很大的方便,用户并不需要知道这些频道到底如何存储在电视机中。

 

在软件开发中,也存在大量类似电视机一样的类,它们可以存储多个成员对象(元素),这些类通常称为聚合类(Aggregate Classes),对应的对象称为聚合对象。
聚合对象拥有两个职责:一是存储数据;二是遍历数据。从依赖性来看,前者是聚合对象的基本职责;而后者既是可变化的,又是可分离的。

因此,我们比照电视机遥控器的功能角色,将遍历数据的行为从聚合对象中分离出来,封装在一个被称之为“迭代器”的对象中,由迭代器来提供遍历聚合对象内部数据的行为,这将简化聚合对象的设计,更符合“单一职责原则”的要求。
同时还可以灵活地为聚合对象增加不同的遍历方法,还可以访问一个聚合对象中的元素但又不需要暴露它的内部结构。

模式定义:

迭代器模式(Iterator Pattern) :提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示,其别名为游标(Cursor)。迭代器模式是一种对象行为型模式。
在迭代器模式结构中包含聚合和迭代器两个层次结构,考虑到系统的灵活性和可扩展性,在迭代器模式中应用了工厂方法模式,其模式结构如图所示:

模式分析:

在抽象迭代器中声明了用于遍历聚合对象中所存储元素的方法,典型代码如下所示:

interface Iterator {  
    public void first(); //将游标指向第一个元素  
    public void next(); //将游标指向下一个元素  
    public boolean hasNext(); //判断是否存在下一个元素  
    public Object currentItem(); //获取游标指向的当前元素  
} 

需要注意的是抽象迭代器接口的设计非常重要,

一方面需要充分满足各种遍历操作的要求,尽量为各种遍历方法都提供声明,另一方面又不能包含太多方法,接口中方法太多将给子类的实现带来麻烦。

因此,可以考虑使用抽象类来设计抽象迭代器,在抽象类中为每一个方法提供一个空的默认实现。

如果需要在具体迭代器中为聚合对象增加全新的遍历操作,则必须修改抽象迭代器和具体迭代器的源代码,这将违反“开闭原则”,因此在设计时要考虑全面,避免之后修改接口。

在具体迭代器中将实现抽象迭代器声明的遍历数据的方法,如下代码所示:

class ConcreteIterator implements Iterator {  
    private ConcreteAggregate objects; //维持一个对具体聚合对象的引用,以便于访问存储在聚合对象中的数据  
    private int cursor; //定义一个游标,用于记录当前访问位置  
    public ConcreteIterator(ConcreteAggregate objects) {  
        this.objects=objects;  
    }  
    public void first() {  ......  }  
    public void next() {  ......  }  
    public boolean hasNext() {  ......  }  
    public Object currentItem() {  ......  }  
} 

聚合类用于存储数据并负责创建迭代器对象,最简单的抽象聚合类代码如下所示:

interface Aggregate {  
    Iterator createIterator();  
} 

具体聚合类作为抽象聚合类的子类,一方面负责存储数据,另一方面实现了在抽象聚合类中声明的工厂方法createIterator(),用于返回一个与该具体聚合类对应的具体迭代器对象

代码如下所示:

class ConcreteAggregate implements Aggregate {    
    ......    
    public Iterator createIterator() {  
         return new ConcreteIterator(this);  
    }  
    ......  
} 

在迭代器模式结构图中,我们可以看到具体迭代器类和具体聚合类之间存在双重关系:

其中一个关系为关联关系,在具体迭代器中需要维持一个对具体聚合对象的引用,该关联关系的目的是访问存储在聚合对象中的数据,以便迭代器能够对这些数据进行遍历操作。
除了使用关联关系外,为了能够让迭代器可以(我倾向于直接访问而不是可以访问)访问到聚合对象中的数据,我们还可以将迭代器类设计为聚合类的内部类,例:自定义迭代器。

自定义迭代器实例:

实例:电视机遥控器
电视机遥控器就是一个迭代器的实例,通过它可以实现对电视机频道集合的遍历操作,本实例我们将模拟电视机遥控器的实现。

 

实例代码:

 

 

//=================
//抽象电视接口,要能返回指定的迭代器
public interface Television
{
    TVIterator createIterator();
}

//===================
//电视节目的迭代器,对电视节目进行基本的操作
public interface TVIterator
{
    void setChannel(int i);
    void next();
    void previous();
    boolean isLast();
    Object currentChannel();
    boolean isFirst();
}

//===================
//电视实现类这是重点部分,里面存储着节目列表,迭代器作为电视的内部类,可以直接访问节目列表,电视类提供一个工厂方法,返回迭代器类实例
public class SkyworthTelevision implements Television
{
    private Object[] obj={"CCTV-1","CCTV-2","CCTV-3","CCTV-4","CCTV-5","CCTV-6","CCTV-7","CCTV-8"};
    public TVIterator createIterator()
    {
        return new SkyworthIterator();
    }

    private class SkyworthIterator implements TVIterator
    {
           private int currentIndex=0;
                
        public void next()
        {
            if(currentIndex<obj.length)
            {
                currentIndex++;
            }
        }
        
        public void previous()
        {
            if(currentIndex>0)
            {
                currentIndex--;
            }
        }    
        
        public void setChannel(int i)
        {
            currentIndex=i;
        }
        
        
        public Object currentChannel()
        {
            return obj[currentIndex];
        }
        
        public boolean isLast()
        {
            return currentIndex==obj.length;
        }
        
        public boolean isFirst()
        {
            return currentIndex==0;
        }
    }
}

//=======================
//电视类2
public class TCLTelevision implements Television
{
    private Object[] obj={"湖南卫视","北京卫视","上海卫视","湖北卫视","黑龙江卫视"};
    public TVIterator createIterator()
    {
        return new TCLIterator();
    }
   
    class TCLIterator implements TVIterator
    {
           private int currentIndex=0;
                
        public void next()
        {
            if(currentIndex<obj.length)
            {
                currentIndex++;
            }
        }
        
        public void previous()
        {
            if(currentIndex>0)
            {
                currentIndex--;
            }
        }    
        
        public void setChannel(int i)
        {
            currentIndex=i;
        }
        
        
        public Object currentChannel()
        {
            return obj[currentIndex];
        }
        
        public boolean isLast()
        {
            return currentIndex==obj.length;
        }
    
        public boolean isFirst()
        {
            return currentIndex==0;
        }
    }
}

//===================
//Client调用
public class Client
{
    public static void display(Television tv)
    {
        TVIterator i=tv.createIterator();
        System.out.println("电视机频道:");
        while(!i.isLast())
        {
            System.out.println(i.currentChannel().toString());
            i.next();
        }
    }
    
    public static void reverseDisplay(Television tv)
    {
        TVIterator i=tv.createIterator();
        i.setChannel(5);
        System.out.println("逆向遍历电视机频道:");
        while(!i.isFirst())
        {
            i.previous();
            System.out.println(i.currentChannel().toString());
        }
    }
    
    public static void main(String a[])
    {
        Television tv;
        tv=new TCLTelevision();
        display(tv);
        System.out.println("--------------------------");
        reverseDisplay(tv);
    }
}

迭代器模式的注意事项和细节
 优点
1) 提供一个统一的方法遍历对象,客户不用再考虑聚合的类型,使用一种方法就可以遍历对象了。
2) 隐藏了聚合的内部结构,客户端要遍历聚合的时候只能取到迭代器,而不会知道聚合的具体组成。
3) 提供了一种设计思想,就是一个类应该只有一个引起变化的原因(叫做单一责任原则)。

在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象集合的责任分开,这样一来集合改变的话,只影响到聚合对象。而如果遍历方式改变的话,只影响到了迭代器。
4) 当要展示一组相似对象,或者遍历一组相同对象时使用, 适合使用迭代器模式
 缺点
每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类

 

观察者模式:

现实生活中,订阅报纸的基本流程如下:

 

实际上,邮局只不过起到一个中转的作用,为了简单,我们去掉邮局,让订阅者直接和报社交互:

引申一下,用类来描述上述的过程,描述如下:
订阅者类向出版者类订阅报纸,很明显不会只有一个订阅者对象订阅报纸,订阅者对象可以有很多;当出版者对象出版新报纸的时候,多个订阅者对象如何知道呢?还有订阅者对象如何得到新报纸的内容呢?
进一步抽象描述这个问题:当一个对象的状态发生改变的时候,如何让依赖于它的所有对象得到通知,并进行相应的处理呢?
解决上述问题的一个合理的解决方案就是观察者模式!

 

观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式是一种对象行为型模式。
观察者模式是使用频率最高的设计模式之一,它用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应作出反应。

在观察者模式中,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。

模式结构:

 

Subject:目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,

  它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法notify()。目标类可以是接口,也可以是抽象类或具体类。

ConcreteSubject:具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;

  同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有的话)。如果无须扩展目标类,则具体目标类可以省略。

Observer:观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法update(),因此又称为抽象观察者。

ConcreteObserver:具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;

  它实现了在抽象观察者中定义的update()方法。通常在实现时,可以调用具体目标类的attach()方法将自己添加到目标类的集合中或通过detach()方法将自己从目标类的集合中删除。

模式分析:

观察者模式描述了如何建立对象与对象之间的依赖关系,如何构造满足这种需求的系统。

这一模式中的关键对象是观察目标和观察者,一个目标可以有任意数目的与之相依赖的观察者,一旦目标的状态发生改变,所有的观察者都将得到通知。

作为对这个通知的响应,每个观察者都将监视观察目标的状态以使其状态与目标状态同步,这种交互也称为发布-订阅(Publish-Subscribe)。

观察目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅它并接收通知。

典型的抽象目标类代码如下所示:

abstract class Subject {  
    //定义一个观察者集合用于存储所有观察者对象  
    protected ArrayList<Observer>  observers= new ArrayList<Observer> ( );  
    //注册方法,用于向观察者集合中增加一个观察者  
    public void attach(Observer observer) {   observers.add(observer);  }  
    //注销方法,用于在观察者集合中删除一个观察者  
    public void detach(Observer observer) {   observers.remove(observer);  }  
  //声明抽象通知方法  
    public abstract void notify();  
} 

具体目标类ConcreteSubject是实现了抽象目标类Subject的一个具体子类,其典型代码如下所示:

class ConcreteSubject extends Subject { 
    //实现通知方法  
    public void notify() {  
        //遍历观察者集合,调用每一个观察者的响应方法  
        for(Object obs:observers) {  
            ((Observer)obs).update();  
        }  
    }     
} 

抽象观察者角色一般定义为接口,通常只声明一个update()方法,为不同观察者的更新(响应)行为定义相同的接口,

这个方法在其子类中实现,不同的观察者具有不同的响应方法。抽象观察者Observer典型代码如下所示:

interface Observer {  
    //声明响应方法  
    public void update();  
} 

在具体观察者ConcreteObserver中实现了update()方法,其典型代码如下所示:

class ConcreteObserver implements Observer {  
    //实现响应方法  
    public void update() {  
     //具体响应代码  
    }  
} 

观察者模式实例:

实例:猫、狗与老鼠
假设猫是观察目标,老鼠和狗是观察者,猫叫,则老鼠跑,狗也跟着叫,使用观察者模式描述该过程。

实例结构:

 

实例代码:

//========================
//被观察者Subject的抽象类,维护观察者列表,有抽象通知方法
import java.util.*;

public abstract class MySubject
{
    protected ArrayList observers = new ArrayList();
    
    //注册方法
    public void attach(MyObserver observer)
    {
        observers.add(observer);
    } 
    
    //注销方法
    public void detach(MyObserver observer)
    {
        observers.remove(observer);
    }
    
    public abstract void cry(); //抽象通知方法
}

//================
//抽象观察者,只有一个抽象反应方法
public interface MyObserver
{
    void response();  //抽象响应方法
}

//====================
//猫类,具体被观察类,执行完特定方法,需要调用观察者对象的反应方法
public class Cat extends MySubject
{
    public void cry()
    {
        System.out.println("猫叫!");
        System.out.println("----------------------------");        
        
        for(Object obs:observers)
        {
            ((MyObserver)obs).response();
        }
        
    }           
}

//==================
//观察者作出反应
public class Dog implements MyObserver
{
    public void response()
    {
        System.out.println("狗跟着叫!");
    }    
}

//===================
public class Mouse implements MyObserver
{
    public void response()
    {
        System.out.println("老鼠努力逃跑!");
    }
}

//====================
public class Pig implements MyObserver
{
    public void response()
    {
        System.out.println("猪没有反应!");
    }    
}

//================
//Client端测试
public class Client
{
    public static void main(String a[])
    {
        MySubject subject=new Cat();
        
        MyObserver obs1,obs2,obs3;
        obs1=new Mouse();
        obs2=new Mouse();
        obs3=new Dog();
        
        subject.attach(obs1);
        subject.attach(obs2);
        subject.attach(obs3);
        
        MyObserver obs4;
        obs4=new Pig();
        subject.attach(obs4);
        
        subject.cry();        
    }
}

 

中介者模式:

在QQ聊天中,一般有两种聊天方式:第一种是用户与用户直接聊天,第二种是通过QQ群聊天。
一个用户如果要与别的用户聊天或发送文件,通常需要加其他用户为好友,一个用户如果要将相同的信息或文件发送给其他所有用户,必须一个一个的发送。
如果使用QQ群,一个用户就可以向多个用户发送相同的信息和文件而无须一一进行发送。
群的作用就是将发送者所发送的信息和文件转发给每一个接收者用户。

 

通过引入群的机制,将极大减少系统中用户之间的两两通信,用户与多个用户之间的联系可以通过群来实现。

在有些软件中,某些类/对象之间的相互调用关系错综复杂,类似QQ用户之间的关系,此时,我们特别需要一个类似“QQ群”一样的中间类来协调这些类/对象之间的复杂关系,

以降低系统的耦合度。有一个设计模式正为此而诞生,它就是将要介绍的中介者模式。

如果在一个系统中对象之间的联系呈现为网状结构,如图所示。对象之间存在大量的多对多联系,将导致系统非常复杂。

这些对象既会影响别的对象,也会被别的对象所影响,这些对象称为同事对象,它们之间通过彼此的相互作用实现系统的行为。

在网状结构中,几乎每个对象都需要与其他对象发生相互作用,而这种相互作用表现为一个对象与另外一个对象的直接耦合,这将导致一个过度耦合的系统。

新对象的引入将带来大量的修改工作。

如果在一个系统中对象之间存在多对多的相互关系,我们可以将对象之间的一些交互行为从各个对象中分离出来,并集中封装在一个中介者对象中,

并由该中介者进行统一协调,这样对象之间多对多的复杂关系就转化为相对简单的一对多关系。可以将系统的网状结构变成以中介者为中心的星形结构,

如图所示。在这个星形结构中,同事对象不再直接与另一个对象联系,它通过中介者对象与另一个对象发生相互作用。中介者模式是“迪米特法则”的一个典型应用。

 

 

 模式定义:

中介者模式(Mediator Pattern)定义:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

中介者模式又称为调停者模式,它是一种对象行为型模式。

模式结构:

 

Mediator: 抽象中介者,它定义一个接口,该接口用于与各同事对象之间进行通信。

ConcreteMediator: 具体中介者,它是抽象中介者的子类,通过协调各个同事对象来实现协作行为,它维持了对各个同事对象的引用。

Colleague: 抽象同事类,它定义各个同事类公有的方法,并声明了一些抽象方法来供子类实现,同时它维持了一个对抽象中介者类的引用,其子类可以通过该引用来与中介者通信。

ConcreteColleague: 具体同事类,它是抽象同事类的子类;每一个同事对象在需要和其他同事对象通信时,先与中介者通信,通过中介者来间接完成与其他同事类的通信;

在具体同事类中实现了在抽象同事类中声明的抽象方法。

模式的核心在于中介者类的引入,它承担两方面职责:

中转作用(结构性):通过中介者提供的中转作用,各个同事对象就不再需要显式引用其他同事,当需要和其他同事进行通信时,可通过中介者来实现间接调用。该中转作用属于中介者在结构上的支持。

协调作用(行为性):中介者可以更进一步的对同事之间的关系进行封装,同事可以一致的和中介者进行交互,而不需要指明中介者需要具体怎么做,

中介者根据封装在自身内部的协调逻辑,对同事的请求进行进一步处理,将同事成员之间的关系行为进行分离和封装。该协调作用属于中介者在行为上的支持。

典型的抽象中介者类代码:

在抽象中介者中可以定义一个同事类的集合,用于存储同事对象并提供注册方法,同时声明了具体中介者类所具有的方法。在具体中介者类中将实现这些抽象方法。

abstract class Mediator {  
    protected ArrayList<Colleague> colleagues; //用于存储同事对象  
  
    //注册方法,用于增加同事对象  
    public void register(Colleague colleague) {  
        colleagues.add(colleague);  
    }  
  
    //声明抽象的业务方法  
    public abstract void operation();  
} 

典型的具体中介者类代码:

在具体中介者类中将调用同事类的方法,调用时可以增加一些自己的业务代码对调用进行控制。

class ConcreteMediator extends Mediator {  
    //实现业务方法,封装同事之间的调用  
    public void operation() {  
        ......  
        ((Colleague)(colleagues.get(0))).method1(); //通过中介者调用同事类的方法  
        ......  
    }  
} 

在抽象同事类中维持了一个抽象中介者的引用,用于调用中介者的方法,典型的抽象同事类代码:

abstract class Colleague {  
    protected Mediator mediator; //维持一个抽象中介者的引用  
      
    public Colleague(Mediator mediator) {  
        this.mediator=mediator;  
    }  
      
    public abstract void method1(); //声明自身方法,处理自己的行为  
      
} 

典型的具体同事类代码:

在具体同事类中实现了在抽象同事类中声明的方法,其中method1()方法是同事类的自身方法(Self-Method),用于处理自己的行为,

而method2()方法是依赖方法(Depend-Method),用于调用在中介者中定义的方法,依赖中介者来完成相应的行为,例如调用另一个同事类的相关方法。

class ConcreteColleague extends Colleague {
       public ConcreteColleague(Mediator mediator) {
    super(mediator);
       }
       //实现自身方法     
       public void method1()  {
    ......
       }
       //定义依赖方法,与中介者进行通信
       public void method2() {
                 mediator.operation();  
      }  
} 

实例:虚拟聊天室

某论坛系统欲增加一个虚拟聊天室,允许论坛会员通过该聊天室进行信息交流,普通会员可以给其他会员发送文本信息,

钻石会员既可以给其他会员发送文本信息,还可以发送图片信息。该聊天室可以对指定字符进行过滤,如“C”等字符;还可以对发送的图片大小进行控制。用中介者模式设计该虚拟聊天室。

实例结构:

实例代码:

//==================
//抽象中介,有注册同事类,还有其他业务类,中介者里面的业务类最终还是要调用同事类里面的方法
public abstract class AbstractChatroom
{
    public abstract void register(Member member);
    public abstract void sendText(String from,String to,String message);
    public abstract void sendImage(String from,String to,String message);
}

//========================
//抽象同事类,抽象同事类里面要有中介者(群聊组)的引用
public abstract class Member
{
    protected AbstractChatroom chatroom;
    protected String name;
    
    public Member(String name)
    {
        this.name=name;
    }
    
    public String getName()
    {
        return name;
    }
    
    public void setName(String name)
    {
        this.name=name;
    }
    
    public AbstractChatroom getChatroom()
    {
        return chatroom;
    }
    
    public void setChatroom(AbstractChatroom chatroom)
    {
        this.chatroom=chatroom;
    }
    
    public abstract void sendText(String to,String message);
    public abstract void sendImage(String to,String image);

    public void receiveText(String from,String message)
    {
        System.out.println(from + "发送文本给" + this.name + ",内容为:" + message);
    }
    
    public void receiveImage(String from,String image)
    {
        System.out.println(from + "发送图片给" + this.name + ",内容为:" + image);
    }    
}

//==================
//同事类使用了模板模式,不同的同时实现的业务也有区别
//普通成员
public class CommonMember extends Member
{
    public CommonMember(String name)
    {
        super(name);
    }
    
    public void sendText(String to,String message)
    {
        System.out.println("普通会员发送信息:");
        chatroom.sendText(name,to,message);  //发送
    }
    
    public void sendImage(String to,String image)//有些功能不对一些成员开放
    {
        System.out.println("普通会员不能发送图片!");
    }
}

//============
//高贵的vip用户
public class DiamondMember extends Member
{
    public DiamondMember(String name)
    {
        super(name);
    }
    
    public void sendText(String to,String message)
    {
        System.out.println("钻石会员发送信息:");
        chatroom.sendText(name,to,message);  //发送
    }
    
    public void sendImage(String to,String image)
    {
        System.out.println("钻石会员发送图片:");
        chatroom.sendImage(name,to,image);  //发送
    }
}

//=======================
//很重要的中介者的实现类
public class ChatGroup extends AbstractChatroom
{
    private Hashtable members=new Hashtable();
    
    public void register(Member member)
    {
        if(!members.contains(member))
        {
            members.put(member.getName(),member);
            member.setChatroom(this);
        }
    }
    
   public void sendText(String from,String to,String message)
   {
         Member member=(Member)members.get(to);//找到要接收的成员
         String newMessage=message;
         newMessage=message.replaceAll("C","*");//进行消息过滤
      member.receiveText(from,newMessage);//实际调用要接收消息的成员的receiveText方法
   }
   
   public void sendImage(String from,String to,String image)
   {
         Member member=(Member)members.get(to);
         //模拟图片大小判断
         if(image.length()>5)
         {
               System.out.println("图片太大,发送失败!");
         }
         else
         {
               member.receiveImage(from,image);
         }
   }
}

//===================
//Client端调用
public class Client
{
    public static void main(String args[])
    {
        AbstractChatroom happyChat=new ChatGroup();
        Member member1,member2,member3,member4,member5;
        member1=new DiamondMember("张三");
        member2=new DiamondMember("李四");
        member3=new CommonMember("王五");
        member4=new CommonMember("小芳");
        member5=new CommonMember("小红");
        
        happyChat.register(member1);
        happyChat.register(member2);
        happyChat.register(member3);
        happyChat.register(member4);
        happyChat.register(member5);
        
        member1.sendText("李四","李四,你好!");
        member2.sendText("张三","张三,你好!");
        member1.sendText("李四","今天天气不错,有日!");
        member2.sendImage("张三","一个很大很大的太阳");
        member2.sendImage("张三","太阳");
        member3.sendText("小芳","还有问题吗?");
        member3.sendText("小红","还有问题吗?");
        member4.sendText("王五","没有了,谢谢!");
        member5.sendText("王五","我也没有了!");
        member5.sendImage("王五","谢谢");
    }
}

中介者模式的注意事项和细节
1) 多个类相互耦合,会形成网状结构, 使用中介者模式将网状结构分离为星型结构,进行解耦
2) 减少类间依赖,降低了耦合,符合迪米特原则
3) 中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响
4) 如果设计不当,中介者对象本身变得过于复杂,这点在实际使用时,要特别注意

 设计模式--行为型模式下篇

posted @ 2020-12-21 11:39  wangid3  阅读(282)  评论(0)    收藏  举报