IOC 容器
什么叫 ioc 容器?
如果把在 Noba 中提供某方面功能的叫做服务1,那么把生产这些服务实体的地方叫做容器或则工厂。而服务有时候会依赖其他服务,如何能解决这些依赖关系?ioc 容器提供了一种很好的解决依赖关系的依赖注入工厂模式。
┌─────────────┐ ┌─────────────┐
│ Application │------->│IOC Container│
└─────────────┘ └─────────────┘
▲ |
| ┌────────────┴───────────┐
| │ │
| Register dependencies Resolve objects
| │ │
| ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Class │ │ Interface │ │ Object │
├─────────────┤ ├─────────────┤ └─────────────┘
│ Dependencies│<-│ Dependencies│
└─────────────┘ └─────────────┘
接下来将从四个方面来阐述 Noba 的 ioc 容器。初识 ioc 容器介绍一套完整的 ioc 容器代码,从创建到注册到使用。创建服务介绍如何创建一个 Noba 服务。注册服务介绍如何把创建的服务注册到 ioc 容器中。创建服务实体介绍如何通过容器创建出服务实体,并使用服务实体实现该有的服务。
初识 ioc 容器
- 新建的 noba 项目
mkdir noba_project cd noba_project
- 在
services
下新建backtest.py
文件如下# services/backtest.py class Indicators(object): def __init__(self, *args): super(Indicators, self).__init__(*args) def simple_moving_average(self, datas, period=5): return sum(datas[0:period])/period class Backtest(object): def __init__(self, ind: Indicators, *args): self.ind = ind def run(self, datas): today_close = datas[4] if self.ind.simple_moving_average(datas) > today_close: print('Five days sma cross close') else: print('Five days sma not cross close')
- 修改
main.py
如下:# main.py from noba import core if __name__ == '__main__': backtest = core.make('services.backtest.Backtest') close = [1.9, 1.3, 2.5, 3.1, 2.1] backtest.run(close)
- 运行 main.py
python main.py
创建服务
* 以 初识 ioc 容器 中代码为例:假设有一个量化回测类依赖于某个指标类
# services/backtest.py
class Indicators(object):
def __init__(self, *args):
super(Indicators, self).__init__(*args)
def simple_moving_average(self, datas, period=5):
return sum(datas)/period
# Backtest 依赖于 Indicators类,所以在 __init__ 中进行依赖注入
class Backtest(object):
def __init__(self, ind: Indicators, *args):
self.ind = ind # 容器会自动解决依赖关系,获得 Indicators 的实例
def run(self, datas):
today_close = datas[4]
if self.ind.simple_moving_average(datas) > today_close:
print('sma cross close')
else:
print('sma not cross close')
- 在服务(Backtest)的 __init__ 中通过
ind: Indicators
方式注入依赖(Indicators)。但容器不一定要解决依赖,也可以简单用作实例化的目的。比如:ind = core.make(Indicators) # 等同于 ind = Indicators()
- 依赖和服务可以不在
main.py
文件中。甚至依赖和服务也不需要在一个文件里
注册服务
* 注册时可以用服务或接口的路径字符串,比如 'services.backtest.ThreeDayBacktest'
就是一个路径字符串。也可以通过 import
服务或则接口,然后用该服务或接口进行注册。推荐使用路径字符串。
-
通过服务别名注册2
- 编辑
config/core_config.json
中aliases
值。举例:
*注意:服务别名注册默认在创建服务实体时是单例模式。可以通过如下方式取消单例模式"aliases": { "backtest": "services.backtest.Backtest" }
"aliases": { "backtest": ["services.backtest.Backtest", false] }
- 编辑
main.py
# backtest = core.make('services.backtest.Backtest') backtest = core.make('services') # 因为有了别名,所以直接可以通过别名创建服务实体
- 编辑
-
通过绑定注册3。你可以通过
bind
,singleton
,instance
绑定接口到服务或则别名到服务。在bind
,singleton
绑定时,还可以绑定一个函数-
修改
services/backtest.py
如下# services/backtest.py from abc import ABCMeta, abstractmethod class BacktestAbs(object, metaclass=ABCMeta): @abstractmethod def run(self): pass class Indicators(object): def __init__(self, *args): super(Indicators, self).__init__(*args) def simple_moving_average(self, datas, period): return sum(datas[0:period])/period class ThreeDayBacktest(BacktestAbs): def __init__(self, ind: Indicators, *args): self.ind = ind def run(self, datas): today_close = datas[2] if self.ind.simple_moving_average(datas, period=3) > today_close: print('Three days sma cross close') else: print('Three days sma not cross close') class FiveDayBacktest(BacktestAbs): def __init__(self, ind: Indicators, *args): self.ind = ind def run(self, datas): today_close = datas[4] if self.ind.simple_moving_average(datas, period=5) > today_close: print('Five days sma cross close') else: print('Five days sma not cross close')
-
bind
和singleton
的区别仅仅在于创建服务实体时是不是单例模式。示例:修改main.py
如下# main.py from noba import core if __name__ == '__main__': # 绑定 BacktestAbs 接口到 ThreeDayBacktest 服务 core.bind('services.backtest.BacktestAbs', 'services.backtest.ThreeDayBacktest') # 绑定 BacktestAbs 接口到 FiveDayBacktest 服务 # core.bind('services.backtest.BacktestAbs', 'services.backtest.FiveDayBacktest')
* tips: 觉得接口名或路径字符串太长?还可以通过绑定接口到别名的方式减少创建服务实体时的麻烦。例如:
# main.py from noba import core if __name__ == '__main__': # 绑定 BacktestAbs 接口到 ThreeDayBacktest 服务 core.bind('services.backtest.BacktestAbs', 'services.backtest.ThreeDayBacktest') core.bind('BacktestAbs', 'services.backtest.BacktestAbs') # make 的用法见创建服务实体 # core.make('BacktestAbs') # 不再需要 core.make('services.backtest.BacktestAbs')
-
instance
绑定接口或则别名到一个实例# main.py from noba import core if __name__ == '__main__': three_day_backtest = core.make('services.backtest.ThreeDayBacktest') core.instance('services.backtest.BacktestAbs', three_day_backtest)
-
绑定函数为创建服务实体时提供了更多操作空间
# main.py from noba import core def ret_services(core): print('绑定接口或别名到函数') three_day_backtest = core.make('services.backtest.ThreeDayBacktest') return three_day_backtest if __name__ == '__main__': core.bind('services.backtest.BacktestAbs', ret_services)
-
-
通过服务提供者注册。服务提供者是绑定服务的更加工程化实现方式。
register
用于服务绑定。绑定方法同第 2 点。boot
用于服务提供者的其他引导工作,会在所有服务提供者的注册工作完成后运行-
修改
services/backtest.py
同绑定环节 -
新建
provider/backtest_provider.py
文件如下:# provider/backtest_provider.py class BacktestProvider(): def __init__(self, core): self.__core = core def register(self): # 绑定 BacktestAbs 接口到 ThreeDayBacktest 服务 self.__core.bind('services.backtest.BacktestAbs', 'services.backtest.ThreeDayBacktest') # 绑定 BacktestAbs 接口到 FiveDayBacktest 服务 # self.__core.bind('services.backtest.BacktestAbs', 'services.backtest.FiveDayBacktest') return self def boot(self): pass
-
修改
config/core_config.json
中providers
值如下:{ "providers": ["provider.backtest_provider.BacktestProvider"] }
-
创建服务实体
* 通过容器创建服务实体有三种等价方式:core.make()
、core()
、core[]
。无论何种方式都能解决依赖注入问题
-
没有经过注册的服务,也可以直接通过容器创建服务实体。* 创建对象可以是一个服务的路径字符串,比如
'services.backtest.ThreeDayBacktest'
。也可以是通过import
服务。推荐使用路径字符串。# main.py from noba import core if __name__ == '__main__': # 假设 ThreeDayBacktest 没有通过服务别名、绑定、服务提供者进行过注册,仍然可以直接通过容器创建出服务实体 three_day_backtest = core.make('services.backtest.ThreeDayBacktest')
-
通过服务别名注册的服务。沿用注册服务第 1 点的例子
# main.py from noba import core if __name__ == '__main__': backtest = core.make('backtest') close = [1.9, 1.3, 2.5, 3.1, 2.1] backtest.run(close) # 运行 main.py 会打印出下面信息 # Five days sma cross close
-
通过绑定注册的服务。沿用注册服务第 2 点的例子
# main.py from noba import core if __name__ == '__main__': backtest = core.make('services.backtest.BacktestAbs') close = [1.9, 1.3, 2.5, 3.1, 2.1] backtest.run(close) # 运行 main.py 会打印出下面信息 # Three days sma not cross close
-
通过服务提供者注册的服务。沿用注册服务第 3 点的例子
# main.py from noba import core if __name__ == '__main__': backtest = core.make('services.backtest.BacktestAbs') close = [1.9, 1.3, 2.5, 3.1, 2.1] backtest.run(close) # 运行 main.py 会打印出下面信息 # Three days sma not cross close
一个服务是一个类,也可以是一个函数或则一个 python 模块等
服务别名的作用是用别名全局代替服务。这样可以方便的在任何地方用较短的路径创建服务实体
绑定注册为面向接口编程提供一种便捷方式