大家好,我是Jasson。
很久没有动手写设计模式的笔记了。今天继续了,做事要有始有终。
最近刚换了砖厂,深感学习和自我提升甚是重要。
今天要写的是外观模式,(英文:facade 读[fəˈsɑːd] 外观,音似弗萨德)。
我们开始吧。
一些思考
每每说到设计模式,我都在想一个问题:它到底是为了解决什么设计问题而存在的。
从这个起点出发,在解答这个问题的过程中,我就能把它的使用场景和注意细节搞得通透,
用起来就得心应手了
一个例子
比方说,我去体检机构做体检。
登记台只要一张身份证就能完成录入资料,缴费的工作,所有的检查项目科室还都在同一层楼。
且体检表上有每一个项目的门牌号,分配了足够的人力对人群进行分流,不会造成客户排队过久的情况。
是不是能联想到我们开发APP的情形呢?用户其实不在乎你的后台运作涉及多少个环节,他要的只是一个结果。
就像我去体检这件事儿,只要亮出身份证给导诊台,交钱,按引导体检,拿结果。具体哪一个部门,谁,在哪给你做检查这些问题我们顾客不需要自己去指定。
面门模式的效果等同于上面所说的体检的导诊台,就是设计一个和用户直接对话的窗口,所有与用户的文互都在这里完成,而用户不需要和系统的其它部分直接对话。
这个模式下是由同一个门面提供给用户所有的交互内容,其它的细节实现不用曝露给用户。
一些角色
那么, 我们就可以归纳出facade模式下的基本结构 ,如下:
main或者client -> 直接和客户对话的入口;
facade类-> 整个系统和用户交互的对象类;
handlers->受facade类调配的处理类;不用曝露给用户;可以是多个;
以上是基础结构,文末再说说一种优化方案。
以下代码是使用kotlin写的,但并不障碍大家对主题内容的学习。
因为我觉得kotlin源于java,而优于java。
是值得学习的一门语言,所以, 我保留了。
我以买保险举例。
以下文字过多,总结起来就是:用户买保险,其实只是选择产品,输入信息,提交订单,购买的过程而已。用户只需要一个对接的页面,而后台有很多的子系统在为之服务。
package imeegaa.pattern.facade
/**
在手机上买保险,忽略一些非必要操作,最后的操作可以简化成以下的三个操作:选择产品,填写信息,付款。
而其实app后台要做的事情却不止这些:结合与用户的交互就是以下的步骤:判断该地区和用户是否可售;判断该产品是否有优惠;
判断该用户是否有优惠等等;用户填写信息;生成订单;付款;检查付款状态;出单。
facade模式就是一个入口,用户只需要做对他来说,需要做的事情就行了。不需要去关注后台的复杂处理;
所以我们的代码结构就是下面这样的:
main:入口类
facade: 接待类,处理用户的请求;在该类中控制用户的操作流程;屏蔽用户无须关注的流程
handler1、handler2、handler3...: 多个处理类;针对不同的事件生成不同的处理方案;
注意:结合了结城浩的《图解设计模式》和其它的网上资料。facade模式因为系统要添加一个新的功能,就要对facade类进行修改,违背了“开闭原则”。
网上有教程建议是建立一个abstractFacade类。所有的具体外观类都继承它,可以解决一些问题。
*/
fun main() {
PolicyOrderFacade().buyPolicy()
}
接下来,是对接用户的facade类。
package imeegaa.pattern.facade
import java.time.LocalDateTime
import java.util.*
import kotlin.random.Random
class PolicyOrderFacade {
fun buyPolicy() {
val scanner = Scanner(System.`in`)
println("please tell me which product you want to buy,input the number? 1 健康险;2 财产险;3 寿险")
val productCode = scanner.nextLine()
println("please input your name")
val realName = scanner.nextLine()
println("please input your gender 'm' or 'f'")
val gender = scanner.nextLine()
println("please input your age")
val age = scanner.nextInt()
val policy = UserInfoHandler().fillPaper(realName, age, gender)
policy.productCode = productCode
DiscountHandler().getDiscount(policy)
OrderHandler().createdOrder(policy)
println("do you want to pay the order now ? 'y' or 'n' (ignore case)")
val payFlag = scanner.nextLine()
PaymentHandler().pay(payFlag, policy)
}
}
上面的代码是通过和控制台输入文本模拟交互的,没有考虑校验等细节。
其实,大家能看出来,有多个类在这里分别处理不同的工作。而我所举的保险这个行业,真的就是一个流程会有很多复杂的子系统在工作。外观模式必不可少。
以下是保险类。
package imeegaa.pattern.facade
class Policy {
var realName: String = ""
var gender: String = "M"
var age: Int = 0
var productCode: String = ""
var orderId: Int = 0
var price: Double = 100.00
var discount:Double = 1.0
var status :String = "0" // 0 init ; 1 paid
}
记录用户信息。
package imeegaa.pattern.facade
class UserInfoHandler {
// 填写用户名称
fun fillPaper(name: String, age1: Int, gender1: String):Policy {
return Policy().apply {
realName = name
age = age1
gender = gender1
}
}
}
判断是否有优惠。
package imeegaa.pattern.facade
import kotlin.random.Random
class DiscountHandler {
//查看该用户是否有优惠项
fun getDiscount(policy: Policy) {
policy?.apply {
discount = Random(123).nextDouble()
println("the discount is $discount")
}
}
}
最后,生成订单。
package imeegaa.pattern.facade
import kotlin.random.Random
class OrderHandler {
//生成订单
fun createdOrder(policy: Policy) {
policy?.apply {
orderId = Random(12).nextInt(1000,9999)
println("the applyOrder $orderId is created.")
}
}
}
由用户决定是否付款。
package imeegaa.pattern.facade
import java.time.LocalDateTime
class PaymentHandler {
//付款
fun pay(status:String,policy: Policy) {
policy.status = status
println("the policy $policy is valid from ${LocalDateTime.now()}")
}
}
需要说明下,看似每个handler的代码量极少,完全可以在main里写完。但是上百人参与的大系统里,一个类的功能是极复杂的,牵一发而动全身。
都说专业的人做专业的事,那代码也是。
我们要以小见大。
大家看到的很多handler里都有apply这个方法。这是kotlin特有的内联扩展函数,我们可以理解为apply是对一个对象实例进行批量的get/set操作。
我的建议是自己去设定应用场景,再去写自己的demo。
大家自己写过之后,动动小手,有什么自己的想法发到评论中,我们一起学习。
开头说的,这个facade模式是有缺陷的,因为只要系统中加入一个子系统,那么facade类就要进行修改,而我们设计模式的提倡设计要符开“开闭原则”。反对修改,提倡扩展。
方法就是建立abstractFacade类,facade作为它的子类,需要添加子系统(handler)时,只是修改具体的facade类,而面向用户的抽象类依旧不变。
说到这,其实我想说,设计模式本身并不是死的比。
如这种先建立抽象类,再使用具体子类进行功能操作的套路其实模板方法里也有类似的结构。万物相通,积累多了,也就能明白了。
这就是我今天分享的facade(外观)设计模式。谢谢大家的阅读。
文中有错误之处的话,请大家在评论区留言。