可扩展程序的良好设计模式

时间:2013-02-08 15:50:54

标签: python design-patterns architecture factory-pattern

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

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

我正在考虑创建名为DataFetcher,DataExtractor和DataLoader的泛型类,然后编写将继承它们的特定类。我想我需要一些工厂设计模式,但是哪个? FactoryMethod或抽象工厂?

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

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

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

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

5 个答案:

答案 0 :(得分:10)

如果获取者都具有相同的界面,则可以使用字典:

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

保持灵活性 - 只需编写干净的惯用代码即可。我倾向于喜欢“你不需要它”(YAGNI)哲学。如果您花费太多时间来展望未来以找出您将需要的东西,那么当您找到实际需要的内容时,您的代码将变得过于臃肿和复杂,无法进行简单的调整。如果代码是清理的,那么应该很容易在以后进行重构以满足您的需求。

答案 1 :(得分:1)

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

  1. 首先查看数据的形状。例如。:
    1. 首先你有XML
    2. 然后你有一些从XML中提取的数据集合(一个简单的词典?一个嵌套的词典?你需要什么数据?它是同质的还是异质的?这是最重要的,但你不要谈论它! )
    3. 然后在SQL后端序列化/保存此数据。
  2. 然后设计方法,属性甚至只是dict或tuple中的项目的“接口”(语言描述),以便于对该数据进行操作。如果你保持简单并坚持使用本机Python类型,你可能甚至不需要类,只需要函数和dicts /元组。
  3. 重新进行迭代,直到获得应用程序所需的抽象级别。
  4. 例如,“提取器”的接口可能是“可生成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是表格式的。听起来很简单,不要过度设计它。您的基本方法可能是:

  1. 在文件系统中查找一堆XML文件
  2. 在XML上调用解析器,获取几棵树
  3. 查询树或递归树以填充一堆表格数据结构
  4. 序列化生成几个INSERT的数据结构
  5. 您的“业务逻辑”是第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的逻辑,它独立于任何外部数据源。