6

プログラムを適切に設計する方法について質問があります。私のプログラムは非常に単純ですが、優れたアーキテクチャーを持ち、将来的にプログラムを簡単に拡張できるようにしたいと考えています。

私のプログラムは、外部データ ソース (XML) からデータをフェッチし、これらのデータから情報を抽出し、最後にデータベースに情報をインポートするための SQL ステートメントを準備する必要があります。したがって、現在および将来存在するすべての外部データ ソースには、アプリケーションの単純な「フロー」があります。つまり、フェッチ、抽出、およびロードです。

DataFetcher、DataExtractor、DataLoader という汎用クラスを作成し、それらから継承する特定のクラスを作成することを考えていました。工場の設計パターンが必要になると思いますが、どれですか?FactoryMethodまたはAbstract Factory?

次のようなコードも使用したくありません。

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

理想的には (これが簡単に可能かどうかはわかりません)、新しい「データ ソース プロセッサ」を作成し、既存のコードに 1 ~ 2 行を追加して、プログラムが新しいデータ ソースからデータをロードするようにしたいと考えています。

目標を達成するために設計パターンを使用するにはどうすればよいですか? Pythonでいくつかの例を提供できれば、それは素晴らしいことです。

4

5 に答える 5

1

最も重要な部分、つまりデータの形状について話すことを怠っています。ここが本当に一番重要です。「デザイン パターン」は気を散らすものです。これらのパターンの多くは、Python にはない言語の制限のために存在し、不必要な厳格さをもたらします。

  1. まず、データの形状を見てください。例えば:
    1. 最初に XML を用意します
    2. 次に、XML から抽出されたデータのコレクションを取得します (単純な dict? ネストされた dict? 必要なデータは? 同種か異種か? これは最も重要なことですが、あなたはそれについて話しません!)
    3. 次に、このデータを SQL バックエンドでシリアル化/永続化します。
  2. 次に、そのデータの操作を容易にするために、メソッド、プロパティ、または dict または tuple 内のアイテムの "インターフェイス" (口頭での説明) を設計します。シンプルに保ち、ネイティブの Python 型に固執する場合は、関数と dict/tuple だけで、クラスさえ必要ない場合があります。
  3. アプリケーションに必要なレベルの抽象化が得られるまで繰り返します。

たとえば、「extractor」のインターフェースは「xml 文字列を生成する iterable」である可能性があります。これは、ジェネレーターまたは__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。(これは Pyramid によって使用されます。)

独自の抽出変換ロード アプリを作成するのではなく、スクレイピーを検討しましたか? スクレイピーを使用すると、文字列が与えられ、一連のアイテム (データ) またはリクエスト (取得する URL など、より多くの文字列のリクエスト) を返す「Spider」を記述できます。アイテムは構成可能なアイテム パイプラインに送られ、アイテムを渡す前に、受け取ったアイテムを必要に応じて処理します (DB に永続化するなど)。

Scrapy を使用しない場合でも、データ中心のパイプラインのような設計を採用し、具体的な「クラス」と「パターン」ではなく、抽象的な「呼び出し可能」および「反復可能」インターフェースの観点から考える必要があります。

于 2013-02-08T17:49:54.290 に答える
0

XML は構造化されており、SQL-Insert は表形式です。十分に単純に聞こえますが、過度に設計しないでください。あなたの基本的なアプローチはおそらく次のようになります。

  1. ファイル システムで多数の XML ファイルを検索する
  2. XML でパーサーを呼び出し、いくつかのツリーを取得します
  3. ツリーをクエリするか、ツリーを再帰的に処理して、一連の表形式のデータ構造を埋めます
  4. いくつかの INSERT を生成するデータ構造をシリアル化します

あなたの「ビジネスロジック」はポイント3で、ケースバイケースで変わります。適切に記述された例は、抽象化の複数のレイヤーよりもはるかに多くの後継者を支援します。全体が小さすぎて、独自のドメイン固有言語に値しない可能性があります。また、XSLT は既に存在します。

他のポイントは再利用の候補ですが、ファクトリ パターンというよりも、適切に記述され、十分に文書化された関数のように思えます。

于 2013-02-08T17:00:16.613 に答える
0

あなたがやろうとしているのは、モジュールの動的インポートです(これは、いくつかの基本クラスに基づいています)。動的 dll ロードの C++ ユースケースによく似ています。

次のSO の質問を試してください。のPythonドキュメントと同様にimportlib.import_module(これは単なるラッパーです__import__

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

個々の外部ソースではなく、その上に抽象化を追加します。外部ソースと対話する方法を抽象化します。たとえば、SoapClient、HttpClient、XmlServiceCLient、NativeOBjectCLient などです。この方法では、新しい方法を使用して外部ソースを呼び出す必要がある場合にのみ、新しいクラスを追加する必要があります。この方法では、新しいフェッチャー クラスを頻繁に記述する必要はありません。(注: 私は Python 開発者ではありません)

ServiceRegistry パターンを使用して外部リソースを呼び出します。サービスの registry.xml 内の 1 つのエントリは次のようになります。

    <service registry>
      <externaldatasource>
         <dsname> xmlservice</dsname>
         <endpointurl> some url </endpointurl>
         <invocationtype> Soap </invocationtype>
      </externaldatasource>
    </service registry> 

クラスが外部ソースにアクセスする必要がある場合、taht calss は単純に Service Registry クラスにデータソース名を渡します。SR は xml ファイルを読み取り、外部ソースを呼び出してデータを取得します。これで、1 つのクラスがすべての外部呼び出しを処理し、コードのオーバーヘッドはほとんどなくなりました。

クライアントから生データを取得したら、それをデータ モデルに変換します。独自の Xsd があると仮定します。xslt を使用して、着信 XML を ur xsd 形式に変換し、検証にも使用します。Json のような非 XML データ形式を処理するファクトリ パターンを提案します。それらを実装する必要はありません...将来の拡張の始まりです。

このクラスのセット全体を Gateway パッケージの下に置くことができます。外部クライアントに依存するコードはすべてこのパッケージ内にあり、他のパッケージには浸透しません。これをゲートウェイパターンと呼びます。このパッケージのパブリック クラスへの入出力は、ドメイン モデルになります。

次に、外部データソースから独立した db にロードするための 1 つのロジックがあります。

于 2013-02-14T17:28:14.033 に答える