簡單工廠模式(simple factory)及程式碼實現

rgc_520_zyl發表於2020-10-25

簡單工廠模式屬於 建立型模式,是用來建立物件的模式,在建立物件時,客戶端程式碼無需知道建立邏輯,只要知道傳輸什麼引數即可

 

實現簡單工廠模式思路(按照如下程式碼示例 思考):

我的業務需求有2個,分別為 計算買入手續費,賣出手續費,分析後發現 獲取 手續費費率 規則 相同,而且 都需要當日淨值

既然都是 手續費,且有相同部分,便可抽象出 一個 手續費基類,包含買入/賣出相同部分,計算手續費規則不同,則可 讓子類實現;

在客戶端程式碼 需要計算 買入或賣出手續費 時,無需考慮 手續費相關類的實現細節,只需 輸入不同引數即可返回 買入或賣出 工廠類

既然如此,便可 通過 簡單工廠模式實現:

 

簡單工廠模式優點:

  • 符合 開閉原則 ;   無需更改現有客戶端程式碼, 便可在程式中引入新的手續費型別如 分紅
  • 符合 單一職責原則 ;  一個具體工廠類只負責計算一種手續費
  • 客戶端呼叫 方便;  只需不同入參,便可獲得對應 物件
  • 避免建立者和具體產品之間的緊密耦合 ;  如 如下程式碼中的 handing_fee_factory建立著函式 只負責根據不同入參返回對應類,而類的具體實現則不在此處

簡單工廠模式缺點:

  • 當 有很多 工廠類時,建立者程式碼 會有很多條件判斷的程式碼,因此而變得複雜; 可能 會有 很多 if else;或者 通過dict來實現引數和具體類的對映;

 

如下幾個示例為 相關工廠模式實現的程式碼

1.簡單工廠模式實現的 計算 基金買入/賣出 手續費 demo (相關計算方式已經簡化,實際程式碼要複雜很多)

# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren <rgc@bvrft.com>
All rights reserved
create time '2020/10/25 15:58'

Usage:
簡單工廠模式 實現 基金 買入/賣出 手續費 計算規則
"""
from abc import ABC, abstractmethod
from copy import deepcopy


class HandlingFeeBase(ABC):
    """手續費基類"""

    @abstractmethod
    def __init__(self, rule, net_val):
        """

        :param rule: 手續費規則
        :param net_val: 當日淨值
        """
        self.rule = rule
        self.net_val = net_val

    def _get_handling_fee_rate(self, benchmark_num):
        """
        獲取手續費費率
        此乃刪減後部分 計算手續費規則,不能直接使用在業務程式碼中
        根據手續費規則list倒敘 後,根據 每個規則第一個資料 大小判斷,如果大於等於 則 直接返回 對應費率
        :param benchmark_num: 買入金額 或 賣出 的時間天數
        :return:

        [
            [0,100000,0.015], # 手續費 >=0,<100000,費率 0.015
            [100000,1000000,0.005],# 手續費 >=100000,<1000000,費率 0.005
            [1000000,-1,0] # 手續費 >=1000000,<無限大,費率 0
        ]
        如果 買入了 100001 元,倒敘校驗後 發現 100000<=100001<1000000 所以返回 0.005費率
        如果 買入了 10000 元,倒敘校驗後 發現 100000<=100000<1000000 所以返回 0.005費率
        """
        rule = deepcopy(self.rule)
        rule.reverse()
        for rule_list in rule:
            handling_fee_rate = rule_list[2]
            min_num = rule_list[0]
            max_num = rule_list[1]
            # -1表示正無窮大
            if max_num == -1:
                max_num = float('inf')
            if min_num <= benchmark_num < max_num:
                return handling_fee_rate

    @abstractmethod
    def calculate(self):
        """
        計算 手續費 方法,由子類實現
        :return:
        """
        pass


class BuyHandingFee(HandlingFeeBase):
    """買入手續費 工廠類"""

    def __init__(self, rule, net_val, **kwargs):
        """
        初始化
        :param rule:
        :param net_val:
        :param buy_amount:
        """
        super(BuyHandingFee, self).__init__(rule, net_val)
        self.buy_amount = kwargs.get('buy_amount')
        self.handling_fee_rate = self._get_handling_fee_rate(self.buy_amount)

    def calculate(self):
        """
        計算買入手續費
        :return:
        """
        return self.buy_amount * self.handling_fee_rate


class SellHandingFee(HandlingFeeBase):
    """賣出手續費 工廠類"""

    def __init__(self, rule, net_val, **kwargs):
        """
        初始化
        :param rule:
        :param net_val:
        :param kwargs:
        """
        super(SellHandingFee, self).__init__(rule, net_val)
        self.sell_days = kwargs.get('sell_days')  # 距離賣出間隔天數
        self.sell_share = kwargs.get('sell_share')  # 賣出份額
        # 根據間隔天數 算出手續費費率
        self.handling_fee_rate = self._get_handling_fee_rate(self.sell_days)

    def calculate(self):
        """
        計算賣出手續費
        :return:
        """
        # 根據 賣出淨值 * 賣出份額 * 費率 計算 賣出手續費
        return self.net_val * self.sell_share * self.handling_fee_rate


def handing_fee_factory(rule, net_val, **kwargs):
    """
    簡單工廠模式的建立著,可以是類或者函式,根據不同入參,返回對應 類
    根據入參 獲取對應 買入 或 賣出 手續費計算類
    :param rule:
    :param net_val:
    :param kwargs:
    :return:
    """
    if 'sell_days' in kwargs and 'sell_share' in kwargs:
        print('返回 賣出 類')
        return SellHandingFee(rule, net_val, **kwargs)
    elif 'buy_amount' in kwargs:
        print('返回 買入 類')
        return BuyHandingFee(rule, net_val, **kwargs)
    raise Exception('引數錯誤,無法獲取對應手續費 物件')


if __name__ == '__main__':
    """
    客戶端(client)程式碼 呼叫 簡單工廠模式實現的 計算買入/賣出 手續費
    客戶端只需填寫引數,便根據不同引數返回對應計算手續費物件,客戶端 不用考慮具體手續費實現邏輯
    在 後續 新增不同手續費計算類 如 分紅,也符合 開閉原則(抽象出一層,達到 對 擴充套件開放,對修改封閉),原有客戶端計算買入/賣出的程式碼無需修改
    """

    # 計算買入手續費
    handing_fee_obj = handing_fee_factory([[0, 100000, 0.015], [100000, -1, 0.001]], 1.5, **{'buy_amount': 1000})
    print('買入手續費金額為:', handing_fee_obj.calculate())
    # 計算賣出手續費
    handing_fee_obj = handing_fee_factory([[0, 7, 0.015], [7, -1, 0.001]], 1.5, **{'sell_share': 100, 'sell_days': 5})
    print('賣出手續費金額為:', handing_fee_obj.calculate())

2.Flask中 通過 工廠模式實現的 根據不同環境引數,返回對應配置類

# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren <rgc@bvrft.com>
All rights reserved
create time '2020/10/25 15:58'

Usage:
簡單工廠模式 實現 flask 配置
"""
import logging
from abc import ABC

from flask import Flask


class Config(ABC):
    """
    配置基類
    """
    pass


class LocalConfig(Config):
    """
    本地配置類
    """
    ENV = 'local'
    DEBUG = True
    LOG_LEVEL = logging.DEBUG


class DevelopConfig(Config):
    """
    開發服配置類
    """
    ENV = 'develop'
    DEBUG = True
    LOG_LEVEL = logging.DEBUG


class ProductConfig(Config):
    """
    生產服配置類
    """
    ENV = 'product'
    DEBUG = False
    LOG_LEVEL = logging.INFO


# 建立者,此處通過簡單的 dict資料結構 便可實現
config = {
    "LOCAL": LocalConfig,
    "DEV": DevelopConfig,
    "PROD": ProductConfig
}


def create_app(config_name):
    """
    客戶端 程式碼部分,根據不同入參,獲取對應 配置類
    :param config_name:
    :return:
    """
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    return app


if __name__ == '__main__':
    # 本地配置
    app = create_app('LOCAL')
    print(app.config.get('ENV'))

    # 生產配置
    app = create_app('PROD')
    print(app.config.get('ENV'))

3.mysql配置中,根據不同入參返回對應 資料庫型別 的 物件

def connect(db, *arg, **kwargs):
    """
    此函式便是一個簡單版本的 工廠模式, 根據不同的入參,返回不同型別的 資料庫連結字串(其實最好 返回的是 不同的 資料庫物件),此處copy 的網上的程式碼;
    而且 這種過於簡單的方式是否符合 簡單工廠模式(仁者見仁智者見智)
    """
    db = db.lower()
    dbname = kwargs['db']

    if db == 'mysql':
        result = "mysql+pymysql://{username}:{password}@{server}/{dbname}".format(username = kwargs['username'], password = kwargs['password'], server = kwargs['server'], dbname=dbname)
    elif db == 'postgresql:
        result = 'postgresql://{username}:{passwrod}@{server}/{dbname}'.format(susername = kwargs['username'], password = kwargs['password'], server = kwargs['server'], dbname=dbname)

    return result

 

總結:

需要理解 簡單工廠模式的思想核心:不同引數,返回 同一種抽象層下的不同物件 即可;不要 過於在意 實現的具體方式;要站在 程式碼設計的 角度去思考 只要滿足 工廠模式的 條件 寫出的程式碼便是工廠模式; 

如  簡單工廠模式的建立者 可以是 類,可以是 函式 甚至可以是 一個dict ;

腦子需要靈活,不用在乎程式碼實現形式,只要符合設計模式 核心思想(意境) 便是完人;

 

相關連結:

https://refactoringguru.cn/design-patterns/factory-method

https://refactoringguru.cn/design-patterns/factory-method/python/example

https://github.com/youngsterxyf/mpdp-code/blob/master/chapter1/factory_method.py

https://www.cnblogs.com/littlefivebolg/p/9927825.html

相關文章