简单工厂模式属于 创建型模式,是用来创建对象的模式,在创建对象时,客户端代码无需知道创建逻辑,只要知道传输什么参数即可
实现简单工厂模式思路(按照如下代码示例 思考):
我的业务需求有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