6

我有一个关于如何为我的程序做出好的设计的问题。我的程序非常简单,但我希望拥有良好的架构,并使我的程序在未来易于扩展。

我的程序需要从外部数据源 (XML) 中获取数据,从这些数据中提取信息,最后它需要准备 SQL 语句以将信息导入数据库。因此,对于现在和将来存在的所有外部数据源,我的应用程序都有一个简单的“流程”:获取、提取和加载。

我正在考虑创建名为:DataFetcher、DataExtractor 和 DataLoader 的通用类,然后编写将从它们继承的特定类。我想我需要一些工厂设计模式,但是哪个?工厂方法还是抽象工厂?

我也不想使用这样的代码:

if data_source == 'X':
     fetcher = XDataFetcher()
elif data_source == 'Y':
     fetcher = YDataFetcher()
....

理想情况下(我不确定这是否容易),我想编写新的“数据源处理器”,在现有代码中添加一两行,我的程序将从新数据源加载数据。

如何使用设计模式来实现我的目标?如果您可以在 python 中提供一些示例,那就太好了。

4

5 回答 5

10

如果提取器都具有相同的接口,则可以使用字典:

fetcher_dict = {'X':XDataFetcher,'Y':YDataFetcher}
data_source = ...
fetcher = fetcher_dict[data_source]()

就保持灵活性而言——只需编写干净的惯用代码即可。我倾向于喜欢“你不需要它”(YAGNI)的哲学。如果您花费太多时间试图展望未来以弄清楚您将需要什么,那么当您发现您真正需要什么时,您的代码将变得过于臃肿和复杂,无法进行简单的调整。如果代码是预先清理的,那么以后重构以满足您的需要应该很容易。

于 2013-02-08T15:55:22.517 回答
1

您忽略了谈论最重要的部分,即数据的形状。这真的是这里最重要的事情。“设计模式”让人分心——其中许多模式的存在是因为 Python 没有的语言限制,并且引入了不必要的僵化。

  1. 首先查看数据的形状。例如:
    1. 首先你有 XML
    2. 然后你有一些从 XML 中提取的数据集合(一个简单的 dict?一个嵌套的 dict?你需要什么数据?是同质的还是异构的?这是最重要的,但你不谈论它!)
    3. 然后在 SQL 后端序列化/持久化这些数据。
  2. 然后设计方法、属性甚至只是字典或元组中的项目的“接口”(口头描述),以促进对该数据的操作。如果你保持简单并坚持使用原生 Python 类型,你甚至可能不需要类,只需要函数和字典/元组。
  3. 重复迭代,直到获得应用程序所需的抽象级别。

例如,“提取器”的接口可能是“产生 xml 字符串的迭代”。请注意,这可以是生成器也可以是带有__iter__andnext()方法的类!无需定义抽象类和子类!

添加到数据中的可配置多态性取决于数据的确切形状。例如,您可以使用约定:

# 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!

或者,如果您需要进一步抽象(也许您需要添加您无法控制的新模块),您可以使用注册表(这只是一个字典)和模块约定:

# 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. (金字塔使用它。)

与其推出自己的 extract-transform-load 应用程序,不如考虑使用 scrapy?使用scrapy,您将编写一个“Spider”,它被赋予一个字符串并返回项目序列(您的数据)或请求(请求更多字符串,例如要获取的URL)。这些项目被发送到一个可配置的项目管道,该管道在传递它们之前对它接收到的项目做任何它想要的事情(例如持久化在数据库中)。

即使你不使用 Scrapy,你也应该采用以数据为中心的类似管道的设计,并且更喜欢从抽象的“可调用”和“可迭代”接口的角度思考,而不是具体的“类”和“模式”。

于 2013-02-08T17:49:54.290 回答
0

您正在尝试做的是模块的动态导入(基于某些基类)。很像动态 dll 加载的 c++ 用例。

试试下面的SO 问题。以及 python 文档importlib.import_module(这只是一个包装器__import__

import importlib
moduleToImport = importlib.import_module("moduleName")
于 2013-02-08T16:18:59.980 回答
0

XML 是结构化的,SQL-Inserts 是表格的。听起来很简单,不要过度设计它。您的基本方法可能是:

  1. 在文件系统中找到一堆 XML 文件
  2. 在 XML 上调用解析器,取回几棵树
  3. 查询树或递归树以填充一堆表格数据结构
  4. 序列化产生几个 INSERT 的数据结构

您的“业务逻辑”是第 3 点,它会因情况而异。一个写得很好的例子将比几层抽象更能帮助任何后继者。整个事情可能太小了,不值得拥有自己的领域特定语言。此外,XSLT 已经存在。

其他点是可重用的候选点,但对我来说,这听起来更像是编写良好且记录良好的函数,而不是工厂模式。

于 2013-02-08T17:00:16.613 回答
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 转换为您的 xsd 格式并进行验证。我建议使用工厂模式来处理 Json 等非 XML 数据格式。不必实施它们……只是为将来的扩展提供一个机会。

这整套类可以在网关包下。任何外部客户端相关代码都将包含在此包中,并且不会渗入任何其他包。这称为网关模式。此包中公共类的输入/输出将是您的域模型。

然后你有一个单一的逻辑来加载到数据库中,它独立于任何外部数据源。

于 2013-02-14T17:28:14.033 回答