Joe
是一名
OO
程序员,他为一家开发模拟鸭子池塘游戏的公司工作,该公司的主要产品是一种可以模拟展示多种会游泳和呷呷叫的鸭子的游戏。这个游戏是使用标准的面向对象技术开发的,系统里所有鸭子都继承于
Duck
基类
,
系统的核心类图如下:
如图所示,在Duck
基类里实现了公共的
quack()
和
swim()
方法,而
MallardDuck
和
RedheadDuck
可以分别覆盖实现自己的
display()
方法,这样即重用了公共的部分,又支持不同子类的个性化扩展。从目前的情况看,这是一个很好的设计,哈!
但是,商场如战场,不进则退。
Joe
的公司最近的日子不好过,盗版泛滥,再加上竞争对手的围追堵劫,已经拖欠好几个月工资了。因此,公司高层决定一定要给系统增加一些超玄的功能,以彻底击垮竞争对手。经过董事会讨论,最终觉得如果能让鸭子飞起来,那么一定可以给对手致命一击。于是
Joe
的上司对董事们拍着胸脯说:“这没有问题,
Joe
是一个
OO
程序员,这对他来说太简单了!我们保证一周内结束战斗。”
接到任务的
Joe
丝毫不敢怠慢,研究了上级的指示以后,发现只要在
Duck
里增加一个
fly()
方法就可以搞定了,这样所有继承
Duck
的鸭子就都拥有了会飞的能力,哈!这回奖金有盼头啦!改进后的系统类图如下:
Joe
的上司很高兴,带着新产品给董事们演示去了
……
……
Joe
的上司:“我正在给董事们演示你会飞的鸭子,但是怎么有很多橡皮鸭子也在四处乱飞呢?你在耍我吗?你还想不想混啦?!”
(
此处省略粗话
100
字
)
Joe
被吓坏了,到手的奖金泡汤了!冷静下来的
Joe
发现,原来在
Duck
类里增加的方法,也同样被继承于
Duck
的
RubberDuck
类继承了,所以就有了会飞的橡皮鸭子,这是严重违反该系统“真实模拟各种鸭子”的原则的!那么该怎么办呢?
Joe
很郁闷!他突然想到:如果在
RubberDuck
类里把
fly()
方法重写一下会如何?在
RubberDuck
类的
fly()
里让橡皮鸭子什么都不做,不就一切
OK
了吗!那以后再增加一个木头鸭子呢?它不会飞也不会叫,那不是要再重写
quack()
和
fly()
方法,以后再增加其它特殊的鸭子都要这样,这不是太麻烦了,而且也很混乱。
最终,
Joe
认识到使用继承不是办法,因为他的上司通知他,董事会决定以后每
6
个月就会升级一次系统,以应对市场竞争,所以未来的变化会很频繁,而且还不可预知。如果以后靠逐个类去判断是否重写了
quack()
或
fly()
方法来应对变化,显然混不下去!
(
Joe
这时很迷惑,为什么屡试不爽的继承,在系统维护升级的时候,无法很好地支持重用呢?)
那么使用接口怎么样?我可以把
fly()
方法放在接口里,只有那些会飞的鸭子才需要实现这个接口,最好把
quack()
方法也拿出来放到一个接口里,因为有些鸭子是不会叫的。就像下面这样:
Joe的上司知道后怒了:“你这样做难道是希望所有需要
quack()
和
fly()
方法的鸭子都去重复实现这两个方法的功能吗?就这么几个鸭子还好说,但是我们有几十、上百个鸭子的时候你怎么办?如果某个方法要做一点修改,难道你要重复修改上百遍吗?你是不是疯啦?”
呵呵!如果你是
Joe
,你该怎么办?
我们知道,并不是所有的鸭子都会飞、会叫,所以继承不是正确的方法。但是虽然上面的使用
Flyable
接口的方法,可以解决部分问题
(
不再有会飞的橡皮鸭子
)
,但是这个解决方案却彻底破坏了重用,它带来了另一个维护的噩梦!而且还有一个问题我们前面没有提到,难道所有的鸭子的飞行方式、叫声等行为都是一模一样的吗?不可能吧!
说到这里,为了能帮助
Joe
摆脱困境,我们有必要先停下来,重新回顾一些面向对象设计原则。请您告诉我:“什么东西是在软件开发过程中是恒定不变的?”,您想到了吗?对,那就是变化本身,正所谓“计划没有变化快”,所以直面“变化这个事实”才是正道!
Joe
面对的问题是,鸭子的行为在子类里持续不断地改变,所以让所有的子类都拥有基类的行为是不适当的,而使用上面的接口的方式,又破坏了代码重用。现在就需要用到我们的第一个设计原则:
Identify the aspects of your application that vary and separate them from what stays the same.
(
找到系统中变化的部分,将变化的部分同其它稳定的部分隔开。
)
换句话说就是:“找到变化并且把它封装起来,稍后你就可以在不影响其它部分的情况下修改或扩展被封装的变化部分。”
尽管这个概念很简单,但是它几乎是所有设计模式的基础,所有模式都提供了使系统里变化的部分独立于其它部分的方法。
OK
!现在我们已经有了一条设计原则,那么
Joe
的问题怎么办呢?就鸭子的问题来说,变化的部分就是子类里的行为。所以我们要把这部分行为封装起来,省得它们老惹麻烦!从目前的情况看,就是
fly()
和
quack()
行为总是不老实,而
swim()
行为是很稳定的,这个行为是可以使用继承来实现代码重用的,所以,我们需要做的就是把
fly()
和
quack()
行为从
Duck
基类里隔离出来。我们需要创建两组不同的行为,一组表示
fly()
行为,一组表示
quack()
行为。为什么是两组而不是两个呢?因为对于不同的子类来说,
fly()
和
quack()
的表现形式都是不一样的,有的鸭子嘎嘎叫,有的却呷呷叫。有了这两组行为,我们就可以组合出不同的鸭子,例如:我们可能想要实例化一个新的
MallardDuck(
野鸭
)
实例,并且给它初始化一个特殊类型的飞行行为
(
野鸭飞行能力比较强
)
。那么,如果我们可以这样,更进一步,为什么我们不可以动态地改变一个鸭子的行为呢?换句话说,我们将在
Duck
类里包含行为设置方法,所以我们可以说在运行时改变
MallardDuck
的飞行行为,这听起来更酷更灵活了!那么我们到底要怎么做呢?回答这个问题,先要看一下我们的第二个设计原则:
Program to an interface, not an implementation.
(面向接口编程,而不要面向实现编程。)
嘿!对于这个原则,不论是耳朵还是眼睛,是不是都太熟悉了!“接口”这个词已经被赋予太多的含义,搞的大家一说点儿屁事就满嘴往外蹦“接口”。那么它到底是什么意思呢?我们这里说的接口是一个抽象的概念,不局限于语言层面的接口
(
例如
C#
里的
interface)
。一个接口也可以是一个抽象类,或者一个基类也可以看作是一种接口的表现形式,因为基类变量可以用来引用其子类。要点在于,我们在面向接口编程的时候,可以使用多态,那么实际运行的代码只依赖于具体的接口
(interface,
抽象类,基类
)
,而不管这些接口提供的功能是如何实现的,也就是说,接口将系统的不同部分隔离开来,同时又将它们连接在一起。
我的神啊!接口真是太伟大了!
(
烂西红柿和臭鸡蛋从四面八方飞来
)
OK!
这回该彻底解决
Joe
的问题了!
根据面向接口编程的设计原则,我们应该用接口来隔离鸭子问题中变化的部分,也就是鸭子的不稳定的行为
(fly()
、
quack())
。我们要用一个
FlyBehavior
接口表示鸭子的飞行行为,这个接口可以有多种不同的实现方式,可以“横”着分,也可以“竖”着分,管它呢!这样做的好处就是我们将鸭子的行为实现在一组独立的类里,具体的鸭子是通过
FlyBehavior
这个接口来调用这个行为的,因为
Duck
只依赖
FlyBehavior
接口,所以不需要管
FlyBehavior
是如何被实现的。如下面的类图,
FlyBehavior
和
QuackBehavior
接口都有不同的实现方式!
Joe已经晕了,“你说了这么多,全是大白话,来点代码行不行,我要
C#
的!”。说到这里,我们也该开始彻底改造这个设计了,并会在最后附加部分代码来帮助大家理解。
第一步:我们要给
Duck
类增加两个接口类型的实例变量,分别是
flyBehavior
和
quackBehavior
,它们其实就是新的设计里的“飞行”和“叫唤”行为。每个鸭子对象都将会使用各种方式来设置这些变量,以引用它们期望的运行时的特殊行为类型
(
使用横着飞,吱吱叫,等等
)
。
第二步:我们还要把
fly()
和
quack()
方法从
Duck
类里移除,因为我们已经把这些行为移到
FlyBehavior
和
QuackBehavior
接口里了。我们将使用两个相似的
PerformFly()
和
PerformQuack()
方法来替换
fly()
和
qucak()
方法,后面你会看到这两个新方法是如何起作用的。
第三步:我们要考虑什么时候初始化
flyBehavior
和
quackBehavior
变量。最简单的办法就是在
Duck
类初始化的时候同时初始化他们。但是我们这里还有更好的办法,就是提供两个可以动态设置变量值的方法
SetFlyBehavior()
和
SetQuackBehavior()
,那么就可以在运行时动态改变鸭子的行为了。
下面是修改后的
Duck
类图:
我们再看看整个设计修改后的类图:
这就是策略模式
前面说了那么多,现在终于到了正式介绍我们今天的主角的时候啦!此刻心情真是好激动啊!其实我们在前面就是使用
Strategy
模式帮
Joe
度过了难过,真不知道他发了奖金后要怎么感谢我们啊。
OK
!下面先看看官方的定义:
The Strategy Pattern defines
a
family of algorithms,encapsulates each one,and makes them
interchangeable. Strategy lets the algorithm vary independently from
clients that use it.
(策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。)
怎么样,有了前面
Joe
的经历,这个定义理解起来还不那么太费劲吧?我想凡是认真看到这里的人,应该都能理解的。那么下面再画蛇添足地罗嗦几句,给那些还不太理解的朋友一个机会吧。
J
Context(
应用场景
):
l
需要使用
ConcreteStrategy
提供的算法。
l
内部维护一个
Strategy
的实例。
l
负责动态设置运行时
Strategy
具体的实现算法。
l
负责跟
Strategy
之间的交互和数据传递。
Strategy(
抽象策略类
)
:
l
定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,
Context
使用这个接口调用不同的算法,一般使用接口或抽象类实现。
ConcreteStrategy(
具体策略类
)
:
l
实现了
Strategy
定义的接口,提供具体的算法实现。
还不理解?!我的神啊!那再看看下面的顺序图吧,这是最后的机会啦!
应用场景和优缺点
上面我们已经看过了
Strategy
模式的详细介绍,下面我们再来简单说说这个模式的优缺点吧!怎么说呢,人无完人,设计模式也不是万能的,每一个模式都有它的使命,也就是说只有在特定的场景下才能发挥其功效。我们要使用好模式,就必须熟知各个模式的应用场景。
对于
Strategy
模式来说,主要有这些应用场景:
1、
多个类只区别在表现行为不同,可以使用
Strategy
模式,在运行时动态选择具体要执行的行为。
(
例如
FlyBehavior
和
QuackBehavior)
2、
需要在不同情况下使用不同的策略
(
算法
)
,或者策略还可能在未来用其它方式来实现。
(
例如
FlyBehavior
和
QuackBehavior
的具体实现可任意变化或扩充
)
3、
对客户
(Duck)
隐藏具体策略
(
算法
)
的实现细节,彼此完全独立。
对于
Strategy
模式来说,主要有如下优点:
1、
提供了一种替代继承的方法,而且既保持了继承的优点
(
代码重用
)
还比继承更灵活
(
算法独立,可以任意扩展
)
。
2、
避免程序中使用多重条件转移语句,使系统更灵活,并易于扩展。
3、
遵守大部分
GRASP
原则和常用设计原则,高内聚、低偶合。
对于
Strategy
模式来说,主要有如下缺点:
1、
因为每个具体策略类都会产生一个新类,所以会增加系统需要维护的类的数量。
分享到:
相关推荐
策略模式结合模板方法模式的设计思路 策略模式结合模板方法模式是策略模式的一种变形,目的是为了解决策略模式中的一些共性问题。在策略模式中,经常会出现这样一种情况,就是发现这一系列算法的实现上存在公共功能...
SpringBoot结合策略模式实战套路 策略模式是一种常用的设计模式,它可以使我们的代码更加灵活、可维护和可扩展。在SpringBoot项目中,策略模式可以与依赖注入机制相结合,实现更加灵活的业务逻辑处理。在本文中,...
设计模式之策略模式 鸭子问题 策略模式是一种经典的设计模式,通过鸭子问题,可以让学习者更好地了解设计模式的概念和实现。策略模式的主要思想是定义一系列的算法,并将每一个算法封装起来,使它们可以相互替换。...
桥接模式和策略模式是软件设计模式中的两种重要模式,它们在实现上有着相似之处,但各自的应用场景和设计理念有所不同。下面将详细阐述这两种模式的特点、区别以及它们在实际编程中的应用。 首先,桥接模式(Bridge...
策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在Java中,策略模式通过定义一系列的算法,并将每一个算法封装起来,使它们可以相互替换,让算法独立于使用它的客户而变化。这种模式通常用于处理多种...
在Spring框架中,策略模式是一种常见的设计模式,它允许我们定义一组可互换的策略,这些策略可以在运行时根据需求动态选择。这篇文章将深入探讨如何在Spring中运用策略模式,并结合源码分析其工作原理。 策略模式的...
策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在Java中,策略模式通常用于将算法封装到不同的类中,使得可以根据需要动态选择并应用这些算法。本示例将详细介绍如何通过两种方法实现策略模式:一种...
本文将探讨三个重要的设计模式:抽象工厂模式、工厂方法模式以及策略模式,并结合一个实际的场景——手机加工厂,来具体阐述它们的应用。 首先,我们来看**抽象工厂模式**。这个模式主要用于创建相关或依赖对象的...
Java 设计模式之策略模式与状态模式 策略模式是 Java 中的一种设计模式,它主要用于解决系统与第三方接口进行数据交互的问题。当系统需要与多种格式的数据进行交互时,使用策略模式可以很好地解决这个问题。例如,...
在"策略模式封装的几个加密解密算法源码"中,我们主要关注的是如何使用策略模式来封装常见的加密解密算法,如BASE64和MD5。 1. **BASE64编码**:BASE64是一种用于将二进制数据编码为ASCII字符的编码方式,以便在...
策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在软件开发中,我们经常遇到需要根据不同条件或选择执行不同算法的情况。策略模式提供了一种将算法封装到独立可互换的策略对象中,使得算法的变化独立...
策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在软件开发中,我们经常遇到需要根据不同的条件或场景来执行不同算法的情况。策略模式就是为了解决这类问题而提出的,它将每种算法封装到具有共同接口...
策略模式和代理模式是设计模式中的两种常见模式,它们在软件开发中扮演着重要的角色,尤其是在JavaScript中,这两种模式提供了更加灵活和可维护的代码结构。 策略模式(Strategy Pattern)是一种行为设计模式,它...
策略模式(Template模式) 策略模式是设计模式中的一种 객체行为型模式,它定义了一系列算法,封装每一个算法,并使它们可以互相替换。策略模式使得算法可以独立于使用它的客户而变化。 概述 在软件开发中,经常...
策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在Java中,策略模式主要通过定义一系列的算法,并将每一个算法封装起来,使它们可以互相替换,让算法独立于使用它的客户而变化。 首先,策略模式的...
策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在Java中,策略模式通常涉及接口或抽象类的实现,允许程序在运行时选择并应用不同的算法或策略。这种模式的核心在于将算法封装到独立的可互换的策略中...
策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在Java中,策略模式允许我们定义一组算法或策略,并将每个策略封装为一个类,使得它们可以互换,而不会影响到客户端代码。这种模式的核心在于"策略",...
策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在策略模式中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为模式。 策略模式定义了一系列的算法,并将每一个算法封装起来,使...
策略模式的设计与实现 策略模式是一种常用的设计模式,它定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。策略模式的主要优点是它可以使得算法的变化独立于使用算法...
策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在软件工程中,当一个系统需要在不同时间执行不同的算法或者行为时,策略模式就显得尤为有用。这种模式将算法封装到独立的可相互替换的策略类中,使得...