阅读:Head First Design的总结;
任务:模拟鸭子游泳:
初始:此系统的内部设计使用了标准的OO技术,设计了一个鸭子超类,并让各种鸭子继承此超类;
暂定三个方法;
呱呱叫quack();
游泳swim();
外观display();
新的要求:需要增加一个飞行的方法;
解决:直接在鸭子的超类中添加方法fly();让其子类可以直接继承使用;
问题出现:继承了该类的是鸭子,但不过是橡皮鸭子也具有了飞行的行为;
结论一:当涉及扩展和维护时,为了“复用”目的而使用继承,结局并不完美;
解决方案一:可以在橡皮鸭子中使用方法重写,将fly()方法进行覆盖!
新的问题:每添加一个新的鸭子子类都需要进行考虑,是否要进行方法的重写!
解决方案二:可以把fly()超类中取出来,放进一个“Flyable接口”中,这样一来只有会飞的鸭子才需要实现此接口;其他的可以同样使用这种方法;
新的问题:如果认为覆盖几个方法算是差劲,那么对于48个Duck的子类都要修改一下飞行的行为,怎么办,哼哼。
以上的解决方案以及相应的新的问题!
因为并非“所有”的子类都具有父类的所有方法,因此继承并不是适当的解决方式。而Flyable与Quackable可以解决“一部分”问题,但却造成了代码无法复用。
过度
现在把问题归零:
- 继承并不能很好地解决问题,因为鸭子的行为在子类里不断地改变,并且让所有的子类都有这些行为是不恰当的
- 以接口进行实现的方式将会变化的行为进行提取出来的方式看似不错,解决了例如只有会飞的鸭子才继承飞行接口的问题,但java接口不具有实现代码的功能,所有继承接口无法达到代码的复用,
解决思想:
- 找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起
(另一种思考方式:把会变化的部分取出来并封装起来,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分)
解决
目的:希望一切都能有弹性!什么叫做弹性;就是当生成一个新的鸭子类时基本的可以继承父类的方法,而这些会变化的行为就可以通过指定的方式进行赋予!
第一步:
分开变化和不变化的部分:
将不易改变的大家都有的行为仍放在超类中不进行更改;然后将变化的行为从Duck类中分开,我们将把他们从Duck类中取出来,建立一组新类代表每个行为!
第二步:
设计鸭子的行为:(设计原则:针对接口编程,而不是针对实现编程)
针对实现编程:
- 行为来自Duck超类的具体实现
- 继承某个接口并由子类自行实现而来
(以上两种行为就会导致我们的实现绑的死死的,没办法更改行为)
针对接口编程:以fly为例子;仍然是设计接口,但不过不同的地方是,这次鸭子并不负责fly接口的实现;反而制造一组其它类专门实现FlyBehavior,这就称为“行为”类。由行为类而不是Duck类来实现行为接口(也就是说特定的具体行为在实现了fly接口的类中实现);
***************对于针对接口编程的理解:
并不一定是指java里面的接口interface,接口是一个“概念”,针对接口编程,关键就在多态,利用多态,程序就可以针对超类编程,执行时会根据实际状况执行到真正的行为,不会被绑死在超类型的行为上。
因此“针对超类编程”,可以更明确的说成“变量的声明类型应该是超类型,通常是一个抽象类或者是一个接口,如此,只要是具体实现此超类型的类所产生的对象都可以指定给这个变量。这也意味着,声明类时不用理会以后会执行是的真正对象类型”
第三步:
实现鸭子的行为:
除了实现鸭子的超类外,进行抽出来经常变动行为的接口的实现!
FlyBehavior{fly()};然后进行实现
FlyWithWings{
fly(){//实现鸭子飞行}
}
FlyNoWay{fly(){//什么都不走,不会飞}}
第四步:
整合鸭子的行为:
关键在于,鸭子现在会将飞行的动作“委托”别人处理,而不是定义在Duck类(或子类)内的飞行方法中;
做法:
首先在Duck类中“加入两个实例变量”,类型为“flyBehavior”,声明类型为接口类型(也就是面向接口进行编程,类型为超类即可)。
然后在父类中创建方法:performFly(){flyBehavior.fly();}
鸭子对象不亲自处理飞行行为,而是委托给flyBehavior的实现类
最后:我们将超类变量的赋值构造对象定义在子类的构造方法中;
未完成的:我们将不对具体实现编程嘛?但是我们在构造器里做什么勒?我们正在制造一个具体FLy的实现类实例!。。。以后在进行改进
在初始化实例变量的做法不够弹性,我们希望能够在运行的时候,通过多态的魔力动态的给它指定不同的flyBehavior实现类!!!
用处:这个时候要进行添加新的鸭子类时,就比较方便了