内容简介:这是我第一次写文章,可能有写得不好的地方请大佬指正可能这会是一篇长期连载的设计模式系列哈哈哈搬至 head First 设计模式 ,这本书真的非常有意思和易懂
这是我第一次写文章,可能有写得不好的地方请大佬指正
可能这会是一篇长期连载的 设计模式 系列哈哈哈
搬至 head First 设计模式 ,这本书真的非常有意思和易懂
首先 要从设计模式入门 ,需要先看一个简单的模拟鸭子应用开始
JOE的公司做了一套相当成功的模拟鸭子的游戏,游戏中会出现各种鸭子 ,一边游泳戏水,一边呱呱叫,此系统的内部设计使用了标准的OO设计,设计了一个鸭子的超类(superclass),并让各种鸭子继承此超类
所有的鸭子都会呱呱叫和游泳,所以让超类负责处理这部分的实现代码
每个鸭子的子类型负责实现自己的display()行为在屏幕上显示其外观
现在主管们决定,需要模拟程序需要会飞的鸭子,在这个时候,Joe告诉主管们,他一个星期就能搞定,这有什么困难?Joe 需要在duck类中假如fly()方法,然后所有鸭子就会飞了,他很高兴的去交差
但是可怕的事情发生了,老板在看的时候发现很多的橡皮鸭子在屏幕上飞来飞去,于是通知他准备另寻工作了。
原来,Joe忽略了一件事,并非Duck所有的子类都会飞,Joe在Duck类上加上新的行为,会使某些并不适合该行为的子类也具有该行为。现在可好,程序中有一个无生命会飞的东西。
对代码所做的局部修改,影响层面可不只是局面(比如会飞的橡皮鸭)
他体会到了一件事,当涉及“维护”时,为了复用目的而使用继承的结局并不完美。
public class RubberDuck extends Duck { @Override void display() { System.out.println("我是橡皮鸭"); } @Override public void quack() { System.out.println("修改为吱吱叫"); } }复制代码
当前结构:
Joe突然想到,只要像quack()方法一样,把fly()方法覆盖掉就可以了。
@Override void fly() { // 什么事都不做 }复制代码
可是又衍生出另一个问题,那我以后要加个木头鸭呢,不会飞也不会叫。
public class DecoyDuck extends Duck { @Override void display() { System.out.println("我是木头鸭"); } @Override public void quack() { // 什么事都不做 } @Override void fly() { // 什么事都不做 } }复制代码
每次有新的鸭子的子类出现,他就要被迫检查并可能需要覆盖掉fly()和quack() ,这简直是无穷无尽的噩梦。。。
所以,他需要一个更清晰的方法,让某些鸭子类型可飞或可叫
有一个想法: 把fly()从超类中取出来,这么一来,只有会飞的鸭子实现flyable接口,同样的方法可以用在quack(),设计一个quackable接口
public interface Quackable { void quack(); }复制代码
public interface Flyable { void fly(); } 复制代码
实现为继承 虚线为实现
大家觉得这个设计如何?
可想而知 ,这是一个超笨的方法,这样一来重复的代码会变多,万一需要修改48个duck的子类的fly行为,你又需要全部都修改了。
我们知道继承并不是适当的 解决方式,虽然flyable 和quackable可以解决一部分问题,但是造成了代码无法复用,这只能说是从一个噩梦跳到另一个噩梦了。
接着往下看
软件开发的一个不变真理:不管软件当初设计得多好,一段时间总是需要成长与改变,否则软件就会死亡。
我们把问题归零,现在我们知道继承并不能很好的解决我们的问题,flyable接口也是。幸运的是有一个设计原则(不是设计模式) 可以帮我们很好的解决这个问题。
设计原则1:找到应用中可能需要变化的地方,把它们独立出来,不要和那些不需要变化的代码混在一起。(这是我们的第一个设计原则,后面还会有)
换句话说,每次新的需求一来,都会使某方面的代码发生改变,那么你就可以确定,这段代码需要被抽出来。把需要变化的部分抽取出来并封装起来,以便以后可以轻易改变或者扩充这部分,不影响不需要变化的部分。
回到我们的问题,把鸭子的行为fly 和quack从duck类中取出。
设计鸭子的行为,我们希望一切能有弹性,我们应该在鸭子类中包含设定行为的方法,这样就可以在运行时动态的改变绿头鸭的飞行行为。
有了这些目标,接下来看看第二个设计原则:
设计原则2:针对接口变成,而不是针对实现编程
从现在开始,鸭子的行为将被放到分开的类中,此类专门提供某行为接口的实现。这样,鸭子类就不再需要知道行为的实现细节。
我们利用接口代表每个行为,比方说,flyBehavior 和quackBehavior,而行为的每个实现都将实现其中的一个接口。所以这次鸭子类不会实现flyable和quackable接口,反而由我们制造一组其他类专门实现flyBehavior 和quackBehavior,这个就被称为行为类。由行为类而不是duck类来实现行为接口。
public interface FlyBehavior { void fly(); }复制代码
public class FlyCanWay implements FlyBehavior { @Override public void fly() { System.out.println("会飞"); } }复制代码
public class FlyNotWay implements FlyBehavior { @Override public void fly() { System.out.println("不会飞"); } }复制代码
public interface QuackBehavior { void quack(); }复制代码
public class Quack implements QuackBehavior { @Override public void quack() { System.out.println("呱呱叫"); } }复制代码
public class Squeak implements QuackBehavior { @Override public void quack() { System.out.println("吱吱叫"); } }复制代码
这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了,而我们可以新增一些行为,不会影响到既有的行为,也不会影响使用到飞行的鸭子类
现在开始整合鸭子的行为
做法是这样:
首先在duck类中加入两个实例变量,为别为flyBehavior和quackBehavior,每个对象都会动态的设置这些变量以运行时引用正确的行为类型。
我们找两个类似的方法performFly()和performQuack()取代duck中的fly()和quack()。往下看
@Data public abstract class Duck { /* 鸭子游泳 */ public void swim(){} /* 鸭子形状 可能有很多种类的鸭子,所以display方法是抽象的 */ abstract void display(); FlyBehavior flyBehavior; QuackBehavior quackBehavior; void performFly(){ // 每只鸭子都会引用实现FlyBehavior接口的对象 flyBehavior.fly(); } void performQuack(){ quackBehavior.quack(); } }复制代码
现在我们来关心如何设定 flyBehavior和 quackBehavior变量
public class MallardDuck extends Duck { @Override void display() { System.out.println("我是绿头鸭"); } /** * 因为继承了duck 所以拥有这两个变量 */ public MallardDuck() { quackBehavior = new Quack(); flyBehavior = new FlyCanWay(); } }复制代码
绿头鸭使用quack类处理呱呱叫,所以当performQuack被调用时,叫的职责被委托给quack对象,我们就得到真正的呱呱叫。
看懂了吗? 当
MallardDuck实例化时,它的构造器会把继承过来的 quackBehavior实例变量初始化为quack类型的新实例,flyBehavior同理。
测试一下
public static void main(String[] args) { Duck mallardDuck = new MallardDuck(); mallardDuck.performFly(); mallardDuck.performQuack(); }复制代码
结果
如何让鸭子具有动态行为呢?
利用flyBehavior的setter方法,我们可以随时调用setter改变鸭子的行为。
public void setFlyBehavior(FlyBehavior flyBehavior) { this.flyBehavior = flyBehavior; } public void setQuackBehavior(QuackBehavior quackBehavior) { this.quackBehavior = quackBehavior; }复制代码
加入我们的新鸭子种类
public class ModelDuck extends Duck { @Override void display() { System.out.println("我是一只橡皮鸭"); } public ModelDuck() { flyBehavior = new FlyNotWay(); quackBehavior = new Squeak(); } }复制代码
此时,我们让飞拥有火箭飞,和普通飞两种,实现flyByhavior接口
public class FlyRocket implements FlyBehavior { @Override public void fly() { System.out.println("火箭飞"); } }复制代码
只使得本来不会飞的模型鸭 变成 火箭飞
public static void main(String[] args) { Duck mallardDuck = new MallardDuck(); mallardDuck.performFly(); mallardDuck.performQuack(); Duck modelDuck = new ModelDuck(); modelDuck.performFly(); modelDuck.setFlyBehavior(new FlyRocket()); modelDuck.performFly(); }复制代码
结果:
会飞
呱呱叫
不会飞
火箭飞
好,我们已经深入研究了鸭子模拟器的设计了。下面是模型图
当你将继承和实现一起组合使用时就衍生另一个设计原则:
设计原则3:多用组合(继承加实现),少用继承
这文到这里就结束了,其实上面的就是第一个设计模式:也就是策略模式!
策略模式:分别封装起来,让他们之间可以互相替换。
总结
其实我这本书还没看完,看了第一次懵懵懂懂,第二次看就会有很大的理解,第三次看慢慢的轮廓就出现在我的脑海中。我写这篇文章的时候应该是我第四次边看边写下来。
接下来会研究继续写,即使没人看哈哈哈
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。