JAVA基础(六)行为类设计模式总结

来源:互联网 发布:提现接口网站 php 编辑:程序博客网 时间:2024/06/07 20:25
本章开始行为类模式的介绍,行为类模式的核心在于类与对象的交互和职责的分配,本章主要介绍HEAD FIRST中重点讲述的策略模式、观察者模式、命令模式、模板方法和状态模式,至于迭代器模式会在下一章与组合模式一同讲述,一方面这两个模式结合紧密,在HEAD FIRST中也是同一章节,另一方面,其代码复杂度多少有些让人头疼。。。
废话少说,下面开始对上述模式进行介绍
一、策略模式
策略模式Strategy:定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
策略模式作为Head First的第一个设计模式,介绍了一个设计程序中非常重要的思想:多用封装、少用继承
这是为什么呢?就以Head First中的鸭子为例,想要实现一个鸭子超类,并提供给子类个性化的quack()和fly()功能,书中给出了下面三种方法:
1.1 抽象函数
将quack和fly作为鸭子超类的抽象函数,并由子类继承实现
public abstract class Duck {//定义抽象函数public abstract void fly();public abstract void quack();}public class RedDuck extends Duck{//普通鸭子实现public void fly() {System.out.println("fly away");}public void quack() {System.out.println("gagaga");}}public class MachineDuck extends Duck{//机器鸭子实现public void fly() {System.out.println("I can't fly");}public void quack() {System.out.println("555");}}
这个方法勉强可行,但是显得十分笨拙,随着子类的扩大,函数重复实现代码将越来越多,如果需要实现的函数数目较多,每一个子类的实现简直就是噩梦,可扩展性极差

1.2 继承接口
将quack和fly分别作为接口Quackable和Flyable的抽象函数,并通过子类进行接口继承
public interface flyable {//fly方法封装为接口public abstract void fly();}public interface quackable {//quack方法封装为接口public abstract void quack();}public class RedDuck extends Duck implements flyable,quackable{//普通鸭实现类继承接口public void fly() {System.out.println("fly away");}public void quack() {System.out.println("gagaga");}}public class MachineDuck extends Duck implements flyable,quackable{//机器鸭实现类继承接口public void fly() {System.out.println("I can't fly");}public void quack() {System.out.println("555");}}
这个方法比抽象函数的方法更加笨拙。。。这使得鸭子类不再具备统一的对外接口,而且重复代码的问题完全没有解决
除了上述问题之外,这两个方法还有一个共同的问题,就是不能在运行时改变对象的方法,基于这些问题,策略模式应运而生~~~

1.3策略模式
将quack和fly分别作为接口直接封装到鸭子类内部,并通过子类定义接口的实现方式
首先将接口独立出来并单独实现(quackable接口与此对应,此处略):
public interface flyable {public abstract void fly();}class duckfly implements flyable{//普通fly实现public void fly() {System.out.println("fly away");}}class mfly implements flyable{//机器鸭fly实现public void fly() {System.out.println("I can't fly");}}
之后重构鸭子类的代码和实现过程:
public abstract class Duck {flyable f;//fly接口将在子类定义quackable q;//quack接口将在子类定义public void fly(){f.fly();//调用实例化后的接口函数}public void quack(){q.quack();//调用实例化后的接口函数}}public class RedDuck extends Duck{public RedDuck(){f = new duckfly();//普通鸭实例化fly方法接口q = new duckquack();//普通鸭实例化quack方法接口}}public class MDuck extends Duck{public MDuck(){f = new mfly();//机器鸭实例化fly方法接口q = new mquack();//机器鸭实例化quack方法接口}}
这种封装提供了统一的接口,简洁的代码实现和巨大的灵活性,在超类中加入方法setFlyBehavior和setQuackBehavior后,可以轻易的改变子类的行为方式,让子类“重获新生”
策略模式是“多用组合,少用继承”的完美体现,这个原则在Effective Java中的16条~21条均有很详尽的介绍,由于内容较多,以后有时间会开帖介绍

二、观察者模式
观察者模式Observer:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会受到通知并自动更新
在Head First书中,观察者模式共有两个案例,即自己编写的版本和java.util.Observer库的版本,在自己编写的版本中,Observable和Observer、Displable分别通过接口实现(此处为了简便删除了Display功能):
2.1 先编写发布者接口:
public interface Observable {public abstract void RegistOb(Observer ob);//注册观察者public abstract void deleteOb(Observer ob);//删除观察者public abstract void notifyObs();//通知所有观察者}
2.2 编写观察者接口:
public interface Observer {public abstract void update(double d);//简单起见,就改一个参数}
2.3 编写发布者对象:
public class Subject implements Observable {private double usefuldata = 5.5;//随便设个发布数private ArrayList<Observer> obs;//做个观察者列表,格式任意,此处用ArryListpublic void RegistOb(Observer ob) {obs.add(ob);//列表直接添加观察者}public void deleteOb(Observer ob) {obs.remove(ob);//移除观察者}public void changeData(double d){usefuldata = d;//设置自身的变量,并调用函数通知所有观察者notifyObs();}public void notifyObs() {for(Observer ob : obs){ob.update(usefuldata);//遍历,调观察者的update函数更新数据}}}
2.4 编写观察者对象
public class SimpleObs implements Observer {private double d;Observable subject;//这里记录一下要观察的对象public SimpleObs(Observable subject){this.subject = subject;//存储要观察的对象subject.RegistOb(this);//构造初始将记录加入观察列表}public void update(double d) {this.d = d;//更新自己的数据}}

在java.util.Observer类中,Observable和Observer、Displable分别被定义成了抽象类,并添加了一些方法,如setChanged(),该函数在每次修改参数后可以选择进行调用,作为notifyObservers()的前置条件,可以只在满足特定阈值或某些周期条件下使用,增加了通知观察者时的灵活性,但Observer由于使用了抽象类的方式,导致灵活性和可扩展性大为减弱,所以在有条件的情况下,建议自己进行开发



三、命令模式
命令模式Command:将“请求”封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象。命令模式也支持可撤销的操作
Head First中,使用命令模式实现了遥控器的功能,通过命令模式,可以用统一的接口执行命令,方便的完成队列控制和回撤操作。命令模式的关键是,针对自己的操作,实现command统一接口:
3.1 命令接口
public interface Command {public abstract void execute();}
对于要实现控制器的功能,继承Commad接口并重写方法excute即可

3.2  控制器对象
public class Controller {Command[] cmd = new Command[6];public void setCommand(int i,Command scmd){//把想添加的设备直接放入列表cmd[i] = scmd;}public void pushButton(int i){cmd[i].execute();//按按钮直接调用excute函数}}
命令模式本身并不复杂,相比之下更像是模板方法的简化版,通过继承命令接口,控制器对象能够任意的调用使用该接口的设备,实现了接口的对外统一

四、模板方法
模板方法Template Method:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤
初一看来,这个方法本身没有什么复杂的地方,无非就是在抽象类中定义一个总控方法,并对其中的子方法进行抽象,并在子类中将抽象方法进行实现:
public abstract class BeverageStore {public final void prepare(){boil();if(wantCondiments())   addCondiments();play();}abstract void boil();void addCondiments(){System.out.println("add something");}void play(){System.out.println("give beverage");}boolean wantCondiments(){//实现挂钩,动态决定是否执行某些步骤return true;}}
稍进一步,我们可以用继承的方法决定某些步骤是否会执行,比如在子类中加入如下的函数:
boolean wantCondiments(){return false;//子类决定了是否执行原有流程中的某些步骤}
模板模式看似结构接单,但这种朴素的策略应用却十分广泛,最常见的例子之一就是Array.sort()函数,只要实现了comparable接口,我们就可以直接用该函数完成一组对象的排序,compareTo接口实现比较简单,在这里就不再赘述了


五、状态模式
状态模式State:允许对象在内部状态改变是改变它的行为,对象看起来好像修改了它的类。
对象状态的不同能够导致不同的行为,这需要为每一个对象的状态设置一个类,作为行为的封装,状态类负责不同的行为实现,并在行为内部完成对象的状态转换,这需要状态类和对象本身的相互依赖。需要注意的是,所有的状态应该在对象内部全部生成,而不应该在状态中进行创建,这样才能避免状态类的反复创建
5.1 先看看状态类的定义:
public abstract class State {public abstract void func1();//随便写俩函数,不同的状态实现不同的行为public abstract void func2();}
5.2  机器类定义:
public class Machine {State state1 = new State1(this);//提前定义好所有状态类型,并传入本机State state2 = new State1(this);//同上State nstate = state1;//nstate反映本机实际的状态public void func1(){//func1,func2会调用实际状态的函数nstate.func1();}public void func2(){nstate.func2();}}
5.3 状态类的实例化:
public class State1 extends State {Machine m;public State1(Machine m){//传入本机,以方便对本机状态进行修改this.m = m;}public void func1() {System.out.println("haha,func1");}public void func2() {m.nstate = m.state2;//重要!!此状态可通过func2转移至另一状态}}public class State2 extends State {Machine m;public State2(Machine m){this.m = m;}public void func1() {m.nstate = m.state1;//重要!!此状态可通过func1转移至另一状态}public void func2() {System.out.println("haha,func2");}}
到此,状态机的实现完成了,只要状态的不同,同样的操作就会产生完全不同的行为





原创粉丝点击