简单工厂模式(simple factory)及代码实现

简单工厂模式属于 创建型模式,是用来创建对象的模式,在创建对象时,客户端代码无需知道创建逻辑,只要知道传输什么参数即可

 

实现简单工厂模式思路(按照如下代码示例 思考):

我的业务需求有2个,分别为 计算买入手续费,卖出手续费,分析后发现 获取 手续费费率 规则 相同,而且 都需要当日净值

既然都是 手续费,且有相同部分,便可抽象出 一个 手续费基类,包含买入/卖出相同部分,计算手续费规则不同,则可 让子类实现;

在客户端代码 需要计算 买入或卖出手续费 时,无需考虑 手续费相关类的实现细节,只需 输入不同参数即可返回 买入或卖出 工厂类

既然如此,便可 通过 简单工厂模式实现:

 

简单工厂模式优点:

  • 符合 开闭原则 ;   无需更改现有客户端代码, 便可在程序中引入新的手续费类型如 分红
  • 符合 单一职责原则 ;  一个具体工厂类只负责计算一种手续费
  • 客户端调用 方便;  只需不同入参,便可获得对应 对象
  • 避免创建者和具体产品之间的紧密耦合 ;  如 如下代码中的 handing_fee_factory创建着函数 只负责根据不同入参返回对应类,而类的具体实现则不在此处

简单工厂模式缺点:

  • 当 有很多 工厂类时,创建者代码 会有很多条件判断的代码,因此而变得复杂; 可能 会有 很多 if else;或者 通过dict来实现参数和具体类的映射;

 

如下几个示例为 相关工厂模式实现的代码

1.简单工厂模式实现的 计算 基金买入/卖出 手续费 demo (相关计算方式已经简化,实际代码要复杂很多)

# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren
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, **kwargs):
        """

        :param rule: 手续费规则
        :param net_val: 当日净值
        """
        self.rule = kwargs.get('rule')
        self.net_val = kwargs.get('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, **kwargs):
        """
        初始化
        :param buy_amount:
        """
        super(BuyHandingFee, self).__init__(**kwargs)
        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, **kwargs):
        """
        初始化
        :param kwargs:
        """
        super(SellHandingFee, self).__init__(**kwargs)
        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(**kwargs):
    """
    简单工厂模式的创建着,可以是类或者函数,根据不同入参,返回对应 类
    根据入参 获取对应 买入 或 卖出 手续费计算类
    :param kwargs:
    :return:
    """
    if 'sell_days' in kwargs and 'sell_share' in kwargs:
        print('返回 卖出 类')
        return SellHandingFee(**kwargs)
    elif 'buy_amount' in kwargs:
        print('返回 买入 类')
        return BuyHandingFee(**kwargs)
    raise Exception('参数错误,无法获取对应手续费 对象')


if __name__ == '__main__':
    """
    客户端(client)代码 调用 简单工厂模式实现的 计算买入/卖出 手续费
    客户端只需填写参数,便根据不同参数返回对应计算手续费对象,客户端 不用考虑具体手续费实现逻辑
    在 后续 添加不同手续费计算类 如 分红,也符合 开闭原则(抽象出一层,达到 对 扩展开放,对修改封闭),原有客户端计算买入/卖出的代码无需修改
    """

    # 计算买入手续费
    handing_fee_obj = handing_fee_factory(
        **{'rule': [[0, 100000, 0.015], [100000, -1, 0.001]], 'net_val': 1.5, 'buy_amount': 1000})
    print('买入手续费金额为:', handing_fee_obj.calculate())
    # 计算卖出手续费
    handing_fee_obj = handing_fee_factory(
        **{'rule': [[0, 7, 0.015], [7, -1, 0.001]], 'net_val': 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

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页