Design Pattern | 策略模式( Strategy Pattern )前端 TypeScript

前言

大家好,我是Makuta。最近又重学了一遍设计模式,想借此机会整理下资料,后面的方式我会结合代码和图片,使用讲故事的方式来解释,下面就来看看它们在Design Pattern 中做出怎样的精彩共演吧!

先从简单的模拟鸭子应用做起

假设有个公司做了一套相当成功的模拟鸭子的游戏。游戏中出现各种鸭子,一边游泳戏水,一边呱呱叫,所有鸭子的区别在于外观,比如有白色,黑色,黄色等,所以我们需要设计一个父类(也可以叫超类),然后让各种鸭子去继承这个父类。

 

上面这图体现出了所有鸭子都有鸭叫和游泳的方法,唯一的区别在于外观不一样,所以这时候外观需要使用抽象,然后在各个子类上实现自己的行为。

现在我们得让鸭子飞起来

有一天呢,公司内部头发风暴后产生了一个新的创意,需要有会飞的鸭子,有人肯定会说我花一下功夫就可以解决了,在鸭子类上加个会飞的方法就行了,这有什么困难?

可怕的问题发生了......

某天游戏中出现了很多“橡皮鸭子”在屏幕上飞来飞去,到底是怎么回事呢?A经理忽略了一件事,并非所有的鸭子都会飞,A直接在父类上加了个飞行的方法,导致本不应该存在的行为发生了。我们在对类处理时,尤其涉及“维护” 时,为了“复用”目的而使用继承,结局并不完美。

那该如何解决呢?

A经理想到那就从继承上来解决吧,既然橡皮鸭子不能飞,那就在橡皮鸭子的子类上去覆盖变成什么都不做,可是,如果以后加入了别的鸭子比如说木头鸭子那该怎么办呢,不会叫也不会飞,难道又要再一次覆盖吗?所以说继承也有一些缺点:

  • 代码在多个子类中重复
  • 运行时的行为不容易改变
  • 很难知道所有鸭子的全部行为
  • 改变会影响全身,造成其他鸭子不想要的改变

那利用接口如何呢?

我们知道,并非“所有”的子类都具有飞行和鸭叫的行为,所以继承并不是适当的解决方式。虽然飞行接口和鸭叫接口可以解决“一部分问题”,但是却造成代码无法复用,因为接口不具有实现代码,所以继承接口无法达到代码的复用。意味着,无论何时你需要修改某个行为的时候,你必须追踪每一个定义此行为的类中修改它,一不小心,可能会造成新的错误!

这只能算是从一个噩梦跳到另一个噩梦里。甚至,在会飞的鸭子中,飞行的动作可能还有多种变化....?但幸运的是,有一个设计原则,恰好适用于此状况。(终于有救了)

设计原则

重要的事说三遍!!!

找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。

找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。

找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。

换句话来说就是每次有新的需求时,都会使某方面的代码发生变化,那么你可以确定,这部分的代码需要被抽出来,和其他稳定的代码有所区分。好,接下来我们开始把鸭子的行为从鸭子类中取出的时候了!

从哪里开始呢?就我们目前所知,除了飞行和鸭叫的问题之外,鸭子类还算一切正常,似乎没有特别需要经常变化或修改的地方。所以,除了某些小改变之外,我们不打算对鸭子类做太多处理。

接下来我们准备建立两组类(完全远离鸭子的类),一个是飞行相关的的,一个是鸭叫相关的,每一组类将实现各自的动作

设计鸭子的行为

如何设计那组实现飞行和鸭叫的行为的类呢?

我们希望一切能有弹性,毕竟,正是因为一开始鸭子行为没有弹性,才让我们走上现在这条路。我们还想指定行为到鸭子的实例,比如说,我们想产生一个新的绿头鸭实例,并指定特定类型的飞行行为给它。干脆让鸭子的行为可以动态变更好了。换句话来说,我们应该在鸭子类中包含设定行为的方法,这样就可以在运行时去动态的改变绿头鸭的飞行行为。(前面说过继承在运行时不容易动态的改变),有了这些目标要实现,接着看下第二个设计原则

重要!!! (不了解接口和实现的同学,可以自行google了解下

针对接口编程,而不是针对实现编程!

针对接口编程,而不是针对实现编程!

针对接口编程,而不是针对实现编程!

我们利用接口代表每个行为,比方说,FlyBehavior 和 QuackBehavior,而行为的每个实现都将实现其中的一个接口。

所以这次鸭子类不会负责实现 Flying 和 Quacking 接口,反而是由我们制造一组其它类专门实现,这就称为“行为”类。由行为类而不是鸭子类来实现接口。

这样的迥异于以往,以前的做法是:行为来自鸭子父类的具体实现,或是继承某个接口并由子类自行实现而来,这两种做法都是依赖于“实现”。

在我们新的设计中,鸭子的子类将使用接口 FlyBehavior 和 QuackBehavior 表示行为,所以实现不会绑定在鸭子的类中。

两个接口具体实现

 

// 鸭子行为接口
interface FlyBehavior {
  fly(): void
}

// 鸭子会飞实现
class FlyWithWings implements FlyBehavior {
  fly() {
     console.log('i am flying')
  }
}

// 鸭子不会飞的接口和实现
class FlyNoWay implements FlyBehavior {
  fly() {
     console.log("i can't flying")
  }
}

// 鸭叫接口
interface QuackBehavior {
  quack(): void
}

// 嘎嘎叫
class Quack implements QuackBehavior {
  quack() {
    console.log('quack')
  }
}

// 不叫
class MuteQuack implements QuackBehavior {
  quack() {
    console.log('slient')
  }
}

// 吱吱叫
class Squeak implements QuackBehavior {
  quack() {
    console.log('squeak')
  }
}

整合鸭子的行为

关键在于,鸭子现在会将飞行和鸭叫的动作委托别人处理,而不是使用定义在鸭类或者子类的鸭叫和飞行方法。

  1. 首先,在父类中加入两个实例的变量,分别为 flyBehavior 与 quackBehavior,那个为接口类型(而不是具体类实现类型),每个鸭子对象都会动态设置这些行为(FlyWithWings, Squeak等)。 在父类下删除 fly 和 quack 两个方法,并且用一个替代方法去替代 fly 和 quack,比如 performFly() 和 performQuack()
  2. 实现 performFly()

     

    class Duck {
       abstract flyBehavior: FlyWithThings
       abstract quackBehavior: QuackBehavior
    
       public performfly() {
         this.flyBehavior.fly()
       }
    
       public performquack() {
         this.quackBehavior.quack()
       }
    
       public swim() {
         console.log('all swim')
       }
    
       public abstract display(): void
    }

     

  3. 将子类继承父类(也叫抽象类)

     

    class ModelDuck extends Duck {
        flyBehavior = new FlyNoWay()
        quackBehavior = new Squeak()
    
        public display() {
          console.log('display')
        }
    }

     

动态设定行为

  1. 在鸭子类中,加入两个新方法

    class ModelDuck extends Duck {
        flyBehavior = new FlyNoWay()
        quackBehavior = new Squeak()
    
        public display() {
          console.log('display')
        }
        
    
        public setflyBehavior(fb: FlyBehavior) {
          this.flyBehavior = fb
        }
    
        public setQuackBehavior(qb: QuackBehavior) {
          this.quackBehavior = qb
        }
    }

     

  2. 建立一个新的FlyBehavior模型

    class FlyRocketPowered implements FlyBehavior {
    	public fly() {
    		console.log("I'm flying with a rocket! ")
    	}
    }

     

  3. 加上火箭动力

     

    var modelduck = new ModelDuck();
    modelduck.performfly();
    modelduck.setflyBehavior(new FlyRocketPowered());
    modelduck.performfly();

     

总结

有一个关系相当有趣,每一个鸭子都有一个 FlyBehavior 和 一个 QuackBehavior,好将飞行和鸭叫委托给它们代为处理。

当你讲两个类结合起来使用,如同本例一般,就是组合(composition)。这种做法和继承不同的地方在于行为不是继承来的,而是和适当的行为对象“组合”来的。

这是一个很重要的技巧。其实使用了我们的第三个设计原则

重要!!!

多用组合,少用继承

多用组合,少用继承

多用组合,少用继承

如你所见,使用组合建立系统具有很大的弹性,不仅可将算法封装成类,更可以“在运行时动态地改变行为”,只要组合的行为对象符合正确的接口标准即可。

最后恭喜你,学会了策略模式。

😃😃😃😃😃😃😃

 

源码地址:

https://codesandbox.io/s/typescript-playground-export-forked-n388y?file=/index.ts:0-1388

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
策略模式是一种行为设计模式,它允许在运行时选择算法的行为。在TypeScript中,可以使用类和多态性来实现策略模式。 以下是一个使用TypeScript实现策略模式的示例: ```typescript // 定义一个策略接口 interface FlyStrategy { fly(): void; } // 实现具体的飞行策略类 class FlyWithWings implements FlyStrategy { fly() { console.log("用翅膀飞行"); } } class FlyNoWay implements FlyStrategy { fly() { console.log("无法飞行"); } } // 定义一个鸭子类 class Duck { private flyStrategy: FlyStrategy; constructor(flyStrategy: FlyStrategy) { this.flyStrategy = flyStrategy; } // 设置飞行策略 setFlyStrategy(flyStrategy: FlyStrategy) { this.flyStrategy = flyStrategy; } // 执行飞行 performFly() { this.flyStrategy.fly(); } } // 使用策略模式 const duck = new Duck(new FlyWithWings()); duck.performFly(); // 输出:用翅膀飞行 duck.setFlyStrategy(new FlyNoWay()); duck.performFly(); // 输出:无法飞行 ``` 在上面的示例中,我们定义了一个`FlyStrategy`接口,它包含一个`fly`方法。然后,我们实现了两个具体的飞行策略类`FlyWithWings`和`FlyNoWay`,它们分别实现了`FlyStrategy`接口。接下来,我们定义了一个`Duck`类,它包含一个`flyStrategy`属性,用于存储当前的飞行策略。`Duck`类还包含了`setFlyStrategy`和`performFly`方法,用于设置和执行飞行策略。 通过使用策略模式,我们可以在运行时选择不同的飞行策略,而不需要修改`Duck`类的代码。这样,我们可以实现算法的变化独立于使用算法的客户。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值