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

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

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

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

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

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

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

简单工厂模式优点:

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

简单工厂模式缺点:

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren <[email protected]>
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中 通过 工厂模式实现的 根据不同环境参数,返回对应配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren <[email protected]>
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配置中,根据不同入参返回对应 数据库类型 的 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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