headFirst学习笔记之一:设计模式入门(4.28)

1.简单的模拟鸭子应用:

超类Duck:呱呱叫(quack)和游泳两个方法由超类实现,因为所有的鸭子都有这个功能,display由子类自己实现,表示的是外观。

1 class Duck{
2    quack(){}
3    swim(){}
4    abstract display();
5 }

子类例子:

 1 class MallardDuck{
 2    @override
 3    display(){
 4    //外观是绿头
 5    }
 6 }
 7
 8 class RedHeadDuck{
 9    @override
10    display(){
11    //外观是红头
12    }
13 }

2.需要增加会飞的鸭子:

joe的最想当然的做法:Duck类中添加fly()方法。

1 class Duck(){
2    quack(){}
3    swim(){}
4    abstract display();
5    fly(){}
6 }

问题出现了:并非所有的子类都会飞,比如子类是橡皮鸭子。所以在超类中添加某些新行为,会使得不具有该行为的子类也具有这些行为了。

joe然后进行弥补:将橡皮鸭中的fly()重写,但是什么都不做。

 1 class RubberDuck{
 2     quack(){
 3     //覆盖成吱吱叫
 4     }
 5     display(){
 6     //外观是橡皮鸭
 7     }
 8     fly(){
 9    //什么都不做
10    }
11 }

问题出现了:如果以后又增加一个木头鸭呢,既不会飞也不会叫,难道也要一个一个的修改,覆盖为什么都不做吗?所以这种方法,会使得每次添加一个新类的时候,joe都要检查是否需要覆盖fly和quark方法。

于是joe觉得继承不是答案,决定改为用接口实现。建立flyable和quackable接口,需要实现这些功能的子类自己去实现接口就可以了。

 1 interface Flyable{
 2    fly();
 3 }
 4
 5 interface Quackable{
 6    quack();
 7 }
 8
 9 class MallardDuck extends Duck implements Flyable, Quackable{
10    display(){}
11    fly(){}
12    quack(){}
13 }
14
15 class RubberDuck extends Duck implements Quackable{
16    display(){//橡皮鸭}
17    quack(){//吱吱叫}
18 }
19
20 class RubberDuck extends Duck{
21    display(){//木头鸭}
22 }

问题出现了:因为接口不具有实现代码,所以实现接口会使得代码重复率太高。

3.合适的解决办法:

(1)为什么继承不是合适的办法:因为新增加的行为maybe不是所有子类都具有,在新增加子类时,我们还得检查,这个子类拥有父类所有的行为是否恰当,否则就要另外处理。比如木头鸭也是鸭子,但是他不叫不飞。所以不能把叫和飞放到duck类中。

(2)为什么接口不是合适的办法:因为接口不具有实现代码,所以实现接口会使得代码重复率太高。每一个子类中都要写一遍fly的实现,会疯的。

(3)解决办法的原则:找出变化之处,把它独立出来;针对接口编程,而非针对实现编程(这句话还不太懂)。

变化的部分是fly和quack,所以新建两组类(与Duck无关)FlyBehavior和QuackBehavior,再增加一个类Quiet。Duck仍然是superclass,但是fly和quack已经不在Duck里面了。

但是根据第二点原则,我们将类FlyBehavior和QuackBehavior改为接口。并且写对应的实现类。

 1 interface FlyBehavior{
 2    fly();
 3 }
 4
 5 class FlyWithWings implements FlyBehavior{
 6    fly(){//实现鸭子飞行}
 7 }
 8
 9 class FlyNoWay implements FlyBehavior{
10    fly(){//什么都不做,不飞}
11 }
 1 interface QuackBehavior{
 2   quack();
 3 }
 4
 5 class Quack() implements QuackBehavior{
 6   quack(){//实现呱呱叫}
 7 }
 8
 9 class Squeak() implements QuackBehavior{
10   quack(){//实现吱吱叫}
11 }
12
13 class MuteQuack() implements QuackBehavior{
14   quack(){//不叫}
15 }

这样的话,可以让飞行和叫的行为被复用,如果要添加新行为,也不会影响现有的行为类和使用行为的鸭子类。

4.插播刚刚关于“针对接口编程,不要针对实现编程”的理解。

针对实现编程:就是前面两种方法,将行为的实现写到超类里,或者写到接口然后子类继承接口再自己写实现。这样的不好前面已经讲过了。

针对接口编程:就是第三种方法,定义行为接口,然后写行为类。将其与“使用这些行为”的duck类独立开来。

为什么要这么做:针对接口编程真正的意思是针对超类型编程,超类型可以是class也可以是interface(那为什么上面要定义为interface?),程序可以针对超类型编程,但是实现时会根据实际情况执行到真正的行为(比较拗口和难懂,看例子最生动)。

 1 class Animal{
 2   abstract makeSound();
 3 }
 4
 5 class Dog() extends Animal{
 6   makeSound(){
 7     bark();
 8   }
 9   bark(){//汪汪叫}
10 }
11
12 class Cat() extends Animal{
13   makeSound(){
14    meow();
15   }
16   meow(){//喵喵叫}
17 }

针对实现编程:

1 Dog d = new Dog();
2 d.bark;

针对接口编程:

1 Animal animal = new Dog();
2 animal.makeSound();
3
4 a = getAnimal();
5 a.makeSound();

(其实还是有点懵,先这样吧)

5.整合鸭子的行为:(1)Duck将fly和quack行为委托delegate到别人那里处理,而不是用自己(Duck或者其子类)内部定义的fly和quack的具体实现。(2)在子类中设定fly和quack行为的实例变量。

 1 class Duck{
 2   QuackBehavior quackBehavior;
 3   FlyBehavior flyBehavior;
 4
 5   performQuack(){
 6     quackBehavior.quack();
 7   }
 8   performFly(){
 9     flyBehavior.fly();
10   }
11   swim(){}
12   display(){}
13 }
1 class MallardDuck extends Duck{
2    MallardDuck(){
3      quackBehavior = new Quack();
4      flyBehavior = new FlyWithWings();
5    }
6 }

然后MallardDuck是真的能够实现呱呱叫和飞行了。

但是有点小问题:我们的原则是不针对具体实现编程,但是我们现在正在结构体里制造一个具体的实现类Quack和FlyWithWings!

这会在后面改正,现在只是初始化实例变量有点不够弹性,但是整体好多了啦~(这么自问自答的精分书也是够了。)

6.实际代码:

 1 public interface FlyBehavior {
 2    public void fly();
 3 }
 4
 5 public class FlyNoWay implements FlyBehavior {
 6
 7     public void fly() {
 8         // TODO Auto-generated method stub
 9       System.out.println("i can‘t fly!");
10     }
11 }
12
13 public class FlyWithWings implements FlyBehavior {
14
15     public void fly() {
16         // TODO Auto-generated method stub
17         System.out.println("i am flying!");
18     }
19 }
 1 public interface QuackBehavior {
 2    public void quack();
 3 }
 4
 5 public class Quack implements QuackBehavior {
 6
 7     public void quack() {
 8         // TODO Auto-generated method stub
 9         System.out.println("Quack!");
10     }
11 }
12
13 public class MuteQuack implements QuackBehavior {
14
15     public void quack() {
16         // TODO Auto-generated method stub
17         System.out.println("silence");
18     }
19
20 }
 1 public abstract class Duck {
 2    FlyBehavior flyBehavior;
 3    QuackBehavior quackBehavior;
 4
 5    public Duck(){}
 6    public abstract void display();
 7    public void performFly(){
 8        flyBehavior.fly();
 9    }
10    public void performQuack(){
11        quackBehavior.quack();
12    }
13    public void swim(){
14        System.out.println("i can swim!");
15    }
16 }
17
18 public class MallardDuck extends Duck {
19     public MallardDuck(){
20         quackBehavior = new Quack();
21         flyBehavior = new FlyWithWings();
22     }
23     public void display() {
24         // TODO Auto-generated method stub
25         System.out.println("i am a Mallard Duck!");
26     }
27
28 }
1 public class MiniDuckSimulator {
2    public static void main(String[] args){
3        Duck mallard = new MallardDuck();
4        mallard.performFly();
5        mallard.performQuack();
6    }
7 }

输出结果是:

1 i am flying!
2 Quack!

我将Duck mallard = new MallardDuck();改为MallardDuck mallard = new MallardDuck();输出结果是一样的,那么写作duck的原因一定和之前说的“针对接口编程有关”,但是这里我还没看出来用处(应该是我笨,这本精分的书告诉我多和自己对话噗)。

7.动态设定行为:如果不想在鸭子的结构体里实例化,而是通过setter来动态设定。

 1 class Duck{
 2    ....
 3    public void setFlyBehavior(FlyBehavior fb){
 4        flyBehavior = fb;
 5    }
 6
 7    public void setQuackBehavior(QuackBehavior qb){
 8        quackBehavior = qb;
 9    }
10 }

8.那么如果现在要添加一个模型鸭,一套流程走下来该怎么弄呢?

(1)新建鸭子类型:ModelDuck,不会飞但是会叫的模型鸭哦~但是要给它提供一个乘坐火箭飞的技能!即使我不会飞,但是我也是有梦想的!(model duck告诉自己)

 1 public class ModelDuck extends Duck {
 2
 3     public ModelDuck(){
 4         quackBehavior = new Quack();
 5         flyBehavior = new FlyNoWay();
 6     }
 7     public void display() {
 8         // TODO Auto-generated method stub
 9        System.out.println("i am a Model Duck!");
10     }
11
12 }

(2)新建新的飞行为类:FlyRocketPowered

1 public class FlyRocketPowered implements FlyBehavior {
2
3     public void fly() {
4         // TODO Auto-generated method stub
5        System.out.println("I am flying with a rocket!");
6     }
7
8 }

(3)改变测试类,为model duck实现自己的梦想!

1 public class MiniDuckSimulator {
2    public static void main(String[] args){
3        //用的是duck
4        Duck model = new ModelDuck();
5        model.performFly();
6        model.setFlyBehavior(new FlyRocketPowered());
7        model.performFly();
8    }
9 }

输出结果:

1 i can‘t fly!
2 I am flying with a rocket!

可以发现,对原程序需要做的改动几乎为0,而且感觉起来就很动态啊~

9.跳出duck,看看整体的格局。

我们把行为想成是“一族算法”,算法代表鸭子能做的事情(不同的叫法和飞行法),客户就可以使用封装好的飞行和呱呱叫算法族;当你将两个类结合起来用,这就是组合。设计原则:少用继承,所用组合。

这一章所学习的就是策略模式(strategy pattern):策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

时间: 2024-11-05 12:24:04

headFirst学习笔记之一:设计模式入门(4.28)的相关文章

现代C++学习笔记之二入门篇2,数据转换

static_cast:    这种强制转换只会在编译时检查. 如果编译器检测到您尝试强制转换完全不兼容的类型,则static_cast会返回错误. 您还可以使用它在基类指针和派生类指针之间强制转换,但是,编译器在无法分辨此类转换在运行时是否是安全的. dynamic_cast: dynamic_cast在运行时检查基类指针和派生类指针之间的强制转换. dynamic_cast 是比 static_cast 更安全的强制类型转换,但运行时检查会带来一些开销. const_cast:    con

MySQL学习笔记之一 MySQL入门

本人之前接触的关系型数据库主要是oracle和sqlserver,而对于mysql知之甚少,但查阅网上资料发现,mysql与oracle非常相似,所以学起来应该不会很费劲,在总结的时候可能更多的把关注点放在它与oracle的不同之处. 一.简介 MySQL是一个真正的多用户.多线程SQL数据库服务器.SQL(结构化查询语言)是世界上最流行的和标准化的数据库语言.MySQL是一个客户端/服务器结构的实现, 它由一个服务器守护程序mysqld和很多不同的客户程序和库组成. MySQL的普及并不局限于

现代C++学习笔记之二入门篇1

现代 C++ 强调: 基于堆栈的范围,而非堆或静态全局范围. 自动类型推理,而非显式类型名称. 智能指针而不是原始指针. std::string 和 std::wstring 类型(请参见 <string>),而非原始 char[] 数组. 标准模板库 (STL) 容器(例如 vector.list 和 map),而非原始数组或自定义容器. 请参见 <vector>.<list> 和 <map>. STL 算法,而非手动编码的算法. 异常,可报告和处理错误条

spark学习笔记总结-spark入门资料精化

Spark学习笔记 Spark简介 spark 可以很容易和yarn结合,直接调用HDFS.Hbase上面的数据,和hadoop结合.配置很容易. spark发展迅猛,框架比hadoop更加灵活实用.减少了延时处理,提高性能效率实用灵活性.也可以与hadoop切实相互结合. spark核心部分分为RDD.Spark SQL.Spark Streaming.MLlib.GraphX.Spark R等核心组件解决了很多的大数据问题,其完美的框架日受欢迎.其相应的生态环境包括zepplin等可视化方面

MyBatis学习笔记(一)入门

一.理解什么是MyBatis? MyBatis 是支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架. MyBatis 消除了几乎所有的 JDBC 代码和参数的手工设置以及对结果集的检索. MyBatis 可以使用简单的XML 或注解用于配置和原始映射,将接口和 Java 的 POJO( Plain Old Java Objects,普通的Java 对象)映射成数据库中的记录. 1)MyBATIS 目前提供了三种语言实现的版本,包括:Java..NET以及Ruby.(我主要学习java,

jQuery学习笔记之一——jQuery入门与基础核心

因为工作的原因,所以自学了下jQuery,这里以李炎恢老师的教程为自觉教程,并记录学习中遇到的问题. 教程下载地址: http://www.verycd.com/topics/2956408/ 课件下载地址: http://download.csdn.net/download/ip_kv3000/8986013 jQuery类库下载地址: http://jquery.com/ jQuery入门  优势.历史.版本我就不多说了,网上有的是.至于为什么学,因为很有用,为什么非要学他,因为微软加入到了

headFirst学习笔记之十一:代理模式之三保护代理(5.2)

1.动态代理:java在java.lang.reflect包中有自己的代理支持,利用这个包你可以在运行时动态的创建一个代理类,实现一个或者多个接口,并且将方法的调用转发到你指定的类. 保护代理:根据访问权限决定客户可否访问对象的代理. 2.任务:对象村的小伙伴们要相亲啦~请负责帮忙实现约会服务系统. (1)PersonBean接口,允许设置或获取一个人的信息等. 1 public interface PersonBean { 2 public String getName() ; 3 publi

headFirst学习笔记之十:状态模式(5.2)

1.如何从状态图得到真正的代码: (1)找出所有状态:没有25分钱,有25分钱,糖果售罄,售出糖果. (2)创建一个实例变量来持有目前状态,并且定义每个状态的值: 1 final static int SOLD_OUT = 0; 2 final static int NO_QUARTER = 1; 3 final static int HAS_QUARTER = 2; 4 final static int SOLD = 3; 5 6 int state = SOLD_OUT; (3)找出所有的动

headFirst学习笔记之九:迭代器与组合模式(5.1)

1.任务: 大新闻!对象村餐厅和对象村煎饼屋合并了!可以在同一个地方吃早饭和午饭了hohoho(有什么好开森的对象村的小伙伴们好容易满足). 但是有一个问题出现了:煎饼屋的菜单menu是用ArrayList记录菜单项menuItem,但是餐厅的菜单menu使用数组Array记录menuItem.大家都不愿意修改自己的结构,那么java女招待的任务就很繁重了啊,因为取出菜单项的方法就要记住两种了:menuItems.get(i)和menuItems[i]. 1 public class MenuI