HeadFirst设计模式学习之OO设计模式入门

发布时间 2023-09-18 17:10:03作者: Chimengmeng

【一】引入 --- 鸭子

  • 无论在哪门编程语言中,都离不开我们最熟悉的鸭子模型,因此作者在引入部分也是利用鸭子作为案例引入我们进行入门的学习

【1】鸭子游戏

  • 现在我们需要做一款模拟鸭子游泳的游戏
  • 在游戏中,有不同的鸭子,不同的鸭子都会游泳和呱呱叫
  • 而这款游戏的实现思路就是一个鸭子的超类(Superclass),让所有鸭子都继承这个超类
    • 所以每个鸭子就都会游泳和呱呱叫

image-20230917155028521

【2】创新 -- 鸭子能飞

  • 在经过一段时间的运行后,公司提出了新的需求,让我们实现鸭子会飞,这个功能
  • 而我们的第一想法就是在我们的超类中增加一个鸭子能飞的方法

image-20230917155237703

【3】继承超类引发的问题 -- 橡皮鸭会飞了!

  • 虽然在超类中增加一个会飞的方法确实解决了我们当前的需求
  • 但是我们确忽略了一件很重要的事!
    • 并非所有的鸭子都有会飞的功能
  • 在超类中增加会飞的这个方法会让所有继承超类的子类都会有会飞的功能
    • 但是我们都知道,橡皮鸭作为一个没有生命的物体来说,他是没有会飞的功能的
  • 对代码的局部修改,影响层面可不只是局部

image-20230917155650905

【4】解决思路 -- 继承

  • 我们想到了一个办法,那就是继承
    • 既然我们继承了超类中的所有方法,正如每一只鸭子的叫声是不一样的,也就是每一只鸭子都重写了他的叫声这个方法
    • 那我们就依照以上同样的思路,对会飞这个方法进行重写,让橡皮鸭失去会飞的这个功能
  • 但是我们又产生了新的问题
    • 现在我们使用的是橡皮鸭,他会游泳也会叫,但是不能飞,因此我们重写的会飞这个方法,让他不会飞
    • 但是我们又有了一只新的鸭子,叫诱饵鸭,诱饵鸭既不会飞也不会叫
  • 因此有了
    • 橡皮鸭,不会飞但会叫
    • 诱饵鸭,不会飞也不会叫

image-20230917160136118

【5】解决思路 -- 接口

  • 既然这些鸭子只能实现其中的某几个方法
    • 那我们不如将其中的方法都抽离出来,成为一个单独的类,而每一个单独的类都会继承超类
      • 例如会飞的功能类、会游泳的功能类、会叫的功能类
    • 当一只鸭子需要实现其中的某个功能时,我们只需要让他继承我们相应的子类,即可实现相关的功能
      • 例如我们让橡皮鸭继承会游泳的功能类和会叫的功能类
      • 我们让诱饵鸭只继承会游泳的功能类

image-20230917160510568

【6】接口导致的重复性问题

  • 虽然使用接口能一定程度上解决我们当前的需求
  • 但是这样一来因为我们每一只鸭子都需要继承不同的接口,去重写各自对应的方法,这也就导致了我们的重复性代码逐渐增多
  • 甚至是我们每一只鸭子会飞的动作都是不一样的,那我们就需要重写N个会飞的方法

【7】OO软件设计原则

  • 继承不能帮我们很好的解决问题,因为鸭子的行为在子类中需要不断的改变,并且所有的子类这些行为都有不恰当的地方
  • Fly接口和Quack接口一开始听起来很不错,解决了继承产生的问题(具有特定功能的鸭子继承相应的方法)
    • 但是Java接口不具备实现的功能,每一个继承接口的鸭子都必须重写这个接口才能实现相应的功能,这样所有的接口都无法达到代码的复用
    • 这也就意味着,当你想修改某一个行为或方法时,你都必须到某一个具体的类中去修改它的实现方法,如果不小心改错了东西,那可能就会产生新的问题
  • 而为了解决以上的问题,我们有了一个新的设计原则
    • 找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起。
  • 把会变化的部分取出来,并 "封装起来" ,好让其他部分不会受到影响
  • 结果如何?
    • 代码变化引起不经意的后果变少,系统变得更加具有弹性。

【二】OO设计原则应用在鸭子上

【1】分开变化和不变化的部分

  • 如何区分变化的和不会变化的部分呢?
    • 就目前来说,除了 飞 (fly) 和 叫 (quack) 的问题之外,超类的其他功能都是正常的,没有特别需要修改的地方。
  • 现在为了区分变化的和不会变化的两部分,我们建立两组类
    • 一个是 飞 (fly) 相关的
      • 跑着飞
      • 跳着飞
    • 一个是 叫 (quack) 相关的
      • 例如一个类叫呱呱叫
      • 一个类叫 吱吱叫
      • 一个类叫 安静

image-20230917210255258

【2】设计鸭子的行为

  • 如何设计实现飞行和呱呱叫的行为的类?
  • 我们都希望一切能有弹性,毕竟,正是因为一开始的鸭子的行为没有弹性,所以我们才会想去让他变得有弹性。
  • 我们还想希望 "指定" 行为到指定鸭子的实例。
  • 比方说我们实例得到一个绿头鸭
    • 指定它具有特定 "类型" 的飞行行为
    • 后期我们希望他还具有或者变为其他的飞行行为
    • 既然如此,那我们顺便就让鸭子的行为可以动态改变
  • 总结来说,就是我们应该在鸭子类中包含设定行为的方法,这样在 "运行过程中",我们就可以 "改变" 指定实例的飞行行为。

【3】针对接口编程设计原则

  • 我们利用接口代表某个行为
    • 比方说 FlyBehavior 与 QuackBehaver,而行为的每个实现都将实现其中的一个接口
  • 基于上述方案,我们这次让呀子类不会负责实现 Flying 和 Quacking接口
    • 而是由我们制造的专门一组其他的类专门实现 FlyBehavior 和 QuackBehavior
    • 这种类我们称之为行为类
    • 由行为类而不是鸭子类来实现行为接口
  • 这样的做法不同于上述我们的方法
    • 以前
      • 行为来自鸭子超类的具体实现,或是继承某个接口并由子类自行实现而来
      • 这两种做法都是依赖于 "实现" 我们被实现绑的死死的,没办法更改行为(除非我们自定义行为,即写更多的代码)
    • 现在
      • 鸭子的子类将使用接口( FlyBehavior 和 QuackBehavior )所表示的行为
      • 所以实际上的 "实现" 并不会被绑定死在鸭子子类中
      • 换句话说,特定的具体行为编写在实现了 FlyBehavior 和 QuackBehavior 的类中

image-20230917212213119

【三】针对接口编程

【1】什么是针对接口编程

  • "针对接口编程" 真正的意思是 "针对超类型(supertype)" 编程

【2】针对接口编程的接口解释

  • 这里所谓的 "接口" 有多个含义
    • 接口是一个 "概念"
    • 接口也是一种 Java 的 interface 构造
  • 我们可以在不用设计 Java 的 interface 的情况下 "针对接口编程" , 关键就在于多态
  • 利用多态,程序可以针对超类型编程,执行时会根据实际情况执行到真正的行为,不会绑死在超类型的行为上

【3】如何理解针对超类型(supertype)

  • 变量的声明类型应该是超类型,通常是一个抽象类或者是一个接口
  • 这样的话,只要是具体实现此超类型的类所产生的对象,都可以指定给这个变量
  • 这也就表明,声明类时不用理会以后执行时的真正对象类型

【4】多态的例子

  • 假设我们有一个抽象类 Animal,有两个具体的实现 Dog 和 Cat,他们都继承了 Animal

(1)针对实现编程

Dog d = new Dog();
d.bark();
  • 声明一个 d 为 Dog 类型
  • d 是 Animal 的具体实现,会造成我们必须针对具体实现编码

(2)针对接口/超类型编程

Animal animal = new Dog();
animal.makeSound();
  • 我们知道该对象是 Dog
  • 但是我们现在利用 animal 进行多态调用

(3)拓展

  • 除了上述的方便之处之外
  • 子类实例化的动作不再需要在代码中硬编码
    • 例如 new Dog();
  • 而是 "在运行时才指定具体实现的对象"
a = getAnimal();
a.makeSound();
  • 我们不知道实际的子类型是什么
  • 我们只关心他知道如何正确的进行 makeSound() 的动作就够了

【5】代码小结

// 声明一个超类 Animal
abstract class Animal {
    abstract void makeSound();
}

// 声明一个子类 Dog ,继承自超类 Animal
class Dog extends Animal {
    void makeSound() {
        System.out.println("Dog barks");
    }
}

// 声明一个子类 Cat ,继承自超类 Animal
class Cat extends Animal {
    void makeSound() {
        System.out.println("Cat meows");
    }
}

// 多态实例
public class Main {
    public static void main(String[] args) {
        // 声明一个 animal 实例 ,来自 Dog
        Animal animal = new Dog();
        animal.makeSound(); // 输出: Dog barks
		
        // 在代码运行过程中 动态的修改我们的实例来达到实现其对应的功能 ,来自 Cat
        animal = new Cat();
        animal.makeSound(); // 输出: Cat meows
    }
}
# 声明一个基类 Animal ,并且 Animal 有一个 make_sound 方法,但是没有具体实现
class Animal:
    def make_sound(self):
        pass

# 声明一个 Dog 继承自 基类 Animal ,并且重写了 基类中的 make_sound 方法
class Dog(Animal):
    def make_sound(self):
        print("Dog barks")

# 声明一个 Cat 继承自 基类 Animal ,并且重写了 基类中的 make_sound 方法
class Cat(Animal):
    def make_sound(self):
        print("Cat meows")

# 声明实例对象
animal = Dog()
# 调用对象的方法
animal.make_sound()  # 输出: Dog barks

# 在代码运行过程中 动态的修改我们的实例来达到实现其对应的功能 ,来自 Cat
animal = Cat()
animal.make_sound()  # 输出: Cat meows

【四】实现鸭子的行为

【0】原理讲解

image-20230917215054450

  • 在此,我们有两个接口,FlyBehavior 和 QuackBehavior ,还有他们对应的类,负责实现具体的行为

    • FlyBehavior 是一个接口,所有飞行类都实现它,所有新的飞行类都必须实现 fly 方法

      • FlyWithWings

        • 实现了所有有翅膀的鸭子会飞行的动作
        fly(){
            // 实现鸭子会飞
        }
        
      • FlyNoWay

        • 实现了所有有翅膀的鸭子不会飞行的动作
        fly(){
            // 什么都不会做,不会飞
        }
        
    • QuackBehavior 是一个接口,所有呱呱叫的类都实现它,所有新的飞行类都必须实现 quack 方法,一个接口只包含一个需要实现的 quack 方法

      • Quack

        • 针对呱呱叫
        quack(){
            // 实现鸭子呱呱叫
        }
        
      • Squack

        • 名为呱呱叫,其实是吱吱叫
        quack(){
            // 实现鸭子吱吱叫
        }
        
      • Mutequack

        • 名为呱呱叫,其实不出声
        quack(){
            // 什么都不做,不会叫
        }
        

【1】Java代码示例

// FlyBehavior 接口
public interface FlyBehavior {
    void fly();
}

// FlyWithWings 类实现 FlyBehavior 接口,表示有翅膀的鸭子会飞行
public class FlyWithWings implements FlyBehavior {
    public void fly() {
        System.out.println("鸭子会飞");
    }
}

// FlyNoWay 类实现 FlyBehavior 接口,表示有翅膀的鸭子不会飞行
public class FlyNoWay implements FlyBehavior {
    public void fly() {
        // 什么都不做,不会飞
    }
}

// QuackBehavior 接口
public interface QuackBehavior {
    void quack();
}

// Quack 类实现 QuackBehavior 接口,表示鸭子发出呱呱叫声
public class Quack implements QuackBehavior {
    public void quack() {
        System.out.println("鸭子呱呱叫");
    }
}

// Squack 类实现 QuackBehavior 接口,表示鸭子发出吱吱叫声
public class Squack implements QuackBehavior {
    public void quack() {
        System.out.println("鸭子吱吱叫");
    }
}

// MuteQuack 类实现 QuackBehavior 接口,表示鸭子不发出声音
public class MuteQuack implements QuackBehavior {
    public void quack() {
        // 什么都不做,不会叫
    }
}

【2】Python代码示例

# FlyBehavior 接口
class FlyBehavior:
    def fly(self):
        pass

# FlyWithWings 类实现 FlyBehavior 接口,表示有翅膀的鸭子会飞行
class FlyWithWings(FlyBehavior):
    def fly(self):
        print("鸭子会飞")

# FlyNoWay 类实现 FlyBehavior 接口,表示有翅膀的鸭子不会飞行
class FlyNoWay(FlyBehavior):
    def fly(self):
      	# 什么都不做,不会飞
        pass

# QuackBehavior 接口
class QuackBehavior:
    def quack(self):
        pass

# Quack 类实现 QuackBehavior 接口,表示鸭子发出呱呱叫声
class Quack(QuackBehavior):
    def quack(self):
        print("鸭子呱呱叫")

# Squack 类实现 QuackBehavior 接口,表示鸭子发出吱吱叫声
class Squack(QuackBehavior):
    def quack(self):
        print("鸭子吱吱叫")

# MuteQuack 类实现 QuackBehavior 接口,表示鸭子不发出声音
class MuteQuack(QuackBehavior):
    def quack(self):
      	# 什么都不做,不会叫
        pass

【五】小问题答疑

【1】是不是一定要先把系统做出来,再回头进行封装?

  • 问题
    • 是不是一定要先把系统做出来,再看看有没有那些地方需要变化,然后才回头去把这些地方分离 & 封装
  • 解释
    • 不尽然。
    • 通常在设计系统时,会预先考虑在那些地方未来可能需要发生变化,于是会提前在代码中加入一些弹性
    • 同时,原则与模式可以应用在软件开发声明周期的任何阶段

【2】Duck应不应该设计成一个接口?

  • 问题
    • Duck应不应该也设计成一个接口
  • 解释
    • 在上述案例中,这样做不恰当
    • 按照上述我们所做,我们已经对一切做了相应的整合和优化,并且让 Duck 成为一个具体的类
    • 这样就可以让衍生的特定类(例如绿头鸭)具备Duck共同的属性和方法
    • 我们已经在 Duck 的继承结构中删除了变化的部分,原来的问题已经得到了解决
    • 所以我们不需要将Duck 单独设计成一个接口

【3】类不是应该同时具备 "状态" 和 "行为"吗?

  • 问题
    • 用一个类代表一个行为,感觉似乎很奇怪
    • 类不是应该代表某一种 "东西" 吗
    • 类不是应该同时具备 "状态" 和 "行为"吗
  • 解释
    • 在 OO 系统中,类代表的东西一般是既有状态(实例变量)又有方法
    • 但是在本案例中,碰巧这只是一个行为
    • 但是即使是行为,也仍然可以有状态和方法
    • 比如
      • 飞行的行为可以具有实例变量,记录飞行行为的属性
      • 比如每秒翅膀拍动几下
      • 最大高度
      • 速度
      • ...

【4】拓展问题

  • 问题
    • 使用上述新的设计模式后,如果我们想加上个火箭动力的飞行动作到Dock系统中,如何做?
  • 问题
    • 除了鸭子之外,你还能想到什么类会需要用到呱呱叫行为?

【六】整合鸭子的行为

  • 现在的关键是
    • 鸭子会将飞行和呱呱叫的动作 "委托" 别人处理,而不是使用定义的Duck类(或子类)内的呱呱叫和飞行方法

【1】首先

  • 在Duck类中 "加入两个实例变量" ,分别是 "FlyBehavior" 和 "QuackBehavior" 声明为接口类型(不是具体实现类型)
    • 每个鸭子对象都会动态的设置这些变量以在运行时引出正确的行为(例如FlyWithWings、Squack等)
  • 我们将Duck类和子列中的 fly 和 quack 删除 ,因为这些方法我们已经都放到了我们的接口当中
  • 我们用两个相似的方法 performFly() 和 performQuack() 取代 Duck 类中的 fly() 和 quack()

image-20230918152138888

【2】performQuack实现

public class Duck {
    // 每只鸭子都会引用实现 QuackBehavior 接口的对象
    QuackBehavior quackBehavior;
    // 其他的方法...
    
    // 鸭子对象不亲自处理呱呱叫行为,而是委托给 quackBehavior 引用的对象
    public void performQuack(){
        quackBehavior.quack();
    } 
}
  • 当鸭子想进行呱呱叫的时候,Duck对象只要叫quackBehavior对象去呱呱叫就可以了
  • 在上述代码中,我们不在乎 quackBehavior 接口的对象到底是什么,我们只关心该对象知道如何进行呱呱叫就足够了

【3】如何设定 "flyBehavior" 和 "quackBehavior" 的实例变量

  • MallardDuck
public class MallardDuck extends Duck{
    public MallardDuck(){
        // 绿头鸭使用 Quack 类处理呱呱叫,所以当performQuack()被调用时,叫的职责被委托给Quack对象
        // 我们就得到了真正的呱呱叫
        quackBehavior = new Quack();
        
        // 使用 FlyWithWings 作为其FlyBehavior类型
        flyBehavior = new FlyWitnWings();
    }
    public void display(){
        System.out.println("I am a ream Mallard duck");
    }
}
  • 那么,绿头鸭会真的 呱呱叫 而不是 吱吱叫 或者是 不出声 ,如何办到?

    • 当 MallardDuck实例化时,他的构造器会把继承来的 quackBehavior 实例变量初始化成Quack类型的新实例

    • Quack 是 QuackBehavior 的具体实现类

  • 同样的我们也可以将上述内容应用到 飞行行为上

    • MallardDuck的构造器将flyBehavior实例变量初始化成FlyWithWings类型的实例
    • FlyWithWings 是 FlyBehavior 的具体实现类
  • 基于上面我们写到的设计方式中我们知道,我们将不对具体实现进行编程。但是我们在上面的构造器内制造一个具体实现的Quack实现类的实例
    • 在后面我们学了更多的设计模式后,我们会对这一行为进行就成
    • 虽然我们把行为设定为具体的类
      • 即通过实例化类似 Quack 或 FlyWithWings 的行为类,并把它指定到行为引用变量中
    • 但是还是可以在运行时,动态的修改它
    • 所以目前的做法仍然是具有弹性的,只是我们在初始化实例变量的做法不够弹性

【七】测试代码

【1】载入并编译鸭子(Duck)类

  • Duck.java
public abstract class Duck {
    
    // 为行为接口类型声明两个引用变量
    // 所有鸭子子类,在同一个package中都会继承他们
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;

    public Duck() {

    }

    public abstract void display();

    public void performFly() {
		// 委托给行为类
        flyBehavior.fly();
    }

    public void performQuack() {
        // 委托给行为类
        quackBehavior.quack();
    }

    public void swim() {
        System.out.println("所有鸭子都会游泳!");
    }
}
  • MallardDuck.java
public class MallardDuck extends Duck{
    public MallardDuck(){
        // 绿头鸭使用 Quack 类处理呱呱叫,所以当performQuack()被调用时,叫的职责被委托给Quack对象
        // 我们就得到了真正的呱呱叫
        quackBehavior = new Quack();

        // 使用 FlyWithWings 作为其FlyBehavior类型
        flyBehavior = new FlyWithWings();
    }
    public void display(){
        System.out.println("I am a ream Mallard duck");
    }
}

【2】载入并编译飞行(Fly)类

  • FlyBehavior.java
public interface FlyBehavior {
    // 所有飞行类都必须实现的接口
    public void fly();
}
  • FlyWithWings.java
public class FlyWithWings implements FlyBehavior{
    // 飞行行为的实现,给会飞的鸭子用
    public void fly(){
        System.out.println("我会飞!");
    }
}
  • FlyNoWay.java
public class FlyNoWay implements FlyBehavior {
    // 飞行行为的实现,给不会飞的鸭子用
    // 包括橡皮鸭和诱饵鸭
    public void fly() {
        System.out.println("我不会飞!");
    }
}

【3】载入并编译叫声(quack)类

  • QuackBehavior.java
public interface QuackBehavior {
    public void quack();
}
  • Quack.java
public class Quack implements QuackBehavior{
    public void quack(){
        System.out.println("Quack");
    }
}
  • Squeak.java
public class Squeak implements QuackBehavior {
    public void quack() {
        System.out.println("Squeak");
    }
}
  • MuteQuack.java
public class MuteQuack implements QuackBehavior{
    public void quack(){
        System.out.println("Silence");
    }
}

【4】编译并测试类

  • MiniDuckSimulator.java
public class MiniDuckSimulator {
    public static void main(String [] args){
        Duck mallard = new MallardDuck();
        
        // 这会调用 MallardDuck 继承类的performQuack()方法,进而委托给该对象的QuackBehavior对象处理
        // 也就是说调用继承的 quackBehavior 引用对象的quack()
        mallard.performQuack();
        
        // performFly 同理
        mallard.performFly();
    }
}

【5】运行结果

image-20230918161554071

【6】Python版本复现

# -*-coding: Utf-8 -*-
# @File : 01start .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/9/18
# 定义飞行行为接口
class FlyBehavior:
    def fly(self):
        pass


# 飞行行为实现类:会飞
class FlyWithWings(FlyBehavior):
    def fly(self):
        print("我会飞!")


# 飞行行为实现类:不会飞
class FlyNoWay(FlyBehavior):
    def fly(self):
        print("我不会飞!")


# 定义叫声行为接口
class QuackBehavior:
    def quack(self):
        pass


# 叫声行为实现类:呱呱叫
class Quack(QuackBehavior):
    def quack(self):
        print("呱呱叫")


# 叫声行为实现类:吱吱叫
class Squeak(QuackBehavior):
    def quack(self):
        print("吱吱叫")


# 叫声行为实现类:不发声
class MuteQuack(QuackBehavior):
    def quack(self):
        print("静音")


# 定义鸭子类
class Duck:
    fly_behavior = None
    quack_behavior = None

    def __init__(self):
        pass

    def display(self):
        pass

    def perform_fly(self):
        self.fly_behavior.fly()

    def perform_quack(self):
        self.quack_behavior.quack()

    def swim(self):
        print("所有鸭子都会游泳!")


# 继承鸭子类的绿头鸭类
class MallardDuck(Duck):

    def __init__(self):
        self.quack_behavior = Quack()
        self.fly_behavior = FlyWithWings()

    def display(self):
        print("我是一只真正的绿头鸭!")


# 主函数
def main():
    mallard = MallardDuck()

    # 这会调用 MallardDuck 继承类的perform_quack()方法,进而委托给该对象的QuackBehavior对象处理
    # 也就是说调用继承的 quack_behavior 引用对象的quack()
    mallard.perform_quack()

    # perform_fly 同理
    mallard.perform_fly()


if __name__ == "__main__":
    main()
  • 运行结果

image-20230918162727102

【七】动态设定行为

【1】引入

  • 在鸭子类中我们定义了许多动态的功能,但是没有用到
  • 假设我们想在鸭子类中通过设定方法 来设定鸭子的行为,而不是在鸭子的构造器内实例化

【2】在Duck中增加新方法

public abstract class Duck {
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;

    public Duck() {

    }

    public abstract void display();

    public void performFly() {

        flyBehavior.fly();
    }

    public void performQuack() {
        quackBehavior.quack();
    }

    public void swim() {
        System.out.println("所有鸭子都会游泳!");
    }
	
    // 新增方法 :方便随时调用方法改变鸭子的飞行行为
    public void SetFlyBehavior(FlyBehavior fb) {
        flyBehavior = fb;
    }
	
    // 新增方法 :方便随时调用方法改变鸭子的叫声行为
    public void SetQuackBehavior(QuackBehavior qb) {
        quackBehavior = qb;
    }
}

【3】创建一个新的鸭子:模型鸭

  • ModelDuck.java
public class ModelDuck  extends Duck{
    public ModelDuck(){
        // 我们定义的模型鸭是没有飞的功能的
        flyBehavior = new FlyNoWay();
        quackBehavior = new Quack();
    }
    public void display(){
        System.out.println("我是一只模型鸭!");
    }
}

【4】定义一个新的飞行方式(火箭)

  • FlyRockerPowered.java
public class FlyRockerPowered implements FlyBehavior {
    public void fly() {
        System.out.println("我坐火箭飞喽!");
    }
}

【5】修改测试类

  • MiniDuckSimulator.java
public class MiniDuckSimulator {
    public static void main(String[] args) {
        Duck mallard = new MallardDuck();

        // 这会调用 MallardDuck 继承类的performQuack()方法,进而委托给该对象的QuackBehavior对象处理
        // 也就是说调用继承的 quackBehavior 引用对象的quack()
        mallard.performQuack();

        // performFly 同理
        mallard.performFly();

        Duck model = new MallardDuck();
        // 不会飞的鸭子
        // 第一次调用 performFly 会被委托给 flyBehavior 对象,即FlyNoWay实例
        // 该对象是在模型鸭构造器中默认设置的
        model.performFly();
        // 装备火箭的鸭子
        // 这会调用 继承来的 方法,把火箭装在到模型鸭中,模型鸭就具有了火箭动力飞行
        model.SetFlyBehavior(new FlyRockerPowered());
        // 如果正常,这里应该会让模型鸭装备火箭
        model.performFly();
    }
}

【6】运行

image-20230918164445304

【7】Python复写

# 定义飞行行为接口
class FlyBehavior:
    def fly(self):
        pass

# 定义飞行方式:使用翅膀飞行
class FlyWithWings(FlyBehavior):
    def fly(self):
        print("我会飞!")

# 定义飞行方式:不会飞行
class FlyNoWay(FlyBehavior):
    def fly(self):
        print("我不会飞!")

# 定义鸭子叫声接口
class QuackBehavior:
    def quack(self):
        pass

# 定义叫声:呱呱叫
class Quack(QuackBehavior):
    def quack(self):
        print("Quack")

# 定义叫声:吱吱叫
class Squeak(QuackBehavior):
    def quack(self):
        print("Squeak")

# 定义叫声:不发声
class MuteQuack(QuackBehavior):
    def quack(self):
        print("Silence")

# 定义鸭子类
class Duck:
    def __init__(self):
        self.fly_behavior = None
        self.quack_behavior = None

    def display(self):
        pass

    def perform_fly(self):
        self.fly_behavior.fly()

    def perform_quack(self):
        self.quack_behavior.quack()

    def swim(self):
        print("所有鸭子都会游泳!")

    def set_fly_behavior(self, fb):
        self.fly_behavior = fb

    def set_quack_behavior(self, qb):
        self.quack_behavior = qb

# 定义绿头鸭类
class MallardDuck(Duck):
    def __init__(self):
        super().__init__()
        self.quack_behavior = Quack()
        self.fly_behavior = FlyWithWings()

    def display(self):
        print("I am a real Mallard duck")

# 定义模型鸭类
class ModelDuck(Duck):
    def __init__(self):
        super().__init__()
        self.quack_behavior = Quack()
        self.fly_behavior = FlyNoWay()

    def display(self):
        print("我是一只模型鸭!")

# 定义火箭动力飞行
class FlyRocketPowered(FlyBehavior):
    def fly(self):
        print("我坐火箭飞喽!")

# 测试
if __name__ == "__main__":
    mallard = MallardDuck()
    mallard.perform_quack()
    mallard.perform_fly()

    model = ModelDuck()
    model.perform_fly()
    model.set_fly_behavior(FlyRocketPowered())
    model.perform_fly()
  • 运行结果

image-20230918164808174

【八】封装行为的大局观

  • 在上述内容中我们已经深入研究了鸭子模型的设计

  • 下面是整个重新设计后的类结构

    • 鸭子继承Duck
    • 飞行行为实现FlyBehavior接口
    • 呱呱叫行为实现QuackBehavior接口
  • 高级一点的用法,我们将鸭子行为当做一组行为,把行为想成是一簇算法

    • 对比到上面的模型中,算法代表着鸭子能干的事(不同的叫法和飞行方法)
    • 这样的做法能让我们更容易的用于一群类计算

image-20230918165317115

【九】设计原则:多用组合,少用继承

【1】有一个 可能比 是一个更好

  • 有一个
    • 每一只鸭子都有一个 FlyBehavior 和 一个 QuackBehavior
    • 好比将飞行和呱呱叫委托给他们代为处理
  • 当将两个类结合在一起用事,这就是组合
    • 这种做法和 "继承" 不同的地方在于
    • 鸭子的行为不是继承来的,而是和适当的行为对象 "组合" 而来

【2】设计原则:多用组合,少用继承

  • 使用组合建立关系系统具有很大的弹性,不仅可将算法簇封装成类,更可以在 "在运行时动态的改变行为"
  • 只要组合的行为对象符合正确的接口标准即可
  • 组合在许多设计模式中,各有优缺点

【十】总结

【1】策略模式

  • 在上面我们应用到的就是策略模式

  • 策略模式定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户

【2】OO 设计模式

image-20230918170113184