規約模式,顫抖吧產品經理!再也不怕你亂改需求了

TechFlow2019發表於2021-01-27

大家好,今天來和大家聊聊規約模式

規約模式的英文是Specification Pattern,這裡的規約其實是一個表意的翻譯,Specification直譯過來是要求、技術說明、明確的意思。光看名字估計大家都是一臉懵逼,根本不知道這個設計模式大概會是一個什麼樣子。這也是設計模式的一個通病,就是內涵比較晦澀,很難通過名稱來概括。

我們先來簡單說說這裡規約的意思,它的含義其實很簡單,就是把程式碼當中的程式碼邏輯以及業務規則區分開。這樣的話我們可以在不影響全域性的情況下自由地修改和組合這兩個部分。

我知道大家看完這個解釋估計還是似懂非懂,沒有關係,我們來簡單看一個例子即可。

舉個例子

我們都知道QQ裡有各種鑽石會員,什麼綠鑽、粉鑽之類的。每種會員呢對應一些特殊的權益,假設現在我們新開發了某一個功能,要提供給同時擁有綠鑽和粉鑽的使用者使用。

我們來實現這個邏輯非常簡單,只需要一個判斷條件就可以了。

def is_satisfied(user):
    return user.isGreenAuth() and user.isPinkAuth()

但是這裡有一個問題,這裡的邏輯是寫死的,時間久了之後,如果這個程式碼轉交給其他人維護了,那麼接手的人估計會一臉懵。根本不知道這裡為什麼要這麼判斷,也不知道這個函式代表的功能是什麼,可能需要去翻很多程式碼或者是找很多文件才能解決疑惑。

這裡的根本問題就是業務規則和實現邏輯混合在一起了,什麼意思呢?這裡的綠鑽 + 粉鑽的判斷就是業務規則,是為了實現某項功能而制定的,產品經理要的是這個兩者疊加的規則。而我們通過user.isGreenAuth() and user.isPinkAuth()來實現,相當於新建了一個規則,從功能上這當然沒有問題。但是問題是這裡的規則和程式碼是定死的

假設說某一天產品經理提了一個新的需求,不僅需要綠鑽 + 粉鑽還需要年齡大於18歲,並且年收入超過10w才可以享受這個功能。那麼帶來的結果就是我們需要在這個is_satisfied函式當中加上許多程式碼,隨著時間的推移這個函式當中的程式碼會變得越來越臃腫,並且很有可能以後這當中的一些判斷邊界需要修改,比如18歲改成20歲,比如10w改成30w等等,這些都是有可能的。當要修改的時候你會發現由於程式碼的耦合和混亂,改起來非常麻煩。

為了解決這個問題就需要引入規約。

規約的含義

規約的意思是把邏輯和規則區分開,規則的歸規則,邏輯的歸邏輯

我們還用上面的例子來看,比如在新的需求當中,邏輯本身是很簡單的。即粉鑽 + 綠鑽 + 年齡達標 + 收入達標,這個是規則,而年齡達標和收入達標則是具體的實現。

當我們把規則定好了之後,我們再去解構其中的元件,比如收入達標的定義是年收入大於10w,比如年齡達標的含義是大於18歲。我們可以把這些元件做成單獨的模組,這樣以後需要修改這些邊界的時候,我們只需要在具體實現當中修改就可以了,對於規則的部分就不用動了。同樣如果我們需要修改規則,我們也可以避免對實現的改動,這就是規約的意義。

首先,我們需要一個框架用來實現邏輯的組合。

在邏輯當中變數之間的關係只有三種,就是與或非。所以整個邏輯關係還是很清楚的。這裡我們採取抽象的方法,先定義介面,再去做具體的實現。這是我們規約出來的條件,它當中有四個介面,分別是與或非的操作介面,以及一個is_satisfied的判斷介面。

from abc import abstractmethod

class Specification:

    def and_specification(self, candidate):
        raise NotImplementedError()

    def or_specification(self, candidate):
        raise NotImplementedError()

    def not_specification(self, candidate):
        raise NotImplementedError()

    @abstractmethod
    def is_satisfied(self, candidate):
        pass

在Specification的基礎上,我們進一步實現與或非執行的邏輯。這裡的邏輯很好理解,就不多解釋了。

class AndSpecification(Specification):
    _one = Specification()
    _other = Specification()

    def __init__(self, one, other):
        self._one = one
        self._other = other

    def is_satisfied(self, candidate):
        return bool(self._one.is_satisfied(candidate) and self._other.is_satisfied(candidate))


class OrSpecification(Specification):
    _one = Specification()
    _other = Specification()

    def __init__(self, one, other):
        self._one = one
        self._other = other

    def is_satisfied(self, candidate):
        return bool(self._one.is_satisfied(candidate) or self._other.is_satisfied(candidate))


class NotSpecification(Specification):
    _wrapped = Specification()

    def __init__(self, wrapped):
        self._wrapped = wrapped

    def is_satisfied(self, candidate):
        return bool(not self._wrapped.is_satisfied(candidate))
    
    
# 組合規則元件,也就是兩個規約條件的邏輯組合
class CompositeSpecification(Specification):
    @abstractmethod
    def is_satisfied(self, candidate):
        pass

    def and_specification(self, candidate):
        return AndSpecification(self, candidate)

    def or_specification(self, candidate):
        return OrSpecification(self, candidate)

    def not_specification(self):
        return NotSpecification(self)

上面所有的類都是為了定義規則的,這些有了之後,我們只需要在它的基礎上填充具體的業務判斷邏輯就可以了。相當於我們把一個複雜的業務邏輯拆分了,拆分成了若干個子邏輯的組合。

class User:
    def __init__(self, age=18, incoming=0, green_auth=False, pink_auth=False):
        self.age = age
        self.incoming = incoming
        self.green_auth = green_auth
        self.pink_auth = pink_auth


class UserSpecification(CompositeSpecification):
    def is_satisfied(self, candidate):
        return isinstance(candidate, User)

class GreenUserSpecification(CompositeSpecification):
    def is_satisfied(self, candidate):
        return getattr(candidate, 'green_auth'False)
    
    
class PinkUserSpecification(CompositeSpecification):
    def is_satisfied(self, candidate):
        return getattr(candidate, 'pink_auth'False)
    
    
class UserAgeSpecification(CompositeSpecification):
    def is_satisfied(self, candidate):
        return getattr(candidate, 'age'0) > 18
    
    
class UserIncomingSpecification(CompositeSpecification):
    def is_satisfied(self, candidate):
        return getattr(candidate, 'incoming'0) > 10

這樣我們就把具體的判斷邏輯和規則剝離出來了,這樣兩者之間就互不影響了,最後我們來看下具體應用的例子。

ivan = User(green_auth=True)
lily = User(age=23, incoming=20, green_auth=True, pink_auth=True)

specification = UserSpecification().and_specification(PinkUserSpecification).and_specification(GreenUserSpecification).and_specification(UserAgeSpecification).and_specification(UserIncomingSpecification)


print(specification.is_satified(ivan))
print(specification.is_satified(lily))

這裡的specification就是產品經理提的具體規則,如果以後需要修改我們只需要修改它的定義即可。如果某一條具體的判斷邏輯需要變動,我們找到對應的程式碼改動,就不會影響邏輯以及其他業務程式碼了。

之前有大牛曾經說過,某種意義上來說設計模式的誕生,其實是為了應對產品經理們朝三暮四的需求變動而產生的。不知道大家看完這個例子有沒有這樣的感觸,至少我是覺得挺有道理的。

好了,今天的文章就到這裡,衷心祝願大家每天都有所收穫。如果還喜歡今天的內容的話,請來一個三連支援吧~(點贊、關注、轉發

相關文章