天天看點

[程式員的自我修養-做一個牛逼的程式員,就得從設計模式入手]-外觀模式

大家好,我是傑森小哥哥。

很久沒有動手寫設計模式的筆記了。今天繼續了,做事要有始有終。

最近剛換了磚廠,深感學習和自我提升甚是重要。

今天要寫的是外觀模式(facade ,這個法語詞的讀音很有味道,建議百度下,别讀錯哦)。

每每說到設計模式,我都要想一個問題。這個鬼東西到底是為了什麼問題而存在的。有動機才會有動力。

說個例子。我今年去體檢過兩次。一次是體檢機構,一次是一家私立醫院。

先說結論,這個體檢機構的效率和服務比私立醫院好。(具體醫院,具體分析,不代表行業現狀)

有一個點我想說下:體檢機構的登記台隻要一張身份證就能完成錄入資料,繳費的工作,所有的項目科室還都在同一層樓。

且體檢表上有每一個項目的門牌号,配置設定了足夠的人力對人群進行分流,不會造成客戶排隊過久的情況。

而私立醫院這些做的不夠好。

不得不說,術業有專攻啊。

客戶體驗真真重要。

回到主題,是不是能聯想到我們開發APP的情形呢?使用者其實不在乎你的背景運作涉及多少個環節。就像我去體檢,你隻要告訴我交身份證,交錢,體檢,拿結果就可以了。其它的我不需要知道。

而facade模式就等同于上面所說的體檢的前台,它就是這個和使用者直接對話的視窗(facade是原意就是建築物的正面)。

這個模式下隻會會告訴使用者必須的步聚是哪些,而其它的細節是不曝露給使用者的。

那麼, 我們就可以歸納出facade模式下的基本結構 ,如下:

main或者client -> 直接和客戶對話的入口;

facade類-> 整個系統和使用者互動的對象類;

handlers->受facade類調配的處理類;不用曝露給使用者;可以是多個;

以上是基礎結構,文末再說說一種優化方案。

下面的codeTime.我的兩點建議。

一,如果你是用電腦看的,請打開IDE,寫一個facade模式的demo.

二,自己設定一種場景,不用照搬我下面的場景。

以下代碼是使用kotlin寫的,但并不障礙大家對主題内容的學習。

因為我覺得kotlin源于java,而優于java。

是值得學習的一門語言,是以, 我保留了。

我以買保險舉例。

以下文字過多,總結起來就是:使用者買保險,其實隻是選擇産品,輸入資訊,送出訂單,購買的過程而已。使用者隻需要一個對接的頁面,而背景有很多的子系統在為之服務。

package imeegaa.pattern.facade

/**今天看了facade(外觀)設計模式,首先要說的是這個單詞的發音不是大家所看到的那樣/feikeid/的讀法,
而是讀作 /fesade/(大緻是這樣,正确音标查百度),這是源自一個法文詞,說的是建築物的正面
用一個手機APP買保險的例子來說明下,大家就能明白。

在手機上買保險,忽略一些非必要操作,最後的操作可以簡化成以下的三個操作:選擇産品,填寫資訊,付款。
而其實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操作。

代碼是在我自己的Matebook上跑過的,大家隻需要把包名改下應該就可以使用了。

但是,我的建議是自己去設定應用場景,再去寫自己的demo。

建議大家自己寫過之後,把連結發到評論裡,我們一起學習。

開頭說的,這個facade模式是有缺陷的,因為隻要系統中加入一個子系統,那麼facade類就要進行修改,而我們設計模式的提倡設計要符開“開閉原則”。反對修改,提倡擴充。

方法就是建立abstractFacade類,facade作為它的子類,需要添加子系統(handler)時,隻是修改具體的facade類,而面向使用者的抽象類依舊不變。

說到這,其實我想說,設計模式本身并不是死的比。

如這種先建立抽象類,再使用具體子類進行功能操作的套路其實模闆方法裡也有類似的結構。萬物相通,積累多了,也就能明白了。

這就是我今天分享的facade(外觀)設計模式。謝謝大家的閱讀。

文中有錯誤之處的話,請大家在評論區留言。

我是傑森,願把知識用快樂的方式和他人分享的美男子。

繼續閱讀