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

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

快速实现

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

v1.0代码

/*** @create on 2020/5/19 22:02* @description 收银客户端* @author mrdonkey*/
class Client {companion object {@JvmStaticfun 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 {@JvmStaticfun 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.02 -> 0.8else -> 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图:

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)*100else 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.0return 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返利100REBATE(0.8);//打8折companion object{@JvmStaticfun getTypeByOrdinal(ordinal: Int): CashAcceptType = values()[ordinal]}
}

CashFactory 现金收费工厂

/*** @create on 2020/5/19 23:35* @description 现金收费工厂* @author mrdonkey*/
class CashFactory {companion object {@JvmStaticfun 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 {@JvmStaticfun 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图:

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 {@JvmStaticfun 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 {@JvmStaticfun 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中的条件判断,让人真不爽!
能怎么办,任何需求的变更都是需要成本的!还有更好的方法吗?
利用反射技术!,在抽象工厂模式章节有对反射的讲解

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

  1. 商场促销——策略模式(设计模式)

    商场收银时,如何促销,用打折还是返利,其实都是一些算法,用工厂来生成算法对象,这没有错,但算法本身只是一种策略,最重要的是这些算法是随时都可能互相替换的,就这点变化,而封装变化点是我们面向对象的一种很 ...

  2. 大话设计模式——第二章:商场促销策略模式

    文章目录 前言 一.代码1.0 1.1. 代码 2.2. 存在的问题 二.代码1.1 增加打折 2.1. 代码 2.2. 存在的问题 三.代码2.0 使用简单工厂写打折类和返利类 3.1. 代码 3. ...

  3. 商场促销 --- 策略模式

    一,概念 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换.策略模式让算法独立于使用它的客户而独立变化. 二,策略模式的组成 1)抽象策略角色: 策略类,通常由一个接口或者 ...

  4. 策略模式-大话设计模式

    策略模式(Strategy):它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户. 一.需求 实现一个商场收银软件,满足满减.打折.正常收费. 二.代 ...

  5. 商场促销——策略模式

    策略模式(Strategy):它定义了算法家族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户. "面向对象的编程,并不是类越多越好,类的划分是为了封装, ...

  6. 17.在NBA我需要翻译-适配器模式(大话设计模式kotlin版)

    适配器模式 基本概念 适配器:就是使得一个东西适合另外一个东西的东西. 定义:适配器模式(Adapter),将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一 ...

  7. 大话设计模式笔记(二)——商品促销 策略模式

    第二章商品促销--策略模式 需求:商品价格计算,并增加折扣条件 工厂模式UML图 在工厂模式中,调用端代码会通过折扣工厂类生成折扣对象,折扣对象调用折扣方法.这里关联了两个类,工厂类和抽象折扣类. 策 ...

  8. 大话设计模式C++版——代理模式

        本篇開始前先发个福利,程杰的<大话设计模式>一书高清电子版(带文件夹)已上传至CSDN,免积分下载. 下载地址:http://download.csdn.net/detail/gu ...

  9. [大话设计模式C++版] 第12章 牛市股票还会亏钱 —— 外观模式

    源码可以在这里找到 大话设计模式C++版 股民炒股代码 //main.cpp #include <iostream>using namespace std;//股票1 class Stoc ...

最新文章

  1. TemplatedParent 与 TemplateBinding
  2. Httprunner测试
  3. 每日一皮:都 0202 年了,我怎么可能被骗呢?
  4. java数组与字符串互转
  5. 五十四、最基础的冒泡排序
  6. 前端学习(2165):vuecli3创建项目和目录结构
  7. Java笔记第七篇 数据类型初了解(下,后含有循环输出a-z)
  8. (03)System Verilog 常用数据类型详解
  9. 快速搭建本地服务器 php,本地PHP服务器环境快速搭建
  10. 全能App研发助手!滴滴开源DoraemonKit
  11. PCB工程师为你详解FPC排线及其用途
  12. TP-Link 886nV6 刷第三方系统回忆
  13. SD卡无法格式化的原因分析
  14. 小程序“成语猜题”部分答案
  15. 定时播放音频、定时播放视频解决方案 —— 定时执行专家
  16. Matlab根据excel数据画图
  17. 【LaTeX 教程】01. LaTeX 简介与安装
  18. python如何分割年月日_将日期拆分为年、月和日,分隔符不一致
  19. xshell用rz上传文件,彻底解决乱码
  20. 赛扬处理器_Intel低功耗奔腾/赛扬“变砖”:紧急退市、升级

热门文章

  1. px,in,mm,pt,dp,dip,sp 之间的换算公式以及区别
  2. show master status 时没有数据显示
  3. CNN | 06Cifar-10分类
  4. 浅谈中国历史上的四大叛将!
  5. 关于golang的http库及常用库
  6. 机器学习项目中遇到的难题_现代难题:何时使用规则与机器学习
  7. 中国医疗保健分析测试服务行业市场供需与战略研究报告
  8. 微信小程序——背景音乐播放
  9. Tiktok的websocket私信协议详解。
  10. mysql安装简书_MySQL 安装和配置