我有一个关于如何为我的程序设计好的问题。我的程序非常简单,但我希望拥有良好的架构,并使我的程序在将来可以轻松扩展。
我的程序需要从外部数据源(XML)获取数据,从这些数据中提取信息,最后需要准备SQL语句以将信息导入数据库。因此,对于现在和将来存在的所有外部数据源,我的应用程序都有简单的“流程”:获取,提取和加载。
我正在考虑创建名为DataFetcher,DataExtractor和DataLoader的泛型类,然后编写将继承它们的特定类。我想我需要一些工厂设计模式,但是哪个? FactoryMethod或抽象工厂?
我也想不要使用这样的代码:
if data_source == 'X':
fetcher = XDataFetcher()
elif data_source == 'Y':
fetcher = YDataFetcher()
....
理想情况下(我不确定这是否容易实现),我想编写新的“数据源处理器”,在现有代码中添加一行或两行,我的程序将从新数据源加载数据。
如何利用设计模式来实现目标?如果您可以在python中提供一些示例,那就太棒了。
答案 0 :(得分:10)
如果获取者都具有相同的界面,则可以使用字典:
fetcher_dict = {'X':XDataFetcher,'Y':YDataFetcher}
data_source = ...
fetcher = fetcher_dict[data_source]()
保持灵活性 - 只需编写干净的惯用代码即可。我倾向于喜欢“你不需要它”(YAGNI)哲学。如果您花费太多时间来展望未来以找出您将需要的东西,那么当您找到实际需要的内容时,您的代码将变得过于臃肿和复杂,无法进行简单的调整。如果代码是清理的,那么应该很容易在以后进行重构以满足您的需求。
答案 1 :(得分:1)
您忽略了谈论最重要的部分,即数据的形状。这真的是最重要的事情。 “设计模式”是一种分心 - 许多这些模式的存在是因为Python没有语言限制,并引入了不必要的僵化。
例如,“提取器”的接口可能是“可生成xml字符串的可迭代”。请注意,这可以是生成器或具有__iter__
和next()
方法的类!无需定义抽象类并将其子类化!
您添加到数据中的哪种可配置多态性取决于数据的确切形状。例如,您可以使用约定:
# persisters.py
def persist_foo(data):
pass
# main.py
import persisters
data = {'type':'foo', 'values':{'field1':'a','field2':[1,2]}}
try:
foo_persister = getitem(persisters, 'persist_'+data['type'])
except AttributeError:
# no 'foo' persister is available!
或者如果你需要进一步的抽象(也许你需要添加你无法控制的新模块),你可以使用一个注册表(这只是一个dict)和一个模块约定:
# registry.py
def register(registry, method, type_):
"""Returns a decorator that registers a callable in a registry for the method and type"""
def register_decorator(callable_):
registry.setdefault(method, {})[type_] = callable_
return callable_
return register_decorator
def merge_registries(r1, r2):
for method, type_ in r2.iteritems():
r1.setdefault(method, {}).update(r2[method])
def get_callable(registry, method, type_):
try:
callable_ = registry[method][type]
except KeyError, e:
e.message = 'No {} method for type {} in registry'.format(method, type)
raise e
return callable_
def retrieve_registry(module):
try:
return module.get_registry()
except AttributeError:
return {}
def add_module_registry(yourregistry, *modules)
for module in modules:
merge_registries(yourregistry, module)
# extractors.py
from registry import register
_REGISTRY = {}
def get_registry():
return _REGISTRY
@register(_REGISTRY, 'extract', 'foo')
def foo_extractor(abc):
print 'extracting_foo'
# main.py
import extractors, registry
my_registry = {}
registry.add_module_registry(my_registry, extractors)
foo_extracter = registry.get_callable(my_registry, 'extract', 'foo')
如果需要,您可以轻松地在此结构之上构建一个全局注册表(尽管您应该避免使用全局状态,即使它不太方便。)
如果您正在构建公共框架,并且您需要最大程度的可扩展性和形式主义,并且愿意支付复杂性,那么您可以查看zope.interface
之类的内容。 (金字塔使用它。)
您是否考虑过scrapy而不是推送自己的extract-transform-load应用程序?使用scrapy,您可以编写一个“Spider”,它被赋予一个字符串并返回Items(您的数据)或Requests(请求更多字符串,例如要获取的URL)的序列。这些项目是在一个可配置的项目管道中发送的,该管道在传递它们之前对它接收的项目(例如,在数据库中保留)执行任何操作。
即使您不使用Scrapy,您也应该采用以数据为中心的管道式设计,并且更倾向于使用抽象的“可调用”和“可迭代”接口来代替具体的“类”和“模式”。< / p>
答案 2 :(得分:0)
您要做的是动态导入模块(基于某些基类)。很像动态DLL加载的c ++用例。
尝试以下SO question。以及importlib.import_module
的python文档(它只是__import__
的包装器)
import importlib
moduleToImport = importlib.import_module("moduleName")
答案 3 :(得分:0)
XML被结构化,SQL-Inserts是表格式的。听起来很简单,不要过度设计它。您的基本方法可能是:
您的“业务逻辑”是第3点,它将根据具体情况而变化。一个写得很好的例子将帮助任何后继者远远超过几层抽象。整个事情可能太小,不值得使用自己的领域特定语言。此外,XSLT已经存在。
其他要点是重用的候选者,但对我来说听起来更像是写得好且记录良好的功能,而不是工厂模式。
答案 4 :(得分:0)
我会将抽象添加到个别外部源但不在其上方。摘要您如何与外部资源进行交互。例如,SoapClient,HttpClient,XmlServiceCLient,NativeOBjectCLient等。这样,您只需在使用新方法调用外部源时添加新类。这样您就不必经常编写新的fetcher类。 (注意:我不是Python开发人员)
使用ServiceRegistry模式调用外部资源。服务registry.xml中的一个条目是
<service registry>
<externaldatasource>
<dsname> xmlservice</dsname>
<endpointurl> some url </endpointurl>
<invocationtype> Soap </invocationtype>
</externaldatasource>
</service registry>
当一个类需要访问外部源时,taht calss只是将Service Registry类传递给数据源名称。 SR读取xml文件并调用外部源并获取数据。现在,单个类将处理所有外部调用,并且没有太多代码开销。
从客户端获得原始数据后,您希望将其转换为数据模型。我假设你有自己的Xsd。使用xslt将传入的XML转换为ur xsd格式并进行验证。我建议使用工厂模式来处理像Json这样的非XML数据格式。不必实现它们......只是未来扩展的开放。
这整套课程可以在Gateway包下。任何外部客户端相关代码都将在此包中,并且不会渗透到任何其他包。这称为网关模式。此包中公共类的输入/输出将是您的域模型。
然后您有一个用于加载到db的逻辑,它独立于任何外部数据源。