何も表さないクラス-それは正しいですか?


42

アプリケーションを設計しているだけで、SOLIDとOOPを正しく理解しているかどうかはわかりません。クラスは1つのことを実行し、適切に実行する必要がありますが、一方で、クラスは実際のオブジェクトを表します。

私の場合、データセットで特徴抽出を行い、機械学習分析を行います。私は3つのクラスを作成できると思います

  1. FeatureExtractor
  2. データセット
  3. アナライザ

しかし、FeatureExtractorクラスは何も表さず、クラスというよりもルーチンのようなものにします。使用される関数はextract_features()1つだけです。

一つのことではなく一つのことを表すクラスを作成するのは正しいですか?

編集:それが重要かどうかはわかりませんが、私はPythonを使用しています

そして、extract_features()がそのように見える場合:そのメソッドを保持する特別なクラスを作成する価値はありますか?

def extract_features(df):
    extr = PhrasesExtractor()
    extr.build_vocabulary(df["Text"].tolist())

    sent = SentimentAnalyser()
    sent.load()

    df = add_features(df, extr.features)
    df = mark_features(df, extr.extract_features)
    df = drop_infrequent_features(df)
    df = another_processing1(df)
    df = another_processing2(df)
    df = another_processing3(df)
    df = set_sentiment(df, sent.get_sentiment)
    return df

13
これは関数として完全に問題ありません。モジュールとしてリストした3つの項目を考慮することは問題ありません。異なるファイルに配置することもできますが、それがクラスである必要があるという意味ではありません。
ベルギ

31
Pythonで非OOアプローチを使用することは非常に一般的で許容できることに注意してください。
jpmc26

13
ドメイン駆動設計に興味があるかもしれません。「クラスは実世界のオブジェクトを表す必要があります」は実際にはfalseです... ドメイン内のオブジェクトを表す必要があります。ドメインは多くの場合、実世界に強くリンクされていますが、アプリケーションによっては、オブジェクトと見なされる場合とされない場合があります。
バクリウ

1
OOPに精通すると、クラスが実際のエンティティと1対1で対応することはほとんどないことがわかると思います。例えば、ここでは単一のクラスでは、実世界のエンティティに関連付けられているすべての機能を詰め込むしようと主張しているエッセイは非常に頻繁にアンチパターンであるだ:programmer.97things.oreilly.com/wiki/index.php/...
ケビン-モニカの

1
「それらは、私たちが作業する実際のオブジェクトを表す必要があります。」必ずしも。多くの言語には、バイトのストリームを表すストリームクラスがあります。これは、「実際のオブジェクト」ではなく抽象的な概念です。技術的には、ファイルシステムも「実際のオブジェクト」ではなく、単なる概念ですが、ファイルシステムまたはその一部を表すクラスが存在する場合があります。
ファラプ

回答:


96

クラスは1つのことをし、それをうまくやる必要があります

はい、それは一般的に良いアプローチです。

しかし、他方では、彼らは私たちが扱う実際のオブジェクトを表すべきです。

いいえ、それは私見のよくある誤解です。OOPへの優れた初心者のアクセスは、多くの場合、「現実世界の物を表すオブジェクトから開始する」ことです。

ただし、これで停止しないでください!

クラスは、プログラムをさまざまな方法で構造化するために使用できます(および使用する必要があります)。現実の世界からオブジェクトをモデリングすることは、この側面の1つですが、それだけではありません。特定のタスク用のモジュールまたはコンポーネントを作成することは、クラスのもう1つの実用的な使用例です。「機能抽出」はおそらくそのようなモジュールであり、パブリックメソッドが1つしかextract_features()含まれていない場合でも、プライベートメソッドと共有状態が多く含まれていない場合は驚かされます。したがって、クラスFeatureExtractorを持つことにより、これらのプライベートメソッドの自然な場所が導入されます。

サイドノート:別のモジュールの概念をサポートするPythonのような言語FeatureExtractorでは、これにモジュールを使用することもできますが、この質問の文脈では、これはごくわずかな違いです。

さらに、「特徴抽出機能」は「特徴を抽出する人またはボット」と考えることができます。それは抽象的な「もの」であり、現実の世界で見つかるものではないかもしれませんが、名前自体は有用な抽象化であり、そのクラスの責任が誰であるかという概念を誰にでも与えます。したがって、このクラスは「何も表さない」ことに同意しません。


33
私は特にあなたが内部状態の問題に言及したことが好きです。Pythonで何かをクラスにするのか、関数にするのかを決定する要因になることがよくあります。
デビッドZ

17
「classitis」を推奨しているようです。このためのクラスを作成しないでください。これはJavaスタイルのアンチパターンです。関数の場合は、関数にします。内部状態は無関係です。関数はそれを(クロージャを介して)持つことができます。
コンラッドルドルフ

25
@KonradRudolph:これは、1つの関数を作成することではないということを忘れているようです。これは、いくつかの機能、共通名、およびおそらく共有状態を必要とするコードについてです。これにモジュールを使用することは、クラスとは別にモジュールの概念を持つ言語では賢明かもしれません。
Doc Brown

8
@DocBrown私は反対する必要があります。これは、1つのパブリックメソッドを持つクラスです。APIに関しては、それは関数と見分けがつきません。詳細については私の答えをご覧ください。状態を表す特定のを作成する必要がある場合は、単一メソッドクラスを使用することを正当化できます。しかし、これはここでは当てはまりません(それでも、関数を使用できる場合があります)。
コンラッドルドルフ

6
@DocBrown私はあなたが何を意味するのかを見始めています:あなたは内部から呼び出される関数について話しているのextract_featuresですか?私は、それらが別の場所からの公的な機能であるとちょうど思いました。十分に公平です。これらがプライベートである場合、おそらく、(状態を共有しない限り、クラスではなく)モジュールに入る必要があることに同意しextract_featuresます。(それはもちろん、その関数内でローカルに宣言することもできます。)
コンラッドルドルフ

44

Doc Brownはスポットオンです。クラスは実際のオブジェクトを表す必要はありません。それらは有用である必要があります。クラスは基本的に、単に追加のタイプであり、どのような行いintstring現実世界に対応しますか?それらは具体的な具体的なものではなく、抽象的な説明です。

とはいえ、あなたのケースは特別です。あなたの説明によると:

そして、extract_features()がそのように見える場合:そのメソッドを保持する特別なクラスを作成する価値はありますか?

あなたは絶対に正しい:あなたのコードが示されているようであれば、それをクラスにすることは意味がありません。Pythonでのそのようなクラスの使用はコードのにおいであり、単純な関数で十分な場合が多いと主張する有名な話があります。あなたのケースはこれの完璧な例です。

クラスの過剰使用は、1990年代にJavaでOOPが主流になったためです。残念ながら、当時のJavaにはいくつかの最新の言語機能(クロージャなど)が欠けていたため、クラスを使用しないと多くの概念を表現するのが困難または不可能でした。たとえば、Javaでは最近まで状態を保持するメソッド(クロージャ)を持つことは不可能でした。代わりに、状態を保持するクラスを作成し、単一のメソッド(のようなものと呼ばれるinvoke)を公​​開する必要がありました。

残念ながら、このスタイルのプログラミングは、Javaをはるかに超えて(そのほかの方法で非常に役立つ影響力のあるソフトウェアエンジニアリングの本の一部が原因で)、そのような回避策を必要としない言語でも普及しまし

Pythonでは、クラスは明らかに非常に重要なツールであり、自由に使用する必要があります。しかし、それらは唯一のツールではなく、意味をなさない場所で使用する理由はありません。自由関数はOOPに場所がないという一般的な誤解です。


例で呼び出される関数が実際にプライベート関数である場合、それらをモジュールまたはクラスにカプセル化することが完全に適切であることを追加すると便利です。そうでなければ、私は完全に同意します。
レリエル

11
「OOP」は「クラスの束を書く」という意味ではないことを覚えておくと便利です。関数はPythonのオブジェクトなので、「OOPではない」と考える必要はありません。むしろ、それは単に組み込み型/クラスを再利用するだけであり、「再利用」はプログラミングの聖杯の1つです。これをクラスにラップすると、再利用が妨げられます。この新しいAPIと互換性のあるものはないため__call__です(定義されていない限り、関数を使用するだけです!)
Warbo

3
また、「自立機能対クラス」をテーマとした:eev.ee/blog/2013/03/03/...
Joker_vD

1
Pythonの「自由関数」はメソッドを公開する型を持つオブジェクトでもありますが、そうではあり__call__()ませんか?これは、匿名の内部クラスインスタンスとは本当に違いますか?構文的には確かですが、言語設計からは、ここで紹介するものほど重要ではないようです。
ジミージェームズ

1
@JimmyJamesそうです。全体のポイントは、特定の目的のために同じ機能を提供しますが、より使いやすいということです。
コンラッドルドルフ

36

アプリケーションを設計しているだけで、SOLIDとOOPを正しく理解しているかどうかはわかりません。

これに20年以上も関わっていますが、どちらもわかりません。

クラスは1つのことをし、それをうまくやる必要があります

ここで間違って行くのは難しい。

それらは私たちが扱う実際のオブジェクトを表す必要があります。

まあ、本当に?これまでで最も人気があり成功を収めているクラスを紹介しますString。テキストに使用します。そして、それが表す実世界のオブジェクトはこれです:

漁師は、ひもの束から吊り下げられた10匹の魚

いいえ、すべてのプログラマーが釣りに夢中になっているわけではありません。ここでは、メタファーと呼ばれるものを使用しています。実際には存在しないもののモデルを作成しても構いません。それは明確でなければならない考えです。あなたは読者の心の中で画像を作成しています。これらの画像は本物である必要はありません。簡単に理解できました。

優れたOOP設計では、データ(状態)を中心にメッセージ(メソッド)がクラスター化されるため、それらのメッセージに対する反応はそのデータに応じて変化します。それを行うと、現実世界の何かがモデル化されます。そうでなければ、まあ。読者にとって意味がある限り、それは問題ありません。

確かに、次のように考えることができます。

文字列から中断されたお祝いの手紙は「LETS DO THINGS!」と読みました。

しかし、メタファーを利用する前にこれが現実の世界に存在しなければならないと思うなら、あなたのプログラミングのキャリアは多くの芸術と工芸を伴うでしょう。


1
絵のきれいな文字列...
ZEVスピッツ

この時点で、「文字列」が比meta的なものであるかどうかはわかりません。classand tablecolumn... などの単語のように、プログラミングの分野では特定の意味を持っています。
Kyralessa

@Kyralessaは、初心者に比phorを教えたり、魔法をかけたりします。魔法を信じるコーダーから私を救ってください。
candied_orange

6

気をつけて!SOLIDは、クラスが「1つのことだけを行う」べきだとは言いません。その場合、クラスにはメソッドが1つしかなく、クラスと関数の間に実際の違いはありません。

SOLIDは、クラスは単一の責任を表すべきだと言います。これらは、チームの人の責任のようなものです。ドライバー、弁護士、スリ、グラフィックデザイナーなど。これらの各人は、複数の(関連する)タスクを実行できますが、すべて単一の責任に関係します。

これのポイントは-要件に変更がある場合、理想的には単一のクラスを変更するだけです。これにより、コードが理解しやすくなり、変更が容易になり、リスクが軽減されます。

オブジェクトが「本物」を表すという規則はありません。OOは当初シミュレーションで使用するために考案されたため、これは単なる貨物カルトの伝説です。ただし、プログラムはシミュレーションではないため(最近のOOアプリケーションはほとんどありません)、このルールは適用されません。各クラスに明確に定義された責任がある限り、大丈夫です。

クラスは、実際には、単一のメソッドを持っている場合と、クラスがどのような状態を持っていない、あなたはそれスタンドアロン機能することを検討できます。これは確かに問題なく、KISSおよびYAGNIの原則に従います。関数で解決できる場合はクラスを作成する必要はありません。一方、内部状態または複数の実装が必要になると思われる理由がある場合は、事前にクラスにすることもできます。ここで最善の判断をする必要があります。


「関数で解決できる場合はクラスを作成する必要はありません」の場合は+1。時々、誰かが真実を話す必要があります。
tchrist

5

一つのことではなく一つのことを表すクラスを作成するのは正しいですか?

一般にそれは問題ありません。

FeatureExtractorクラスが何をすべきかをもう少し具体的な説明がなければ、それを伝えるのは困難です。

とにかく、FeatureExtractorがパブリックextract_features()関数のみを公開している場合でも、Strategyクラスを使用して構成することを考えて、抽出を正確に行う方法を決定します。

別の例は、テンプレート関数を持つクラスです。

そして、もっとある行動デザインパターンクラスのモデルに基づいています、。


明確化のためにコードをいくつか追加しました。

そして、extract_features()がそのように見える場合:そのメソッドを保持する特別なクラスを作成する価値はありますか?

この線

 sent = SentimentAnalyser()

クラスをStrategyで構成できるという意味です。

そのSentimentAnalyserクラスのインターフェイスがある場合FeatureExtractorは、関数の特定の実装に直接結合する代わりに、その構築点でクラスに渡すことができます。


2
FeatureExtractorさらに複雑さ(SentimentAnalyserクラスのインターフェイス)を導入するためだけに、複雑さ(クラス)を追加する理由がわかりません。デカップリングが望ましい場合extract_featuresget_sentiment関数は引数として使用できます(load呼び出しは関数とは無関係で、その効果のためにのみ呼び出されるようです)。また、Pythonにはインターフェースがありません。
ウォーボ

1
@warbo-関数を引数として指定しても、関数にすることで、関数の形式に適合する実装を制限しますが、1つの呼び出しと次の呼び出しの間で永続的な状態を管理する必要がある場合次に(a CacheingFeatureExtractorまたはa TimeSeriesDependentFeatureExtractor)、オブジェクトがはるかによく適合します。現在オブジェクトが必要ないからといって、決して存在しないというわけではありません。
ジュール

3
@Julesまずあなたはそれを必要としません(YAGNI)、2番目にPython関数は必要な場合は永続状態(クロージャ)を参照できます(あなたはそうしません)、3番目に関数を使用しても何も制限されません__call__メソッドは、必要に応じて互換性があります(あなたはしません)、第4に、FeatureExtractorコードをこれまでに記述された他のすべてのコードと互換性がないようにラッパーを追加することにより(__call__メソッドを提供しない限り、関数は明らかに単純になります) )
Warbo

0

パターンとすべての派手な言語/概念は別として、あなたが偶然見つけたのはJobまたはBatch Processです。

結局のところ、純粋なOOPプログラムでさえ、実際に作業を実行するために何らかの形で駆動される必要があります。何らかの方法でエントリポイントが必要です。たとえば、MVCパターンでは、「C」コントローラーはGUIからクリックなどのイベントを受け取り、他のコンポーネントを調整します。従来のコマンドラインツールでは、「メイン」機能でも同じことができます。

一つのことではなく一つのことを表すクラスを作成するのは正しいですか?

あなたのクラスは、エンティティを表してい何か他のものと指揮すべてを。ControllerJobMainなど、思いついた名前を付けることができます。

そして、extract_features()がそのように見える場合:そのメソッドを保持する特別なクラスを作成する価値はありますか?

それは状況に依存します(これがPythonで行われる通常の方法に慣れていません)。これが小さなワンショットコマンドラインツールである場合は、クラスではなくメソッドで問題ありません。あなたのプログラムの最初のバージョンは確かにメソッドで逃げることができます。後で、もしグローバル変数が混在していても、そのようなメソッドがたくさんあることに気付いたら、クラスにリファクタリングする時が来ました。


3
このようなシナリオでは、スタンドアロンプ​​ロシージャ「メソッド」の呼び出しが混乱する可能性があることに注意してください。Pythonを含むほとんどの言語は、それらを「関数」と呼びます。「メソッド」は、クラスの特定のインスタンスにバインドされる関数/手順であり、用語の使用法の反対です:)
Warbo

本当です、@ Warbo。または、procedure、defun、sub、または...と呼ぶこともできます。また、インスタンスに関連しないクラスメソッド(sic)である場合もあります。穏やかな読者が意図した意味を抽象化できることを願っています。:)
AnoE

@Warbo知っておくと良い!私が出会ったほとんどの学習資料では、関数とメソッドという用語は互換性があり、言語に依存する好みに過ぎないと述べています。
ドム

@Dom 一般的に、「(純粋な)」「関数」は、入力値から出力値へのマッピングです。「手順」は、効果(ファイルの削除など)を引き起こす可能性がある関数です。両方とも静的にディスパッチされます(つまり、字句スコープで検索されます)。「メソッド」とは、値(「オブジェクト」と呼ばれる)から動的にディスパッチ(ルックアップ)される関数または(通常)プロシージャであり、メソッドの(暗黙的thisまたは明示的なself)引数に自動的にバインドされます。オブジェクトのメソッドは相互に「オープンに」再帰的であるため、置換するfooとすべてのself.foo呼び出しでこの置換が使用されます。
ウォーボ

0

OOP は、システムの動作をモデル化するものと考えることができます。システム「現実世界」に存在する必要はないことに注意してください。ただし、現実世界のメタファーが役立つ場合があります(「パイプライン」、「工場」など)。

目的のシステムが複雑すぎて一度にモデル化できない場合は、それを小さなピースに分割して、それらをモデル化することができます(「問題ドメイン」)。 (多かれ少なかれ)数値、文字列、リストなどの組み込み言語オブジェクトのものです。

これらの単純なピースを作成したら、それらを組み合わせて大きなピースの動作を記述し、さらに大きなピースに組み合わせて、全体に必要なドメインのすべてのコンポーネントを記述することができます。システム。

いくつかのクラスを作成するのは、この「結合」フェーズです。希望どおりに動作する既存のオブジェクトが存在しない場合、クラスを作成します。たとえば、ドメインには「foos」、「bars」と呼ばれるfoosのコレクション、「bazs」と呼ばれるバーのコレクションが含まれる場合があります。fooは文字列でモデル化するのに十分単純であることに気付くかもしれませんので、そうします。バーは、Pythonが提供するものとは一致しない特定の制約に従うためにバーのコンテンツを必要とすることがわかります。その場合、この制約を強制する新しいクラスを記述することができます。おそらく、bazにはそのような特性がないため、リストで表すことができます。

これらのコンポーネント(foo、bar、baz)のすべてに対して新しいクラスを作成できることに注意してください。ただし、既に正しい動作をしているものがある場合は必要ありません。特に、クラスを有効にするには、何か(データ、メソッド、定数、サブクラスなど)を「提供」する必要があります。そのため、カスタムクラスのレイヤーが多数ある場合でも、何らかの組み込み機能を使用する必要があります。たとえば、foosの新しいクラスを作成した場合、おそらく文字列が含まれているだけなので、fooクラスを忘れて、代わりにこれらの文字列をbarクラスに含めるのはなぜですか?クラスも組み込みオブジェクトであり、特に柔軟なものであることに注意してください。

ドメインモデルを取得したら、それらの部分の特定のインスタンスを取得し、モデル化する特定のシステムの「シミュレーション」に配置できます(「...のための機械学習システム」など)。

このシミュレーションができたら、それを実行します。実際に...(またはモデリングしているものは何でも)の機械学習システム(のシミュレーション)があります。


今、あなたの特定の状況では、「機能抽出」コンポーネントの動作をモデル化しようとしています。問題は、「機能抽出機能」のように動作する組み込みオブジェクトがありますか、それともより単純なものに分割する必要がありますか?機能抽出プログラムは関数オブジェクトと非常によく似ているように見えるので、モデルとしてそれらを使用しても問題ないと思います。


これらの種類の概念について学習する際に留意すべきことの1つは、さまざまな言語がさまざまな組み込みの機能とオブジェクトを提供できることです(そしてもちろん、「オブジェクト」などの用語を使用しないものもあります!)。したがって、ある言語で意味のあるソリューションは、別の言語ではあまり役に立たない可能性があります(これは同じ言語の異なるバージョンにも当てはまります!)。

歴史的に、OOPの文献(特に「デザインパターン」)の多くは、Pythonとはまったく異なるJavaに焦点を当ててきました。たとえば、Javaクラスはオブジェクトではありません。Javaにはごく最近まで関数オブジェクトがありませんでした。Javaには厳密な型チェックがあり(インターフェイスとサブクラス化を促進します) floats / etc。オブジェクトではなく、Javaのメタプログラミング/イントロスペクションには「リフレクション」などが必要です。

私はJavaを選択しようとはしていません(別の例として、OOP理論の多くはSmalltalkを中心に展開していますが、これもPythonとはまったく異なります)、コンテキストについて非常に慎重に考えなければならないことを指摘しようとしていますソリューションが開発された制約、およびそれが現在の状況と一致するかどうか。

あなたの場合、関数オブジェクトは良い選択のようです。なぜ「ベストプラクティス」のガイドラインで関数オブジェクトが可能な解決策として言及されていないのか疑問に思っているなら、それは単にそれらのガイドラインがJavaの古いバージョンのために書かれたからかもしれません!


0

実用的には、「何か重要なことをし、分離すべきその他のこと」があり、明確なホームがない場合、それをUtilitiesセクションに入れ、それを命名規則として使用します。すなわち。FeatureExtractionUtility

クラス内のメソッドの数は忘れてください。今日の単一のメソッドは、明日には5つのメソッドに成長する必要があるかもしれません。重要なのは、さまざまな機能を収集するためのユーティリティエリアなど、明確で一貫性のある組織構造です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.