行为型模式介绍及实例(上)

  本系列文章共分为六篇:
    设计模式的分类与区别
    创建型模式介绍及实例
    结构型模式介绍及实例
    行为型模式介绍及实例(上)
    行为型模式介绍及实例(下)
  上篇文件介绍了结构型模式,本篇将介绍行为型模式。行为型模式的主要关注点是“描述类或对象之间怎样通信、协作共同完成任务,以及怎样分配职责”。因为行为型模式较多,分成上下两篇来介绍。

一、模板模式

1.1 模板模式定义

  定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。

1.2 模板模式特点

  优点:
   1>它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
   2>它在父类中提取了公共的部分代码,便于代码复用。
   3>部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
  缺点:
   1>对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
   2>父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

1.3 模板模式主要角色

  抽象类:负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。
   模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
   基本方法:是整个算法中的一个步骤,包含以下几种类型。
    1>抽象方法:在抽象类中申明,由具体子类实现。
    2>具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
    3>钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
  具体子类:实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。

1.4 模板模式实现方式

  此处,以两个人张三和李四上班为例,主要涉及三个部分:起床、吃早饭、乘坐交通工具去上班,起床是公共方法,吃早饭可以推迟到之类,乘什么样的交通工具可以用钩子方法来判别。示例代码如下:

/*抽象类*/
abstract class Worker {
    public void WorkerWay() {
    	getUp();
        haveBreakfast();
        if(haveCar()){
            takeBus();  
        }else{
        	drive();
        }
    }  
    /*具体方法*/
    public void getUp() {
        System.out.println("起床");
    }
    public void takeBus(){
    	System.out.println("坐公交上班");
    }
    public void drive(){
    	System.out.println("开车上班");
    }
    /*钩子方法*/
    public boolean haveCar(){   return true;   }
    /*抽象方法*/
    public abstract void haveBreakfast();  
}
/*具体子类:张三*/
public class Zhangsan extends Worker{
	@Override
	public void haveBreakfast() {
		System.out.println("吃三明治、喝牛奶");
	}
    public boolean haveCar(){   return false;   }
}
/*具体子类:李四*/
public class Lisi extends Worker{
	@Override
	public void haveBreakfast() {
		System.out.println("吃包子、喝豆浆");
	}
    public boolean haveCar(){     return true;   }
}
/*测试类*/
public class TemplateTest {
	public static void main(String[] args) { 
		Zhangsan zhangsan =new Zhangsan();
		System.out.println("张三的上班方式:");
		zhangsan.WorkerWay();
		Lisi lisi =new Lisi();
		System.out.println("李四的上班方式:");
		lisi.WorkerWay();		
	}
}

  结果如下:

张三的上班方式:
起床
吃三明治、喝牛奶
开车上班
李四的上班方式:
起床
吃包子、喝豆浆
坐公交上班

1.5 模板模式应用场景

  模板模式通常适用的场景:
   1)算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
   2)当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
   3)当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。

二、策略模式

2.1 策略模式定义

  该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。

2.2 策略模式特点

  优点:
   1>多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
   2>可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
   3>提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
   4>把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
  缺点:
   1>客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
   2>策略模式造成很多的策略类。

2.3 策略模式主要角色

  抽象策略类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
  具体策略类:实现了抽象策略定义的接口,提供具体的算法实现。
  环境类:持有一个策略类的引用,最终给客户端调用。

2.4 策略模式实现方式

  此处,以小明做饭为例,当他想吃面时,做了一份臊子面;想吃饭时,做了一份炒饭。示例代码如下:

/*抽象策略*/
public interface  Cook {
	 public void cooking(); 
}
/*具体策略:做面*/
public class NoodleCook implements Cook{
	public void cooking() {
		System.out.println("做了一份臊子面");
	}
}
/*具体策略:做饭*/
public class RiceCook implements Cook{
	public void cooking() {
		System.out.println("做了一份炒饭");
	}
}
/*环境类*/
public class Context {
    private Cook cook;
    public Cook getCook(){    return cook;   }
    public void setCook(Cook cook){
        this.cook=cook;
    }
    public void cooking(){
    	cook.cooking();
    }
}
/*测试类*/
public class StrategyTest {
	public static void main(String[] args) { 
		Context context = new Context();
		System.out.println("小明要吃面");
		Cook cook = new NoodleCook();
		context.setCook(cook);
		context.cooking();
		System.out.println("小明要吃饭");
		cook = new RiceCook();
		context.setCook(cook);
		context.cooking();
	}
}

  结果如下:

小明要吃面
做了一份臊子面
小明要吃饭
做了一份炒饭

2.5 策略模式应用场景

  模板模式通常适用的场景:
   1)一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
   2)一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
   3)系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
   4)系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
   5)多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。

三、状态模式

3.1 状态模式定义

  对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

3.2 状态模式特点

  优点:
   1>状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
   2>减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
   3>有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。
  缺点:
   1>状态模式的使用必然会增加系统的类与对象的个数。
   2>状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。

3.3 状态模式主要角色

  环境角色:也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
  抽象状态角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
  具体状态角色:实现抽象状态所对应的行为。

3.4 状态模式实现方式

  此处,以档位切换为例,某个开关有3个档位,且遵从高 --> 中 --> 低 --> 高的切换方式。示例代码如下:

/*抽象状态*/
public abstract class Switch {
	public abstract void setPre(Context context);
	public abstract void setNext(Context context);
}
/*具体状态1:低档位*/
public class LowSwitch extends Switch{
	@Override
	public void setPre(Context context) {
        System.out.println("当前是低档位,向前切换为中档位");
        context.setSwitch(new MiddSwitch());
	}
	@Override
	public void setNext(Context context) {
        System.out.println("当前是低档位,向后切换为高档位");
        context.setSwitch(new HighSwitch());
	}
	@Override
	public String toString() {    return "低档位";  }
}
/*具体状态2:中档位*/
public class MiddSwitch extends Switch{
	@Override
	public void setPre(Context context) {
        System.out.println("当前是中档位,向前切换为高档位");
        context.setSwitch(new HighSwitch());
	}
	@Override
	public void setNext(Context context) {
        System.out.println("当前是中档位,向后切换为低档位");
        context.setSwitch(new LowSwitch());
	}
}
/*具体状态3:高档位*/
public class HighSwitch extends Switch{
	@Override
	public void setPre(Context context) {
        System.out.println("当前是高档位,向前切换为低档位");
        context.setSwitch(new LowSwitch());
	}
	@Override
	public void setNext(Context context) {
        System.out.println("当前是高档位,,向后切换为中档位");
        context.setSwitch(new MiddSwitch());
	}
}
/*环境类*/
public class Context {
    private Switch switch1;
    //定义环境类的初始状态
    public Context(){
        this.switch1 = new LowSwitch();
    }
    //设置档位
    public void setSwitch(Switch sw){
        switch1 = sw;
    }
    //读取档位
    public Switch getSwitch(){
    	System.out.println("当前档位是:"+switch1.toString());
        return(switch1);
    }
    //往前设置一个档位
    public void setPre(){
    	switch1.setPre(this);
    }
    //往后设置一个档位
    public void setNext(){
    	switch1.setNext(this);
    }
}
/*测试类*/
public class SwtichTest {
    public static void main(String[] args){
        Context context=new Context();    //创建环境       
        context.getSwitch();    //处理请求
        context.setPre();
        context.setPre();
        context.setNext();
    }
}

  结果如下:

当前档位是:低档位
当前是低档位,向前切换为中档位
当前是中档位,向前切换为高档位
当前是高档位,,向后切换为中档位

3.5 状态模式应用场景

  状态模式通常适用的场景:
   1)当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
   2)一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。

四、观察者模式

4.1 观察者模式定义

  指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

4.2 观察者模式特点

  优点:
   1>降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
   2>目标与观察者之间建立了一套触发机制。
  缺点:
   1>目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
   2>当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

4.3 观察者模式主要角色

  抽象主题角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
  具体主题角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
  抽象观察者角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
  具体观察者角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

4.4 观察者模式实现方式

  此处,以小明追小说为例,有两本小说《剑来》和《英雄志》都未完结,他关注了这两部小说,当则两部小说更新时,他都会收到对应的消息。代码示例如下:

/*抽象主题:小说*/
public abstract  class Story {
	 protected List<Observer> observers=new ArrayList<Observer>();
	    //增加观察者方法
	    public void add(Observer observer){
	        observers.add(observer);
	    }    
	    //删除观察者方法
	    public void remove(Observer observer){
	        observers.remove(observer);
	    }   
	    public abstract void notifyObserver(int chapterNum); //通知观察者方法
}
/*抽象主题1:剑来*/
public class Story1 extends Story{
	private String name = "jianlai";
    public void notifyObserver(int chapterNum){       
        for(Object obs:observers){
            ((Observer)obs).response(this.name,chapterNum);
        }
    }
}
/*抽象主题2:英雄志*/
public class Story2 extends Story{
	private String name = "yingxiongzhi";
    public void notifyObserver(int chapterNum){      
        for(Object obs:observers){
            ((Observer)obs).response(this.name,chapterNum);
        }
    }
}
/*抽象观察者*/
public interface Observer {
	void response(String storyName,int number);
}
/*具体观察者:小明*/
public class ObserverXiaoming implements Observer{
    private String name = "小明";
	public void response(String storyName,int chapterNum) {
		if("jianlai".equals(storyName)){
			System.out.println("《剑来》更新了"+chapterNum+"章,"+this.name+"等养肥了在看");
		}else{
			System.out.println("《英雄志》更新了"+chapterNum+"章,"+this.name+"立马去看");
		}
	}
}
/*测试类*/
public class ObserverTest {
    public static void main(String[] args){
    	Story story1=new Story1();  
    	Story story2=new Story2(); 
    	Observer observer=new ObserverXiaoming();           
    	story1.add(observer); 
    	story2.add(observer);           
    	story1.notifyObserver(20);
        story2.notifyObserver(10);
    }
}

  示例结果如下:

《剑来》更新了20章,小明等养肥了在看
《英雄志》更新了10章,小明立马去看

4.5 观察者模式应用场景

  观察者模式通常适用的场景:
   1)对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
   2)当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。

五、备忘录模式

5.1 备忘录模式定义

  在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。

5.2 备忘录模式特点

  优点:
   1>提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
   2>实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
   3>简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
  缺点:
   1>资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。

5.3 备忘录模式主要角色

  发起人角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
  备忘录角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
  管理者角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

5.4 备忘录模式实现方式

  此处,以小亮周末做的一些事情为例,如:上午健身、下午和朋友聚餐、晚上看小说为例,用备忘录来记住他周末一天做的事,示例代码如下:

/*备忘录*/
public class Memento {
    private String state;
    public Memento(String state) {
        this.state = state;
    }
    public String getState() {   return state;   }
}
/*发起人:小亮*/
public class OriginatorXiaoliang {
    private String state;
    public String getState() {    return state;  }
    public void setState(String state) {
        this.state = state;
    }
    public Memento saveStateToMemento(){    return new Memento(state);  }
    public void getStateFromMemento(Memento memento){
        state = memento.getState();
    }
}
/*管理者*/
public class CareTaker {
    private List<Memento> mementoList = new ArrayList<Memento>();
    public void add(Memento memento){
        mementoList.add(memento);
    }
    public Memento get(int index){    return mementoList.get(index);  }
}
/*测试类*/
public class MementoTest {
    public static void main(String[] args) {
    	OriginatorXiaoliang xiaoliang = new OriginatorXiaoliang();
        CareTaker careTaker = new CareTaker();
        xiaoliang.setState("健身");
        careTaker.add(xiaoliang.saveStateToMemento());
        xiaoliang.setState("和朋友聚会");
        careTaker.add(xiaoliang.saveStateToMemento());
        xiaoliang.setState("看小说");
        System.out.println("小亮晚上做的事: " + xiaoliang.getState());
        xiaoliang.getStateFromMemento(careTaker.get(0));
        System.out.println("小亮上午做的事: " + xiaoliang.getState());
        xiaoliang.getStateFromMemento(careTaker.get(1));
        System.out.println("小亮下午做的事: " + xiaoliang.getState());
    }
}

  测试结果如下:

小亮晚上做的事: 看小说
小亮上午做的事: 健身
小亮下午做的事: 和朋友聚会

5.5 备忘录模式应用场景

  备忘录模式通常适用的场景:
   1)需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
   1)需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,Eclipse 等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。

猜你喜欢

转载自blog.csdn.net/m0_37741420/article/details/106600250