本章开始行为类模式的介绍,行为类模式的核心在于类与对象的交互和职责的分配,本章主要介绍HEAD FIRST中重点讲述的策略模式、观察者模式、命令模式、模板方法和状态模式,至于迭代器模式会在下一章与组合模式一同讲述,一方面这两个模式结合紧密,在HEAD FIRST中也是同一章节,另一方面,其代码复杂度多少有些让人头疼。。。
废话少说,下面开始对上述模式进行介绍
一、策略模式
策略模式Strategy:定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
策略模式作为Head First的第一个设计模式,介绍了一个设计程序中非常重要的思想:多用封装、少用继承
这是为什么呢?就以Head First中的鸭子为例,想要实现一个鸭子超类,并提供给子类个性化的quack()和fly()功能,书中给出了下面三种方法:
1.1 抽象函数
策略模式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分别作为接口直接封装到鸭子类内部,并通过子类定义接口的实现方式
将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:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会受到通知并自动更新
观察者模式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; //做个观察者列表,格式任意,此处用ArryList
public 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:将“请求”封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象。命令模式也支持可撤销的操作
命令模式Command:将“请求”封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象。命令模式也支持可撤销的操作
Head First中,使用命令模式实现了遥控器的功能,通过命令模式,可以用统一的接口执行命令,方便的完成队列控制和回撤操作。命令模式的关键是,针对自己的操作,实现command统一接口:
3.1 命令接口
public interface Command {
public abstract void execute();
}
对于要实现控制器的功能,继承Commad接口并重写方法excute即可
3.2 控制器对象
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:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤
模板方法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");
}
}
到此,状态机的实现完成了,只要状态的不同,同样的操作就会产生完全不同的行为