2.商场促销 - 策略模式 (大话设计模式Kotlin版)

标题

编程题:制作商场收银软件

利用编程做一个简易的商场收银系统,营业员根据客户购买的商品的单价与数量,向客户收费。

快速实现

马上想到的解决思路:无非是提供两个输入,分别对应商品单价与数量,然后通过计算得到费用输出!

v1.0代码
/**
 * @create on 2020/5/19 22:02
 * @description 收银客户端
 * @author mrdonkey
 */
class Client {

    companion object {
        @JvmStatic
        fun main(vararg arg: String) {
            val scanner = Scanner(System.`in`)
            println("请输入商品单价:")
            val price = scanner.nextLine().toDouble()
            println("请输入商品数量:")
            val num = scanner.nextLine().toInt()
            println("商品总价为:${price.times(num)}")
        }
    }
}

测试结果:

请输入商品单价:
38.5
请输入商品数量:
10
商品总价为:385.0

拓展一

上面的代码看起来可用,可是如果商场对商品搞活动,所有的商品打八折该怎么办呢?
简单,那不是在总价再乘以0.8不就好了吗。
那难到商场活动结束了,你还要再改一遍吗,然后再用改好的程序去把所有机器都安装一遍吗?
嗯?那增加一个优惠方式不就行了,默认是原价,可以选择原价/打折

增加打折的代码
/**
 * @create on 2020/5/19 22:24
 * @description 收银客户端:增加打折功能
 * @author mrdonkey
 */
class Client {
    companion object {
        @JvmStatic
        fun main(vararg arg: String) {
            val scanner = Scanner(System.`in`)
            println("请输入商品单价:")
            val price = scanner.nextLine().toDouble()
            println("请输入商品数量:")
            val num = scanner.nextLine().toInt()
            println("请选择计算方式:")
            println("1.原价")
            println("2.打8折")
            println("3.打7折")
            val rebate = when (scanner.nextLine().toInt()) {
                1 -> 1.0
                2 -> 0.8
                else -> 0.7
            }
            println("商品总价为:${price.times(num).times(rebate)}")
        }
    }
}

测试结果:

请输入商品单价:
38.5
请输入商品数量:
10
请选择计算方式:
1.原价
2.打8折
3.打7折
2
商品总价为:308.0

拓展二

除了打折,商场又需要添加新的需求:满300返100的促销
简单!写一个基类,再继承它实现多个打折和返利类,利用多态结合简单工厂模式,完成代码
那你打算写几个类?
打八折、七折、五折、满300送100、满200送50…要几个写几个
真的有必要这样做吗?如果我要打三折,要满300送80,难道再去加子类?不想想看,哪些是相同的,哪些是不同的
呃…有道理!打折都是一样的,只是参数不同,满几送几,则需要两个参数来确定

面向对象编程,并不是越多类越好,类的划分是为了封装,但分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类
打一折和打九折只是形式不同,抽象分析出来,所有打折都是一样的,所以打折算法应该是一个类。

利用简单工厂模式实现

简单工厂模式的UML图:
简单工厂模式UML
CashSuper 现金收费抽象类

/**
 * @create on 2020/5/19 23:11
 * @description 现金收费抽象类
 * @author mrdonkey
 */
abstract class CashSuper {
    /**
     * 收取现金抽象方法,返回为当前价
     */
    abstract fun acceptCash(money: Double): Double
}

CashNormal 正常收费子类

/**
 * @create on 2020/5/19 23:14
 * @description 正常收费子类
 * @author mrdonkey
 */
class CashNormal : CashSuper() {
    /**
     * 正常收费返回原价
     */
    override fun acceptCash(money: Double): Double {
        return money
    }
}

CashReturn 返利收费子类

/**
 * @create on 2020/5/19 23:19
 * @description 返利收费子类
 * @author mrdonkey
 */
class CashReturn(var moneyCondition: Double = 0.0, var moneyReturn: Double = 0.0) : CashSuper() {
    /**
     * 例如 满300返100
     * [moneyCondition] 返利条件  300
     * [moneyReturn] 返利 100
     */
    override fun acceptCash(money: Double): Double {
        return if (money >= moneyCondition)
            money.minus(money.div(moneyCondition).toInt().times(moneyReturn))// 500-(500/300)*100
        else money
    }
}

CashRebate 打折收费子类

/**
 * @create on 2020/5/19 23:16
 * @description 打折收费子类
 * @author mrdonkey
 */
class CashRebate(var moneyRebate: Double = 1.0) : CashSuper() {

    /**
     * 打折收费
     */
    override fun acceptCash(money: Double): Double {
        //moneyRebate为构造参数参入折扣率,如果是8折则传入0.8,默认则1.0
        return money.times(moneyRebate)
    }

}

CashAcceptType 收费类型的枚举类

/**
 * @create on 2020/5/19 23:38
 * @description 收费类型
 * @author mrdonkey
 */
enum class CashAcceptType(vararg var arg: Double) {
    NORMAL,//正常收费
    RETURN300_100(300.0, 100.0),//满300返利100
    REBATE(0.8);//打8折

    companion object{
        @JvmStatic
        fun getTypeByOrdinal(ordinal: Int): CashAcceptType = values()[ordinal]
    }
}

CashFactory 现金收费工厂

/**
 * @create on 2020/5/19 23:35
 * @description 现金收费工厂
 * @author mrdonkey
 */
class CashFactory {
    companion object {
        @JvmStatic
        fun createCashAccept(type: CashAcceptType): CashSuper {
            return when (type) {
                CashAcceptType.NORMAL -> CashNormal()
                CashAcceptType.RETURN300_100 -> CashReturn(type.arg[0], type.arg[1])
                CashAcceptType.REBATE -> CashRebate(type.arg[0])
            }
        }
    }
}

Client 客户端代码

/**
 * @create on 2020/5/19 23:46
 * @description 客户端代码
 * @author mrdonkey
 */
class Client {
    companion object {
        @JvmStatic
        fun main(vararg args: String) {
            val scanner = Scanner(System.`in`)
            println("请输入商品单价:")
            val price = scanner.nextLine().toDouble()
            println("请输入商品数量:")
            val num = scanner.nextLine().toInt()
            println("请选择计算方式:")
            println("0.原价")
            println("1.满300返100")
            println("2.打8折")
            val ordinal = scanner.nextLine().toInt()
            val cashSuper = CashFactory.createCashAccept(CashAcceptType.getTypeByOrdinal(ordinal))
            val total = cashSuper.acceptCash(price.times(num))
            println("总价:$total")
        }
    }
}

测试结果:

请输入商品单价:
38.5
请输入商品数量:
10
请选择计算方式:
0.原价
1.满300返100
2.打8折
1
总价:285.0

利用简单工厂,我只要选择对应的收费方式,工厂类就会生成一个收费基类(指向具体的收费实现类),调用基类引用的抽象方法,
即可得到最终的价格!

简单工厂模式虽然也能解决这个问题,但这是解决的对象创建问题,由于工厂本身包括所有收费方式,商场是经常性的更改打折额度和返利额度,每次维护或拓展收费都需要改动这个工厂,以至于代码需要重新编译部署,这是很糟糕的处理方式,所以用它不是最好的解决办法。
面对算法的时常改动,而不影响使用算法的客户端?有什么好方法呢?

策略模式试探

策略模式: 它定义了算法家族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户。

策略模式基本代码

策略模式UML图:
策略模式UML图

Strategy类,定义所有支持的算法的公共接口

/**
 * @create on 2020/5/20 08:05
 * @description 抽象算法类
 * @author mrdonkey
 */
abstract class Strategy {

    /**
     * 算法方法
     */
    abstract fun algorithmInterface()
}

ConcreteStrategy 具体算法类

/**
 * @create on 2020/5/20 08:19
 * @description 具体算法A
 * @author mrdonkey
 */
class ConcreteStrategyA : Strategy() {
    /**
     * 算法A的实现方法
     */
    override fun algorithmInterface() {
        println("算法A的具体实现")
    }
}
/**
 * @create on 2020/5/20 08:19
 * @description 具体算法B
 * @author mrdonkey
 */
class ConcreteStrategyB : Strategy() {
    /**
     * 算法B的实现方法
     */
    override fun algorithmInterface() {
        println("算法A的具体实现")
    }
}
/**
 * @create on 2020/5/20 08:21
 * @description 具体算法C
 * @author mrdonkey
 */
class ConcreteStrategyC : Strategy() {
    /**
     * 算法C的实现方法
     */
    override fun algorithmInterface() {
        println("算法C的具体实现")
    }
}

Context类:用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用

/**
 * @create on 2020/5/20 08:23
 * @description Context上下文,用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用
 * @author mrdonkey
 */
class Context constructor(private val strategy: Strategy) {
    /**
     * [strategy] 算法的一部分是作为参数传递的
     * 根据具体的侧脸对象,调用其算法的方法
     */
    fun contextInterface() {
        strategy.algorithmInterface()
    }
}

Client 客户端类

/**
 * @create on 2020/5/20 08:26
 * @description 客户端代码
 * @author mrdonkey
 */
class Client {
    companion object {
        @JvmStatic
        fun main(vararg args: String) {
            //由于实例化不同的策略,所以最终结果在调用context.contextInterface时所获得的结果不尽相同
            var cxt = Context(ConcreteStrategyA())
            cxt.contextInterface()
            cxt = Context(ConcreteStrategyB())
            cxt.contextInterface()
            cxt = Context(ConcreteStrategyC())
            cxt.contextInterface()
        }
    }
}

测试结果:

算法A的具体实现
算法A的具体实现
算法C的具体实现
商场收银软件用策略模式实现

看上面的策略模式的基本代码示例,模仿写策略模式的代码,只需要增加一个CashContext类,改一下客户端即可

CashContext:收费上下文(策略与简单工厂的结合)

/**
 * @create on 2020/5/20 08:32
 * @description 收费上下文
 * @author mrdonkey
 */
class CashContext constructor(type: CashAcceptType) {
    private var cs: CashSuper = when (type) {
        CashAcceptType.NORMAL -> CashNormal()
        CashAcceptType.RETURN300_100 -> CashReturn(type.arg[0], type.arg[1])
        CashAcceptType.REBATE -> CashRebate(type.arg[0])
    }
    /**
     * 根据策略不同,获得计算结果
     */
    fun getResult(money: Double): Double {
        return cs.acceptCash(money)
    }
}

Client 客户端类(策略与简单工厂的结合)

/**
 * @create on 2020/5/20 08:34
 * @description 客户端
 * @author mrdonkey
 */
class Client {
    companion object {
        @JvmStatic
        fun main(vararg args: String) {
            val scanner = Scanner(System.`in`)
            println("请输入商品单价:")
            val price = scanner.nextLine().toDouble()
            println("请输入商品数量:")
            val num = scanner.nextLine().toInt()
            println("请选择计算方式:")
            println("0.原价")
            println("1.满300返100")
            println("2.打8折")
            val ordinal = scanner.nextLine().toInt()
            val cc = CashContext((CashAcceptType.getTypeByOrdinal(ordinal)))
            val total = cc.getResult(price.times(num))
            println("总价:$total")
        }
    }
}

简单工厂模式对比策略模式

简单工厂模式:

val cashSuper = CashFactory.createCashAccept(CashAcceptType.getTypeByOrdinal(ordinal))
val total = cashSuper.acceptCash(price.times(num))

策略模式与简单工厂结合的用法:

val cc = CashContext((CashAcceptType.getTypeByOrdinal(ordinal)))
val total = cc.getResult(price.times(num))

简单工厂模式:需要客户端认识两个类,CashSuper与CashFactory
策略模式结合简单工厂的用法:客户端只需要认识一个类CashContext即可。耦合性更低

策略模式解析

优点:
  1. 策略模式: 是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。

  2. 策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能(就是获得计算费用结果的getResult()方法),这使得算法间有了抽象的父类CashSuper。

  3. 策略模式简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。

总结:

在编程之初,用when条件分支,判断具体使用哪个算法,这是正常的。因为,当所有不同行为堆砌在同一个类中时,就很难避免使用条件语句来选择合适的行为。将这些行为封装在一个个独立的Strategy类中,可以在使用这些行为的类中消除条件语句。在上述商场收银软件设计中,巧妙利用简单工厂模式将客户端的条件语句放到Context中,减轻了客户端的职责。

总的来说:
“策略模式封装了变化”,策略模式就是用来封装算法的,但在实际中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式来处理这种变化的可能性。

但我感觉,在基本的策略模式中,选择所有具体实现的职责由客户端对象承担,并转给策略模式的Context对象。 这本身没有解除客户端需要选择判断的压力,而策略模式与简单工厂模式结合后,选择具体的职责也可以由Context来承担了。

不足之处:
CashContext中还是到了条件语句,如果需要加一种算法,那么必须新增CashContext中的条件判断,让人真不爽!
能怎么办,任何需求的变更都是需要成本的!还有更好的方法吗?
利用反射技术!,在抽象工厂模式章节有对反射的讲解

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值