web-dev-qa-db-ja.com

異なる実装を必要とする同様のクラスの設計パターン

編集:更新は下部にあります

このシナリオには一般的な、またはベストプラクティスがあるかもしれませんが、私はそれに慣れていません。ただし、クラスをどのように実装したいかについては、主観的な意見の問題である可能性があります。いずれにせよ、ここでクラスデザイナーのスペクトルから意見を述べたいと思います。

現在、ユーザーがデータを視覚化するためのファイルを生成できるプロジェクトに取り組んでいます。

ライブラリは、フォーマットが異なる2つのファイルタイプ(バイナリとXML)をサポートします。これが原因で、クラスのインスタンス化とAPIアクセスをどのように制御したいかについてのジレンマが残されています。

  1. ファイルの種類と視覚化の種類ごとに個別のクラスを作成する
  2. 視覚化の種類ごとに個別のクラスを作成し、ファイルの種類ごとにメソッドを読み込む
  3. (実証されていません)オプション2の逆

オプション1:

_class Base:
    # Stuff
class F1V1(Base):
    # Methods specific to file and visualization type one
class F1V2(Base):
    # Methods specific to file type one and visualization type two
class F1V3(Base):
    # Methods specific to file type one and visualization type three
class F2V1(Base):
    # Same logic as before but for file type two
class F2V2(Base):
    # ...
class F2V3(Base):
    # ...
_

ここでは、ライブラリのユーザーが直接クラスを呼び出して、ライブラリに固有の操作を実行します。キーワードパラメータを設定して、ライブラリの機能を決定する必要はありません(例:fv = F1V2()

この方法の優れている点は、ユーザーが明示的に、作業しているファイルと視覚化の種類を正確に把握していることです。しかし、ファイルや視覚化の種類をさらに追加したい場合には、面倒で拡張性がないと思います。可能な組み合わせごとに新しいクラスを書くように強制します。

オプション2:

_class Base:
    # Stuff
class V1(Base):
    def __init__(self, ftype_1=True)
        self.ftype_1 = ftype_1
    def write(self):
        if self.ftype_1:
            # Write method specific to file type one
        # Write method specific to file type two
    # Assume 3 methods for each operation
    # One method such as `write()`
    # One method for each of the file types
class V2(Base):
    # Same logic as before
class V3(Base):
    # ...
_

このメソッドについて私が気に入らないのは、構築時に提供されるキーワードに基づいて実行する各クラスに対して複数のメソッドを定義する必要があることです(つまり、fv = V2(ftype_1=False))。理想的には、どのメソッドがそのクラスに属する必要があるかを決定するキーワード引数を指定することです。例えば:

_fv = V2() # Only contains methods to file type one operations
fv = V2(ftype_1=False) # Only contains methods to file type two operations
_

示されているように、以下を妨げるものは何もありません。

_fv = V2() # Set for file type one
fv.write_ftype_2() # Will produce an invalid file type formatting
_

キーワード引数に基づいてメソッドを動的にバインド/削除できる方法がわかりません。各視覚化タイプ内の各ファイルタイプのすべてのメソッドを単純に記述し、クラスに関係のないメソッドを削除できたら素晴らしいと思います。これが賢明かどうかはわかりませんが、次のようなシナリオはすでに考えられます。

_def write(self):
    if self.ftype_1:
        # Write method specific to file type one
    Elif self.type_2:
        # ...
    else:
        # ...
_

キーワード引数に基づいてクラスからメソッドを動的に削除した場合、最初のメソッドが保持されているとしたら、条件のポイントはどうなりますか?

概要:

それで、どちらが一般的またはベストプラクティスですか?どちらを改善できますか?または別の方法が欠けていますか?

理想的な例は(私の心の中で)でしょう:

_fv = Hexagons(ftype='.abc', flat=True, area=3)
fv.insert(data)
fv.write(data) # Specifically writes for file types of '.abc'
_

Hexagons()が___new___を介してサブクラスを返すようにできると思いますが、何が起こっているのかは不明かもしれません。 Hexagons()を呼び出すがABCHexagonsオブジェクトを受け取ると、ユーザーがコードベースを検査するときに混乱が生じる可能性があります。

ファクトリーメソッドはこれにidealですが、それは単にクラスのインスタンス化です。ただし、各視覚化タイプには、他のタイプには適用されないさまざまなキーワードパラメータがある場合があります。むしろ、私の問題は、コードベースでそれらを定義する方法にあり、最終的にはそれらをユーザーに公開する方法の解決につながります。

更新:

@Mihaiと@Ewanが提案した後、各ファイルタイプにライタークラスを持つことが最善の方法であり、継承はそうではないことは明らかです。ここで、構成がより良い戦略であるかどうかを調べる必要があります。詳細をいくつか明らかにしたいと思います。

  1. 各ファイルタイプには、形状のグリッドを表すようにフォーマットされたデータが含まれています
  2. ユーザーが入力したデータを表示するためにビジュアライザークラスは使用されません
  3. ビジュアライザークラスは、書き込みクラスが書き込む方法を決定するためにのみ使用されます

例えば:

_class binaryWriter:
    # Writes only binary files
class xmlWriter:
    # Writes only XML files
class Hexagons:
    # Contains methods for determining geometry
class Triangles:
    # Same as Hexagons
_

六角形タイルの配列を含むバイナリファイルを書きたいとしましょう。ビジュアライザー(六角形グリッド)を選択すると、ライタークラスとビジュアライザークラス内の操作が連携して、書き込みクラスがディスク上のファイルに書き込む方法を決定します。グリッドにポイントを挿入し、それが属する六角形を見つけたいとしましょう。

_w = binaryWriter(shape='hexagon')
w.insert(1, 2) # Some point of x, y
# Repeat insertions
w.save() # Write to file on disk
_
3
datta

あなたが正しく気づいたように、継承を使用してこの問題を解決することは、あなたが提示した方法では、良い考えではありません。代わりにコンポジションの使用を検討する必要がありますが、私は 継承のコンポジション のより広い概念を検討することをお勧めします。より具体的なデザインパターンを示すために、必要なのは Strategy だと思います。

この特定の場合、私がすることは次のとおりです。

  • データを読み取るための類似したクラスのグループがあります。それらはすべて同じように見え、動作しますが、内部の実装のみが異なります(1つはバイナリを読み取り、1つはXMLを読み取ります)。
  • データが読み取られた後のデータの内部契約を持っているこれは、可能なすべてのリーダーと視覚化の組み合わせの互換性を保証するために重要です。すべてのリーダーは同じ形式でデータを出力し、すべての視覚化はその単一の形式からデータを読み取る方法を知っています。
  • 視覚化のための同様のクラスのグループがあります。それらはすべて同じように見え、同じように動作します。彼らは内部の「ユニバーサル」フォーマットでデータを受け取り、それで何かをします。

次に、実行時に、データ読み取りクラスの1つのインスタンスをビジュアライゼーションの1つのインスタンスと動的に組み合わせ、それらの間でデータを受け渡すだけで済みます。どのデータ読み取りクラスとどの視覚化クラスを使用するかについての決定は、あなた(およびコード)が判断します。非常に単純なフローは次のようになります。

reader = binaryDataReader()
data = reader.readData()
visualisation = prettyColorsVisualisation()
visualisation.display(data)
1
Mihai Coman

コード内のオブジェクトを永続化形式から分離します。

バイナリファイルをロードしてxmlファイルとして保存したいという要件を追加したとします。

Hexagons v = binaryFileReader.Load(filename);
v.AddHex(1,2);
xmlFileWriter.Save(v);

hexagonクラスは視覚化のみを考慮します。色の設定、新しい要素の追加などに関連するメソッドしかありません

BinaryFileReader/Writerクラスは、メモリ内のHexagonオブジェクトをバイナリ形式のファイルに変換して戻すことのみを認識しています。同様に、xmlFileWriterはxmlとHexagonsについてのみ知っています。

ここで、新しい視覚化タイプを追加すると、オプションが表示されます。ファイルリーダーを改善して、複数の視覚化に関する知識を追加したり、新しい視覚化タイプを扱うまったく新しいクラスを作成したりできます。

同様に、新しいファイルタイプを追加する場合は、それに対処するために新しいCsvFileReaderWriterオブジェクトを作成できます。

0
Ewan