4、HeadFirst--策略模式(Swift)

1.概述

在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同
选择不同的算法或者策略来完成该功能。如查找、排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,
如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,
每一个方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的
方法中,通过if…else…或者case等条件判断语句来进行选择。这两种实现方法我
们都可以称之为硬编码,如果需要增加一种新的查找算法,需要修改封装算法类
的源代码;更换查找算法,也需要修改客户端调用代码。在这个算法类中封装了
大量查找算法,该类代码将较复杂,维护较为困难。如果我们将这些策略包含在
客户端,这种做法更不可取,将导致客户端程序庞大而且难以维护,如果存在大
量可供选择的算法时问题将变得更加严重。

demo地址,欢迎star

例子1:

商场产品销售:可以正常价格售出,可以打折形式售出,也可以通过积分的形式,不管哪种形式,最后要的就是一个最后的总价

例子2:

出行旅游:我们可以有几个策略可以考虑:可以骑自行车,汽车,做火车,飞 机。每个策略都可以得到相同的结果,但是它们使用了不同的资源。选择策略的依据是费用,时间,使用工具还有每种方式的方便程度 。

例子3:

HeadFirst案例:不同的鸭子具有不同的飞行行为,鸣叫行为,不同的飞行行为或鸣叫行为就是不同的算法策略

2.问题

如何让算法和对象(客户端)分开来,使得算法可以独立于使用它的客户而变化?

3.解决方案

策略模式:
定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。也称为政策模式(Policy)。 策略模式把对象本身和运算规则区分开来,其功能非常强大,因为这个设计模式本身的核心思想就是面向对象编程的多形性的思想。

4.适用性

当存在以下情况时使用Strategy模式

  • 许多相关的类仅仅是行为有异。 “策略”提供了一种用多个行为中的一个行为来配置一个类的方法。即一个系统需要动态地在几种算法中选择一种。
  • 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间 /时间权衡的算法。当这些变体实现为一个算法的类层次时 ,可以使用策略模式。
  • 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构
  • 一个类定义了多种行为 , 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。

5.swift实现结构图

swift策略模式.png

6.模式的组成

  • 环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。
  • 抽象策略类(Strategy):定义所有支持的算法的公共接口。 Context使用这个接口来调用某ConcreteStrategy定义的算法。
  • 具体策略类(ConcreteStrategy):以Strategy接口实现某具体算法。

7.效果

Strategy模式有下面的一些优点:

  • 相关算法系列 Strategy类层次为Context定义了一系列的可供重用的算法或行为。 继承有助于析取出这些算法中的公共功能。
  • 扩展性良好: 继承提供了另一种支持多种算法或行为的方法。你可以直接生成一个Context类的子类,从而给它以不同的行为。但这会将行为硬行编制到 Context中,而将算法的实现与Context的实现混合起来,从而使Context难以理解、难以维护和难以扩展,而且还不能动态地改变算法。最后你得到一堆相关的类 , 它们之间的唯一差别是它们所使用的算法或行为。 将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展。
  • 避免使用多重条件判断 :Strategy模式提供了用条件语句选择所需的行为以外的另一种选择。当不同的行为堆砌在一个类中时 ,很难避免使用条件语句来选择合适的行为。将行为封装在一个个独立的Strategy类中消除了这些条件语句。含有许多条件语句的代码通常意味着需要使用Strategy模式。
  • 算法可以自由切换:实现的选择 Strategy模式可以提供相同行为的不同实现。客户可以根据不同时间 /空间权衡取舍要求从不同策略中进行选择。
  • 降低耦合:策略以相同的方式调用所有的算法,减少客户端与算法类之间的耦合

Strategy模式缺点:

  • 所有策略类都需要对外暴露: 本模式有一个潜在的缺点,就是一个客户要选择一个合适的Strategy就必须知道这些Strategy到底有何不同。此时可能不得不向客户暴露具体的实现问题。因此仅当这些不同行为变体与客户相关的行为时 , 才需要使用Strategy模式。
  • Strategy和Context之间的通信开销 :无论各个ConcreteStrategy实现的算法是简单还是复杂, 它们都共享Strategy定义的接口。因此很可能某些 ConcreteStrategy不会都用到所有通过这个接口传递给它们的信息;简单的 ConcreteStrategy可能不使用其中的任何信息!这就意味着有时Context会创建和初始化一些永远不会用到的参数。如果存在这样问题 , 那么将需要在Strategy和Context之间更进行紧密的耦合。
  • 策略类会增多:可以通过使用享元模式在一定程度上减少对象的数量。 增加了对象的数目 Strategy增加了一个应用中的对象的数目。有时你可以将 Strategy实现为可供各Context共享的无状态的对象来减少这一开销。任何其余的状态都由 Context维护。Context在每一次对Strategy对象的请求中都将这个状态传递过去。共享的 Strategy不应在各次调用之间维护状态。

8.实现

抽象类

//这里其实可以用协议替代
class CashSuper: NSObject {
    //优点1: 继承有助于析取出这些算法中的公共功能。
    //缺点2: Strategy和Context之间的通信开销,对于子类实现的通信接口(acceptCash),参数(money)可以会永远不会使用
    func acceptCash(money: Double) -> Double {
        return 0.0
    }
}

具体实现对象--打折类(其它策略类 类似)

//打折类
class CashRebate: CashSuper {
    
    private var moneyRebate: Double = 1.0
    
    init(moneyRebate: Double) {
        self.moneyRebate = moneyRebate
        super.init()
    }

    //打折返回
    override func acceptCash(money: Double) -> Double {
        return money * moneyRebate
    }

}

客户端对象

//客户端对象(具体使用的对象)
class CashContext: NSObject {

    private var cs: CashSuper!
    
    //优点4: 客户可以根据不同时间 /空间权衡取舍要求从不同策略中进行选择。
    //优点3: 将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展。
    //缺点3: 策略模式将造成产生很多策略类
    func cashContext(type: CacultorType) {
        switch type {
        case .Normal:
            cs = CashNormal()
        case .Rebate:
            cs = CashRebate(moneyRebate: 0.8)
        case .Return:
            cs = CashReturn(moneyCondition: 300, moneyReturn: 100)
        }
    }
    
    //优点2: 策略以相同的方式调用所有的算法,减少客户端与算法类之间的耦合
    //传入初始金额,利用私有cs,计算最终金额
    func getResult(money: Double) -> Double {
        return cs.acceptCash(money: money)
    }
}

demo地址,欢迎star

9.HeadFirst -- 策略模式

class Duck {

    //fly 和 quack 不是每个子类都可能具备的所以它们是变化的部分,应该抽取出来
    //设计原则:使用组合(FlyBehavior, QuackBehivor)而不是使用继承
    var flyBehavior: FlyBehavior?        //针对接口(FlyBehavior)编程而不是针对实现编程
    var quackBehavior: QuackBehivor?     //针对接口(QuackBehivor)编程而不是针对实现编程

    //不需要变化的行为
    func swim() {
        print("I can swim")
    }

    //根据子类的而变化的行为
    func display() {

    }

    func performQuack() {
        flyBehavior?.fly()
    }

    func performFly() {
        quackBehavior?.quack()
    }
}

//具体子类,绿头鸭
class Mallard: Duck {
    override func display() {
        print("一只绿头鸭")
    }
}

变化的行为Fly

//设计原则:封装变化-找出应用中需要变化的地方,将他们独立出来,不要和不需要变化的代码混合到一块
//设计原则:针对接口编程而不是针对实现编程

protocol FlyBehavior {
    func fly()
}

extension FlyBehavior {
    func fly() {
        print("默认实现")
    }
}

//下面是针对FlyBehavior协议定义的算法簇
class FlyWithWings: FlyBehavior {
    func fly() {
        print("I can fly!")
    }
}

class FlyNoWay: FlyBehavior {
//    func fly() {
//        print("I can't fly!")
//    }
}

变化的行为Quack

//设计原则:封装变化-找出应用中需要变化的地方,将他们独立出来,不要和不需要变化的代码混合到一块
//设计原则:针对接口编程而不是针对实现编程
protocol QuackBehivor {
    func quack()
}

//下面是针对QuackBehivor协议定义的算法簇
class Quack: QuackBehivor {
    func quack() {
        print("Quack")
    }
}

class Slient: QuackBehivor {
    func quack() {
        print("slient")
    }
}

class Squeak: QuackBehivor {
    func quack() {
        print("Squeak")
    }
}

总结

HeadFirst策略模式使用的设计原则

  • 封装变化-找出应用中需要变化的地方,将他们独立出来,不要和不需要变化的代码混合到一块
  • 针对接口编程而不是针对实现编程-针对接口编程实际上就是针对超类型编程,其关键在于多态,利用多态就可以实现超类型编程,也就是根据实现情况来执行具体的行为,而不是绑定在超类型的行为上。通俗一点来说,就是声明一个变量,这个变量的类型就是超类型(超类型通过是一个抽象类或一个接口,在swift中接口就是协议,不过和java的接口相比,swift协议可以有默认实现),只要具体实现此超类型的对象都可以直接赋值给上面的变量。也就是说不用关心执行具体的对象类型。
  • 多使用组合而不是继承

HeadFirst策略模式

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

demo地址,欢迎star

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容

  • 1 场景问题# 1.1 报价管理## 向客户报价,对于销售部门的人来讲,这是一个非常重大、非常复杂的问题,对不同的...
    七寸知架构阅读 5,071评论 9 62
  • 1 场景问题 1.1 报价管理 向客户报价,对于销售部门的人来讲,这是一个非常重大、非常复杂的问题,对不同的客户要...
    4e70992f13e7阅读 3,080评论 2 16
  • 本文仅仅为入门,高手勿喷。 实际工作中,我们总会遇到类似如下的需求:某支付系统接入以下几种商户进行充值:易宝网易,...
    JarvanMo阅读 14,232评论 14 26
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,639评论 18 139
  • 1.问题例1:一个菜单功能能够根据用户的“皮肤”首选项来决定是否采用水平的还是垂直的排列形式。同事可以灵活增加菜单...
    小飞猪阅读 360评论 0 0