Head First Design Mode(2)-设计模式入门
该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!
设计模式入门:
欢迎来到设计模式世界;
我们会看到设计模式的用途和优点,再看看关键的OO原则,通过实例来了解模式是如何运作的;
以往是代码复用,现在是经验复用;
模拟鸭子的应用:
Duck(鸭子)会Quark(呱呱叫)和 Swim(游泳);
我们定义超类Duck::quark();swim();dispaly();
外观display方法是一个抽象方法,因为每种鸭子的外观各有不同;
ADuck,BDuck子类型的鸭子实现了display()方法;共同继承自Duck类;
这是一种继承的方式,在学习Java时一定学习过类似的题目;
现在让鸭子能飞:
利用继承,我们在Duck类中添加fly()方法;
但是,很多“橡皮鸭子”飞来飞去,因为父类中的方法会被所有子类继承;
导致很多不适合’飞’行为的子类也具有了该行为;
当涉及“维护”时,为了“复用”(reuse)目的而使用继承,结局并不完美;
继承下的做法:
在橡皮鸭类中把fly()方法覆盖掉,让它什么都不做;
带来的问题是,其他类型的鸭子都会受到影响,你不得不为其他不会叫的鸭子类重写fly()方法;
而当加入新鸭子时,由不得不检查这些方法;
我们不想代码在多个子类中重复,又很难知道鸭子的全部行为,最重要的,这样修改 会导致 牵一发而动全身,这显然不是我们想要的;
利用接口:
需要一个清晰的方法,让某些鸭子类型可飞或可叫;
我们把fly()和quark()分别声明在接口Flyable和Quarkable中,需要飞的鸭子实现相应的接口即可;
但这个设计真的好吗,这是一个多对多的关系,如果有20种鸭子,他们的飞行行为需要调整,那简直就是灾难;
我们看到:
继承并不是适当的解决方式;
接口解决了一部分问题,但是代码却没法有效的复用;
采用良好的OO软件设计原则:
软件开发的不变真理就是变化——不管当初软件设计的多好,一段时间后,总要成长与改变;
把问题归零:
我们知道了使用继承并不能很好的解决问题,因为鸭子的行为在子类里不断的变化,让所有的子类都有这些行为是不恰当的;
使用接口虽然解决了一部分问题,但是接口只有声明不具有实现的代码,所以实现接口无法达到代码复用;
适应此情况的设计原则:
找到应用中变化之处,独立出来,不要和不需要变化的代码混在一起;
变化的代码进行封装,使代码变化引起的后果变小,系统变得更有弹性;
该原则的另一种思考方式:
把会变化的部分取出来并封装起来,以便以后轻易地改动或扩充此部分,而不影响不需要变化的其他部分;
这几乎是所有设计模式背后的精神所在“系统中的某部分改变不会影响其他部分”;
分开变化的 和 不变的:
新建两组类与Duck无关的,一组是fly相关,一组是quark相关,每组类将实现各自的动作(高飞类、底飞类,咕咕叫类、嘎嘎叫类…);
这样取出来的一组fly相关类,就可以用来表示Duck的fly行为,一个鸭子可以有多种行为;
设计鸭子的行为:
一切能有弹性;比如,新建一个绿头鸭,初始的飞行行为是高飞,在运行过程中,我们可以动态的改变成底飞;
第二个设计原则:
针对接口编程,而不是针对实现编程;
比如fly行为被放在多个分开的类中,而这些类专门提供fly行为接口的实现;
这样鸭子类就不再需要知道行为的实现细节;
我们用接口代表了行为:FlyBehavior、QuarkBehavior;
由具体的行为类而不是Duck类来实现行为接口;
以往是行为来自超类的具体实现,或继承某个接口由子类自行实现而来;这两种做法都依赖于实现,没办法改变行为;
“针对接口编程”真正的意思是“针对超类型编程”:
因为所有实现接口的类,其接口类型,都可以看作是实现类的超类(或父类);
由于实现了相同的接口,他们就是相同的类型了,变量声明类型都是超类型;
这利用了多态,执行时会根据实际状况执行真正的行为;
超类型,通常是一个抽象类或一个接口;
如此,只要是具体实现此超类型的类所产生的对象,都可以指定给这个变量;这意味着,声明类时不用理会以后执行时的真正对象类型;
针对实现编程:
Dog dog1 = new Dog();
dog1.bark();
针对接口编程:
Animal animal = new Dog();
animal.makeSound();
而且,子类实例化执行的new动作不再需要在代码中硬编码,实际的子类型是什么不一定要知道,我们只关心他是否能正确的执行相应的动作;
实现鸭子的行为:
FlyBehavior-interface: fly();
FlyHigh: :fly();
FlyLow: :fly();
QuarkBehavior: quark();
QuarkGugu: :quark();
QuarkGuagua: :quark();
这样的设计这些行为被其他代码复用,因为它们已经和鸭子无关了;
整合鸭子的行为:
鸭子现在将飞和叫的动作“委托”给了其他对象处理,而不是用Duck类的叫和飞;
1)在Duck类中加入两个实例变量,均声明为接口类型;
2)使用performFly()和performQuark()方法,其中通过实例变量调用fly()和quark();
鸭子对象不亲自处理叫和飞的行为,而是委托给两个实例变量对象;
3)新建鸭子DuckSub1,在初始化方法中为实例变量赋值:高飞 咕咕叫!(实例变量是从父类继承来的)
编译运行下:
bogon:第一章 huaqiang$ javac *.java
bogon:第一章 huaqiang$ java DuckDrive
Fly high !
Quark gugu !
DuckSub1!
由于实例变量是接口类型的,我们也可以为它指定相同接口不同的实现类;
动态设定行为:
加入如下两个方法;
public void setFlyBehavior(FlyBehavior fb){
flyBehavior = fb;
}
public void setQuarkBehavior(QuarkBehavior qb){
quarkBehavior = qb;
}
这样,就可以把 新的行为实现设定给指定的鸭子;
封装行为的大局观:
稍稍改变描述的方式,我们把一组行为,描述为一族算法;
那么,算法就表示鸭子能做的事,类似的场景:用一群算法计算某城市的销售税金;
封装飞行行为(各种飞行方式):一族算法;
……
我们发现同一族里的算法是可以互换的;
在上述示例中一定要明确几种关系:IS-A、HAS-A、IMPLEMENTS;
有一个的关系可能比是一个更好:
每个鸭子有一个FlyBehavior和QuarkBehavior,好将飞和叫 委托给他们代为处理;
这里将两个行为类 进行了 “组合”;鸭子的行为不是继承来的,而是适当行为对象组合来的;
第三个设计原则:
多用组合,少用继承;
实用组合将算法族封装成类,在符合正确的接口标准时,建立的系统也很有弹性;
此间,我们用了学到的第一个设计模式:策略模式;
策略模式:
定义了算法族,分别封装起来,让他们之间互相替换,此模式让算法的变化独立于使用算法的客户;
如何使用设计模式:
我们开发需要使用别人设计好的库和框架;我们挑选好组件,把他们放到合适的地方;
但是,库和框架无法帮助我们将应用组织成容易了解、容易维护、具有弹性的架构,所以需要设计模式,并在合适的场景中使用之;
设计模式告诉我们如何组织类和对象已解决某种问题;
有很多面向对象的原则,使用所有模式,当没有找到合适的设计模式时,遵循这些原则即可,下一章我们继续学习;
总结:
1.知道OO基础,不足以设计出良好的OO系统;
2.良好的OO设计必须具备 复用 可扩充 可维护三个特性;
3.模式可以让我们建造出具有良好OO设计质量的系统;
4.模式被认为是经历验证的OO设计经验;
5.模式不是代码,而是针对设计问题的通用解决方案;
6.大多数模式和原则,都着眼于软件变化的主题;
7.我们常把系统中会变化的部分抽出来封装;
8.模式让开发人员之间有共享的语言,能够最大化沟通的价值;
OO基础:
抽象 封装 多态 继承;
OO原则:
封装变化
多用组合,少用继承
针对接口编程,不针对实现编程;
OO模式:
策略模式:定义算法族,分别封装起来,让他们之间互相替换,此模式让算法的变化独立于使用算法的客户;