web-dev-qa-db-ja.com

Pythonでクラスを設計するにはどうすればよいですか?

私は以前の質問でいくつかの本当に素晴らしい助けをしました 足を検出するため足の中のつま先 ですが、これらのソリューションはすべて一度に1つの測定に対してのみ機能します。

これでデータが得られました これはオフで構成されています:

  • 約30匹の犬。
  • それぞれに24の測定値があります(複数のサブグループに分割されています)。
  • 各測定には、少なくとも4つの接点(各足に1つ)とがあります。
    • 各連絡先は5つの部分に分割され、
    • 接触時間、場所、総力などのいくつかのパラメーターがあります。

alt text

明らかにすべてを1つの大きなオブジェクトに固定しても、それはカットされません。したがって、現在の多数の関数の代わりにクラスを使用する必要があると考えました。しかし、クラスに関するPythonの学習の章を読んだにもかかわらず、それを自分のコードに適用できません( GitHub link

また、すべてのデータを処理するのはかなり奇妙だと感じていますevery情報を取得したい時間です。各Pawの位置がわかれば、これを再度計算する必要はありません。さらに、同じ犬のすべての足を比較して、どの接触がどの足(前/後ろ、左/右)に属するかを判断します。関数のみを使用し続けると、これは混乱になります。

だから今、私は賢明な方法で私のデータ( 1匹の犬のzipされたデータへのリンク )を処理できるようにするクラスを作成する方法に関するアドバイスを探しています。

141
Ivo Flipse

クラスを設計する方法。

  1. 言葉を書き留めます。これを始めました。一部の人々はそうではなく、なぜ彼らに問題があるのか​​疑問に思っています。

  2. 単語のセットを、これらのオブジェクトが何をするかについての簡単なステートメントに展開します。つまり、これらのことについて行うさまざまな計算を書き留めてください。 30匹の犬、24匹の測定値、4匹の連絡先、および連絡先ごとのいくつかの「パラメーター」の短いリストは興味深いですが、ストーリーの一部にすぎません。 「各足の位置」と「同じ犬のすべての足を比較して、どの接触がどの足に属するかを判断する」は、オブジェクト設計の次のステップです。

  3. 名詞に下線を引きます。真剣に。一部の人々はこの価値について議論していますが、初めてのOO開発者にとっては役立つと思います。名詞に下線を引きます。

  4. 名詞を確認します。 「パラメータ」や「測定」などの一般的な名詞は、問題の領域の問題に当てはまる特定の具体的な名詞に置き換える必要があります。詳細は問題の明確化に役立ちます。ジェネリックは単に詳細を排除します。

  5. 各名詞( "contact"、 "Paw"、 "dog"など)について、その名詞の属性と、そのオブジェクトが関与するアクションを書き留めます。これをショートカットしないでください。すべての属性。たとえば、「データセットには30匹の犬が含まれています」が重要です。

  6. 各属性について、これが定義された名詞との関係か、文字列や浮動小数点数などのような他の種類の「プリミティブ」または「アトミック」データか、または還元できないものかを識別します。

  7. アクションまたは操作ごとに、どの名詞が責任を持ち、どの名詞が単に参加するかを識別する必要があります。それは「可変性」の問題です。更新されるオブジェクトと更新されないオブジェクトがあります。可変オブジェクトは、それらの突然変異に対する全責任を負わなければなりません。

  8. この時点で、名詞のクラス定義への変換を開始できます。一部の集合名詞は、リスト、辞書、タプル、セット、または名前付きタプルであり、多くの作業を行う必要はありません。他のクラスは、派生データが複雑であるか、実行される更新/突然変異のために、より複雑です。

Unittestを使用して各クラスを単独でテストすることを忘れないでください。

また、クラスは変更可能でなければならないという法律はありません。たとえば、あなたの場合、変更可能なデータはほとんどありません。持っているのは、ソースデータセットからの変換関数によって作成された派生データです。

430
S.Lott

次のアドバイス(@ S.Lottのアドバイスに似ています)は、本からのものです Beginning Python:Novice to Professional

  1. 問題の説明を書き留めます(問題は何をすべきですか?)。すべての名詞、動詞、形容詞に下線を引きます。

  2. 名詞を調べて、潜在的なクラスを探します。

  3. 動詞を調べて、潜在的な方法を探します。

  4. 形容詞を調べて、潜在的な属性を探します

  5. クラスにメソッドと属性を割り当てます

クラスを改良するために、本は次のことができるとアドバイスしています。

  1. 一連のユースケース-プログラムの使用方法のシナリオを書き留めてください。すべての機能をカバーするようにしてください。

  2. すべてのユースケースを段階的に検討し、必要なものがすべて網羅されていることを確認します。

22
mitchelllc

私はTDDアプローチが好きです...だから、あなたが振る舞いにしたいもののテストを書くことから始めましょう。そして通過するコードを書きます。この時点で、設計についてあまり心配する必要はありません。テストスイートと合格するソフトウェアを取得するだけです。複雑なメソッドを持つ単一の大きないクラスになってしまったとしても心配しないでください。

場合によっては、この初期プロセス中に、テストするのが難しいため、テストするためだけに分解する必要のある動作が見つかることがあります。これは、別のクラスが保証されるというヒントかもしれません。

それから楽しい部分...リファクタリング。動作するソフトウェアを入手したら、複雑な部分を見ることができます。多くの場合、動作の小さなポケットが明らかになり、新しいクラスが提案されますが、そうでない場合は、コードを単純化する方法を探してください。サービスオブジェクトと値オブジェクトを抽出します。メソッドを簡素化します。

Gitを適切に使用している場合(gitを使用しているのではないですか?)、リファクタリング中に特定の分解を非常にすばやく実験し、それを放棄して、物事を単純化しない場合は元に戻すことができます。

最初にテスト済みの作業コードを作成することにより、設計優先アプローチでは簡単に取得できなかった問題の領域に関する詳細な洞察を得ることができます。テストとコードを書くことで、「どこから始めるのか」という麻痺を乗り越えてください。

13
Les Nightingill

OO設計の全体的な考えは、コードを問題にマップすることです。そのため、たとえば、犬の最初の足跡が必要な場合は、次のようにします。

dog.footstep(0)

さて、あなたのケースでは、生データファイルを読み込んで足跡の位置を計算する必要があるかもしれません。これらはすべてfootstep()関数で非表示にできるため、一度しか発生しません。何かのようなもの:

 class Dog:
   def __init__(self):
     self._footsteps=None 
   def footstep(self,n):
     if not self._footsteps:
        self.readInFootsteps(...)
     return self._footsteps[n]

[これは一種のキャッシュパターンです。最初に行って足跡データを読み取り、それ以降はself._footstepsから取得します。]

しかし、はい、OOデザインを正しく取得するのは難しい場合があります。データに対して実行したいことについてもっと考えてください。そうすれば、どのメソッドをどのクラスに適用する必要があるかがわかります。

3
Spacedman

名詞、動詞、形容詞を書き出すことは素晴らしいアプローチですが、クラスデザインは質問をすることと考えるのが好きですどのデータを隠すべきか

QueryオブジェクトとDatabaseオブジェクトがあるとします:

Queryオブジェクトは、クエリの作成と保存に役立ちます。保存は、関数が同じように簡単にクエリを作成するのに役立つため、ここで重要です。たぶんあなたはとどまることができます:Query().select('Country').from_table('User').where('Country == "Brazil"')。構文はまったく関係ありません-それがあなたの仕事です! -キーはオブジェクトがあなたを助けることです何かを隠す、この場合はクエリを保存および出力するために必要なデータです。オブジェクトの力は、それを使用する構文(この場合は巧妙な連鎖)に由来し、オブジェクトを機能させるために格納するものを知る必要はありません。正しく実行された場合、Queryオブジェクトは複数のデータベースのクエリを出力できます。内部的には特定の形式を保存しますが、出力時に他の形式(Postgres、MySQL、MongoDB)に簡単に変換できます。

では、Databaseオブジェクトについて考えてみましょう。これは何を隠して保存しますか?データベースの完全な内容を保存できないのは明らかです。データベースがある理由です!それで、ポイントは何ですか?目標は、データベースの動作を隠すDatabaseオブジェクトを使用する人々からです。良いクラスは、内部状態を操作するときの推論を単純化します。このDatabaseオブジェクトでは、ネットワーク呼び出しの動作を非表示にしたり、クエリや更新をバッチ処理したり、キャッシュレイヤーを提供したりできます。

問題は、このDatabaseオブジェクトが巨大であるということです。データベースへのアクセス方法を表しているため、カバーの下では何でも何でもできます。システムによっては、ネットワーキング、キャッシュ、バッチ処理が明らかに難しいため、それらを非表示にすることは非常に役立ちます。しかし、多くの人が気付くように、データベースは非常に複雑であり、生のDB呼び出しから遠ざかるほど、パフォーマンスを調整し、物事の仕組みを理解することが難しくなります。

これは、OOPの基本的なトレードオフです。適切な抽象化を選択すると、コーディングが簡単になります(文字列、配列、辞書)、大きすぎる抽象化(データベース、EmailManager、NetworkingManager)を選択すると、複雑になりすぎて、どのように機能するか、期待する。目標は複雑さを隠すですが、ある程度の複雑さが必要です。良い経験則は、Managerオブジェクトを避けて開始し、代わりにstructsのようなクラスを作成することです。あなたの人生が楽になります。たとえば、EmailManagerの場合、sendEmailオブジェクトを受け取るEmailという関数で開始します。これは単純な出発点であり、コードは非常に理解しやすいものです。

例として、探しているものを計算するために、どのデータを一緒にする必要があるかを考えてください。たとえば、動物がどこまで歩いているかを知りたい場合は、AnimalStepおよびAnimalTrip(AnimalStepsのコレクション)クラスを使用できます。各Tripにはすべてのステップデータが含まれているので、それについての情報を把握できるはずです。おそらくAnimalTrip.calculateDistance()は理にかなっています。

2
Evan Moran