かなりの時間、私は静的クラスの代わりにオブジェクトを持つ理由を考えることができません。オブジェクトは私が思うよりも多くの利点がありますか?[閉まっている]


9

私はオブジェクトの概念を理解しており、Javaプログラマーとして、OOパラダイムは実際にはかなり自然に生まれてくると感じています。

しかし最近、私は自分自身が考えていることに気づきました。

ちょっと待ってください、実際に静的クラスを使用するよりもオブジェクトを使用することの実際的な利点は何ですか(適切なカプセル化とOOプラクティスで)。

オブジェクトを使用することの2つの利点を考えることができます(どちらも重要で強力です)。

  1. ポリモーフィズム:実行時に動的かつ柔軟に機能を交換できます。また、システムに新しい機能「パーツ」と代替手段を簡単に追加することもできます。たとえばCarEngineオブジェクトを操作するように設計されたクラスがあり、Carが使用できるシステムに新しいエンジンを追加したい場合、新しいEngineサブクラスを作成して、 このクラスのオブジェクトをオブジェクトに渡すだけで済み Carます。について何かを変更しCarます。そして、実行時にそうすることを決定できます。

  2. 「機能を渡す」ことができる:システムの周りにオブジェクトを動的に渡すことができます。

しかし、静的クラスよりもオブジェクトに他の利点はありますか?

多くの場合、新しい「パーツ」をシステムに追加するときは、新しいクラスを作成し、そこからオブジェクトをインスタンス化します。

しかし、最近立ち止まって考えたところ、静的クラスは、オブジェクトを通常使用する多くの場所で、オブジェクトとまったく同じように機能することに気付きました。

たとえば、アプリにファイルの保存/読み込みメカニズムを追加する作業をしています。

オブジェクトを使用すると、コードの呼び出し行は次のようになります。 Thing thing = fileLoader.load(file);

静的クラスを使用すると、次のようになります。 Thing thing = FileLoader.load(file);

違いは何ですか?

かなり古いことですが、私は、昔ながらの静的クラスがまったく同じように動作するときに、オブジェクトをインスタンス化する理由を考えることができません。しかし、OOシステムでは、静的クラスはほとんどありません。だから私は何かを逃しているに違いない。

リストした2つのオブジェクト以外のオブジェクトには、他に利点がありますか?説明してください。

編集:明確にします。機能を交換したり、データを渡したりするときに、オブジェクトは非常に便利です。たとえば、メロディーを構成するアプリを作成しました。MelodyGenerator異なる方法でメロディーを作成するいくつかのサブクラスがあり、これらのクラスのオブジェクトは交換可能でした(Strategyパターン)。

メロディーもオブジェクトを渡すことができたので便利です。コードとスケールもそうだった。

しかし、システムの「静的な」部分についてはどうですか?それは渡されません。たとえば、「ファイルを保存する」メカニズムです。静的クラスではなくオブジェクトに実装する必要があるのはなぜですか?


それで、フィールドとおそらくメソッドを持つオブジェクトの代わりに、オブジェクトもレコードもなく、静的メソッドに渡されたり静的メソッドから返されたりするスカラー値の全量だけですか?編集:あなたの新しい例はそうではないことを示唆します:インスタンスメソッドなしのオブジェクト?そうでなければ、何Thingですか?

FileLoaderソケットから読み取るものと交換する必要がある場合はどうなりますか?またはテスト用のモック?または、zipファイルを開くものですか?
ベンジャミンホジソン2014年


1
@delnanわかりました答えが理解に役立つ質問を見つけました。なぜシステムの「静的」部分をオブジェクトとして実装するのですか?問題の例のように:ファイル保存メカニズム。オブジェクトに実装することで何が得られますか?
アビブコーン

1
デフォルトでは、非静的オブジェクトを使用する必要があります。静的クラスを使用するのは、意図が本当によく表現されていると思われる場合のみにしてください。System.Math.NETは、静的クラスとしてより意味のあるものの例です。それをスワップアウトしたり、模擬したりする必要はなく、論理的にインスタンスの一部となる操作はありません。あなたの「節約」の例がその法則に合うとは本当に思えません。
ベンジャミンホジソン2014年

回答:


14

LOLあなたは私が働いていたチームのように聞こえます;)

Java(そしておそらくC#)は確かにそのプログラミングスタイルをサポートしています。そして、私は最初の本能が「静的な方法にすることができます!」しかし、時間とともにあなたに追いつくいくつかの微妙なコストがあります。

1)Javaはオブジェクト指向言語です。そして、それは機能的な男のナッツを駆り立てますが、実際にはかなりうまくいきます。OOの背後にある考えは、機能を状態にバンドルして、状態を非表示にし、そのコンテキストで意味のある機能のみを公開することで、セマンティクスを保持する小さなデータ単位と機能を持たせることです。

静的メソッドのみを含むクラスに移動することで、方程式の「状態」の部分を壊すことになります。しかし、国家はまだどこかに住んでいる必要があります。つまり、すべての静的メソッドを持つクラスは、状態がクラスから関数呼び出しに移行しているため、ますます複雑なパラメーターリストを持ち始めるということです。

すべての静的メソッドを含むクラスを作成した後、実行して、それらのメソッドのうちいくつが単一の共通パラメーターを持つかを調べます。そのパラメーターは、それらの関数を含むクラスであるか、またはパラメーターがインスタンスの属性である必要があるというヒントです。

2)OOのルールはかなりよく理解されています。しばらくすると、クラスの設計を見て、SOLIDなどの基準を満たしているかどうかを確認できます。そして、たくさんの練習用単体テストの後、クラスを「適切なサイズ」と「一貫性」にするものについての良い感覚を養います。しかし、すべての静的メソッドを持つクラスに適切なルールはなく、そこにすべてをバンドルするべきではないという本当の理由はありません。クラスはエディターで開いているので、一体何ですか?そこに新しいメソッドを追加するだけです。しばらくすると、アプリケーションは多数の競合する「神のオブジェクト」に変わり、それぞれが世界を支配しようとします。繰り返しになりますが、それらを小さな単位にリファクタリングすることは非常に主観的であり、正しいかどうかを判断するのは困難です。

3)インターフェースは、Javaの最も強力な機能の1つです。クラスの継承は問題があることが証明されていますが、インターフェイスを使用したプログラミングは、言語の最も強力なトリックの1つです。(同上C#)すべて静的なクラスは、そのモデルに自分自身を入れることはできません。

4)それは、あなたが利用できない重要なオブジェクト指向技術にドアをバタンと閉める。したがって、ドライバーを使っていたとしたら、どれほど簡単であったかを理解せずに、ツールボックスにハンマーだけで何年も作業することができます。

4.5)可能な限り最も難しく、壊れないコンパイル時の依存関係を作成します。したがって、たとえば、実行している場合FileSystem.saveFile()、JVMを実行時に偽造することを除いて、それを変更する方法はありません。つまり、静的関数クラスを参照するすべてのクラスには、その特定の実装に対するコンパイル時の依存性があり、拡張がほとんど不可能になり、テストが非常に複雑になります。静的クラスを分離してテストできますが、そのクラスを参照するクラスを分離してテストすることは非常に困難になります。

5)同僚を狂わせます。私が一緒に働くほとんどの専門家は、コードを真剣に受け止め、少なくともある程度の設計原則に注意を払います。言語のコアインテントを脇に置いておくと、コードを常にリファクタリングするため、髪の毛を抜くことができます。

言語を使用しているときは、常に言語を上手に使うようにしています。したがって、たとえば、Javaを使用しているときは、優れたOO設計を使用します。これは、その言語を実際に利用して何をするかです。私がPythonを使用しているときは、モジュールレベルの関数とときどきクラスを混在させます。クラスはPythonでしか記述できませんでしたが、その言語を使って得意とすることはないと思います。

別の戦術は、言語をひどく使用することであり、彼らはそれが引き起こすすべての問題について文句を言う。しかし、それはほとんどすべてのテクノロジーに当てはまります。

Javaの重要な機能は、理解しやすいように一緒にぶら下がっている小さなテスト可能なユニットで複雑さを管理することです。Javaは、実装に関係なく明確なインターフェース定義を強調します-これは大きな利点です。そのため、それ(および他の同様のオブジェクト指向言語)は非常に広く使用され続けています。すべての冗長性と儀式性について、大きなJavaアプリで作業を終えたときは、アイデアがコード内でより動的な言語でのプロジェクトよりも明確に分離されているように感じます。

しかし、それは難しい問題です。私は人々が「すべて静的な」バグに遭遇するのを見てきました。そして、それから彼らを話すのはちょっと難しいです。しかし、彼らがそれを乗り越えたとき、彼らは大きな安心感を見た。


主に、物事を小さくモジュール化し、状態と機能を一緒に保つことについて話します。静的クラスでこれを行うことを妨げるものは何もありません。彼らは状態を持つことができます。そしてそれらをゲッター・セッターでカプセル化することができます。また、機能と状態をカプセル化する小さなクラスにシステムを分割できます。これはすべて、クラスとオブジェクトの両方に当てはまります。これがまさに私のジレンマです。アプリに新しい機能を追加しているとしましょう。もちろん、SRPに従うために別のクラスに入ります。しかし、なぜこのクラスをインスタンス化する必要があるのでしょうか。これは私には理解できません。
Aviv Cohn

つまり、なぜオブジェクトが必要なのかが明確になることがあります。編集から私の質問までの例を使用します。メロディーを構成するアプリがありました。MelodyGeneratorsに接続してさまざまなメロディーを生成できるさまざまなスケールがありました。そして、メロディーはオブジェクトだったので、それらをスタックに入れて、古いものを再生するなどすることができました。このような場合、なぜオブジェクトを使用する必要があるのか​​は明らかです。私が理解していないのは、オブジェクトを使用してシステムの「静的」部分を表す必要がある理由です。たとえば、ファイル保存メカニズムなどです。なぜこれは単なる静的クラスであるべきではないのですか?
アビブコーン

静的メソッドを持つクラスは、その状態を外部化する必要があります。これは、時には、非常に重要な因数分解手法です。しかし、ほとんどの場合、状態をカプセル化します。具体的な例:数年前、同僚がJavaScriptで「日付ピッカー」を作成しました。そしてそれは悪夢だった。顧客は絶えずそれについて不平を言いました。そのため、私はそれを「カレンダー」オブジェクトにリファクタリングし、突然、それらの複数をインスタンス化し、それらを並べて配置したり、月と年の間でジャンプしたりしました。インスタンス化はあなたにそのスケールを与えます。
Rob

3
「オブジェクト指向の背後にある考え方は、機能を状態にバンドルして、データの小さな単位と、そのコンテキストで意味のある機能だけを公開して状態を非表示にすることでセマンティクスを維持する機能を持たせることです。」これは根本的に間違っています。オブジェクト指向は変更可能な状態を意味せず、抽象化はオブジェクト指向に限定されません。抽象化を実現するには2つの方法があり、オブジェクトはそれらの1つにすぎません(もう1つは抽象データ型です)。
ドヴァル2014年

1
@Doval OOのすべての議論が、必ずしも関数型プログラミングの議論にならなければならない理由がわかりません。
Rob

6

あなたは尋ねました:

しかし、静的クラスよりもオブジェクトに他の利点はありますか?

この質問の前に、オブジェクトを使用する2つの利点として、ポリモーフィズム受け継がれいることを挙げました。これらがオブジェクト指向パラダイムの特徴であると言いたいです。カプセル化は、OOパラダイムのもう1つの機能です。

ただし、これらはメリットではありません。利点は次のとおりです。

  1. より良い抽象化
  2. メンテナンス性の向上
  3. テスト性の向上

あなたが言った:

しかし、最近立ち止まって考えたところ、静的クラスは、オブジェクトを通常使用する多くの場所で、オブジェクトとまったく同じように機能することに気付きました。

そこには正当なポイントがあると思います。基本的に、プログラミングはデータの変換とデータに基づく副作用の作成にすぎません。時々、データの変換には補助データが必要です。それ以外の場合は、そうではありません。

変換の最初のカテゴリを処理する場合、補助データを入力として渡すか、どこかに保存する必要があります。オブジェクトは、そのような変換のための静的クラスよりも優れたアプローチです。オブジェクトは補助データを保存し、適切なタイミングで使用できます。

変換の2番目のカテゴリの場合、静的クラスは、オブジェクトと同じかそれよりも優れています。数学関数は、このカテゴリの典型的な例です。ほとんどの標準Cライブラリ関数もこのカテゴリに分類されます。

あなたは尋ねました:

オブジェクトを使用すると、コードの呼び出し行は次のようになります。 Thing thing = fileLoader.load(file);

静的クラスを使用すると、次のようになります。 Thing thing = FileLoader.load(file);

違いは何ですか?

場合はFileLoaderない、今まで、私は第二のアプローチとなるだろう、任意のデータを格納する必要があります。操作を実行するために補助データが必要になる可能性が少しでもある場合、最初のアプローチはより安全な方法です。

あなたは尋ねました:

リストした2つのオブジェクト以外のオブジェクトには、他に利点がありますか?説明してください。

OOパラダイムを使用する利点(利点)をリストしました。彼らが自明であることを望みます。そうでない場合は、詳しく説明させていただきます。

あなたは尋ねました:

しかし、システムの「静的な」部分についてはどうですか?それは渡されません。たとえば、「ファイルを保存する」メカニズムです。静的クラスではなくオブジェクトに実装する必要があるのはなぜですか?

これは、静的クラスが単純に実行しない例です。アプリケーションデータをファイルに保存するには多くの方法があります。

  1. CSVファイルに保存します。
  2. XMLファイルに保存します。
  3. それをjsonファイルに保存します。
  4. データを直接ダンプして、バイナリファイルとして保存します。
  5. データベースのいくつかのテーブルに直接保存します。

そのようなオプションを提供する唯一の方法は、インターフェースを作成し、インターフェースを実装するオブジェクトを作成することです。

結論として

目前の問題に適切なアプローチを使用します。あるアプローチと他のアプローチについては信心深くない方が良いでしょう。


多くの異なるオブジェクトタイプ(クラス)の必要性を保存する場合にも、(例えばMelodyChordImprovisationsSoundSampleなど)、抽象化を経由して、実装を簡素化するために、経済的になります。
rwong

5

時々、それは言語と文脈に依存します。たとえば、PHPリクエストのサービスに使用されるスクリプトには、サービスのリクエストと応答の1つが常に含まれているため、スクリプトの有効期間全体を通じて生成される静的メソッドがあるため、リクエストを処理してレスポンスを生成する静的メソッドが適切な場合があります。しかし、で作成されたサーバーではNode.js、多くの異なる要求と応答が同時に発生する場合があります。したがって、最初の質問は-静的クラスが本当にシングルトンオブジェクトに対応していることを確信していますか?

次に、シングルトンがある場合でも、オブジェクトを使用すると、Dependency_injectionFactory_method_patternなどの手法を使用してポリモーフィズムを利用できます。これは多くの場合、さまざまなInversion_of_controlパターンで使用され、テストやロギングなどのためのモックオブジェクトの作成に役立ちます。

上記の利点を説明したので、ここでは説明しませんでした:継承。多くの言語では、インスタンスメソッドをオーバーライドできるのと同じ方法で静的メソッドをオーバーライドできません。一般に、静的メソッドを継承してオーバーライドすることははるかに困難です。


回答ありがとうございます。あなたの意見では、システムの「静的な」部分を作成するとき、「渡されない」もの-たとえば、サウンドを再生するシステムの部分、またはデータをファイルに保存するシステムの部分:オブジェクトまたは静的クラスに実装する必要がありますか?
アビブコーン

3

Rob Yの投稿に加えて


の機能が、load(File file)使用する他のすべての関数から明確に分離できる限り、静的メソッド/クラスを使用しても問題ありません。状態を外部化します(悪いことではありません)。また、関数を部分的に適用したりカリー化したりできるため、繰り返しを行う必要がないため、冗長性も得られません。(実際には、factoryパターンの使用と同じまたは類似しています)

ただし、これらの関数のうち2つが一般的に使用され始めたら、何らかの方法でそれらをグループ化できるようにする必要があります。あなたはload機能だけでなく機能も持っていると想像してくださいhasValidSyntax。あなたは何をしますか?

     if (FileLoader.hasValidSyntax(myfile))
          Thing thing = FileLoader.load(myfile);
     else
          println "oh noes!"

myfileここへの2つの参照を参照してください。呼び出しごとに外部化された状態を渡す必要があるため、繰り返し始めます。Rob Yは、次のように状態を内部化する方法(ここではfile)を説明しました。

     FileLoader myfileLoader = new FileLoader(myfile)
     if (myfileLoader.hasValidSyntax())
          Thing thing = myfileLoader.load();
     else
          println "oh noes!"

同じオブジェクトを2回渡さなければならないのは、表面的な症状です。それが示す可能性のある実際の問題は、一部の作業が重複して実行されることです- 状態(部分的に解析されたファイルの結果)の再利用が許可されていない場合、ファイルを2度開き、2度解析し、2度閉じる必要がhasValidSyntax()ありload()ます。
rwong

2

静的クラスを使用して大きな問題は、彼らがいることで強制あなたの依存関係を隠すために、あなたを、そして強制的な実装に依存することができます。次のコンストラクター署名では、依存関係は何ですか?

public Person(String name)

さて、署名から判断すると、人は名前が必要なだけですよね?まあ、実装が次の場合はそうではありません:

public Person(String name) {
    ResultSet rs = DBConnection.getPersonFilePathByName(name);
    File f = FileLoader.load(rs.getPath());
    PersonData.setDataFile(f);
    this.name = name;
    this.age = PersonData.getAge();
}

つまり、人はインスタンス化されるだけではありません。実際にデータベースからデータをプルします。これにより、ファイルへのパスが得られます。このファイルを解析する必要があります。次に、必要な実際のデータを抽出する必要があります。この例は明らかに上にありますが、それは要点を証明しています。しかし、待ってください。もっとたくさんあります!私は次のテストを書きます:

public void testPersonConstructor() {
    Person p = new Person("Milhouse van Houten");
    assertEqual(10, p.age);
}

これはパスするはずですよね?つまり、他のすべてのものをカプセル化しましたよね?さて、実際にはそれは例外を除いて爆破します。どうして?ああ、はい、私たちが知らなかった隠された依存関係はグローバルな状態を持っています。DBConnectionニーズが初期化され、接続されています。FileLoaderで初期化ニーズFileFormat(のようなオブジェクトXMLFileFormatまたはCSVFileFormat)。それらについて聞いたことがありませんか?まあ、それがポイントです。静的呼び出しはこれらの依存関係を隠すため、コード(およびコンパイラー)はこれらが必要であることを通知できません。テストって言った?新しいジュニア開発者があなたの最新のリリースでこのようなものを出荷したことを意味しました。結局のところ、コンパイル済み=機能しますよね?

さらに、MySQLインスタンスが実行されていないシステムを使用しているとします。または、Windowsシステムを使用しているが、DBConnectionクラスはLinuxボックス上のLinuxサーバー(Linuxパスを使用)のMySQLサーバーにのみ送信されるとします。または、によって返されるパスDBConnectionが読み取り/書き込みではないシステムを使用しているとしましょう。つまり、これらの条件のいずれかでこのシステムを実行またはテストしようとすると、コードエラーが原因ではなく、コードの柔軟性を制限し、実装に結びつく設計エラーが原因で失敗します。

ここでPerson、特定の問題のあるパスを通過する1 つの特定のインスタンスについて、データベースへのすべての呼び出しをログに記録するとします。にログインすることもできDBConnectionますが、これによりすべてがログに記録され、多くの混乱が生じ、追跡する特定のコードパスを区別するのが難しくなります。ただし、DBConnectionインスタンスで依存性注入を使用している場合は、インターフェイスをデコレータに実装するだけです(またはオブジェクトで両方のオプションを使用できるため、クラスを拡張できます)。静的クラスでは、依存関係を注入したり、インターフェースを実装したり、デコレーターでラップしたり、クラスを拡張したりできません。直接呼び出すことしかできず、コードのどこかに隠れています。したがって、私たちは強制されます 実装への隠された依存関係を持ちます。

これは常に悪いですか?必ずしもそうとは限りませんが、見方を逆にして、「これインスタンスであってはならない正当な理由はありますか?」と言う方が良いでしょう。「これインスタンスであるべき正当な理由はありますか?」ではなく コードのMath.abs()実装と使用方法の両方において、コードがと同じように揺るぎない(そしてステートレスである)と本当に言える場合は、静的にすることを検討できます。ただし、静的クラスを介してインスタンスを作成することで、事後の再取得が必ずしも容易ではない柔軟性の世界が得られます。また、コードの依存関係の本質をより明確にすることができます。


非表示の依存関係は非常に良い点です。「コードの品質は、それがどのように実装されるかではなく、どのように使用されるかによって判断されるべきである」という慣用句をほとんど忘れていました。
陶酔感

しかし、非依存クラスは非静的クラスにも存在する可能性がありますか?したがって、これが静的クラスの欠点であって、非静的クラスの欠点ではない理由はわかりません。
ヴァレンタリー2014年

@valenterryはい、静的でない依存関係も非表示にできますが、重要な点は、静的でない依存関係を非表示にすることです。私は可能性new(一般的にはお勧めできません)、コンストラクタ内のオブジェクトの束をアップしたが、私はすることができますまた、それらを注入。静的クラスの場合、それらを注入する方法はありません(とにかくJavaでは簡単ではありません。それを可能にする言語については、他のすべての議論になります)。そのため、選択肢はありません。だからこそ、依存関係を隠すことを余儀なくされるという考えを、私の回答の中で非常に強調しています。
cbojar 2014年

ただし、引数として指定することで、メソッドを呼び出すたびにそれら注入できます。したがって、あなたはそれらを非表示にすることを強制されるのではなく、それらを明示的にすることを強制されるため、誰もが「あは、そのメソッドはこれらの引数の一部()に依存し、隠された内部状態に依存します知っている」。
ヴァレンタリー2014

Javaでは、私の知る限りでは、リフレクション全体を通過せずに静的クラスをパラメーターとして指定することはできません。(私は考えられるあらゆる方法を試しました。もしあなたが方法を持っているなら、それを見たいと思います。)そして、あなたがそうすることを選択した場合、あなたは本質的に組み込み型システムを破壊しています(パラメータはClass、次に、それが適切なクラスであることを確認します)またはダックタイピングです(適切なメソッドに応答することを確認します)。後者を行う場合は、言語の選択を再評価する必要があります。あなたがかつてのをしたい場合は、インスタンスだけで罰金を行うと、内蔵しています。
cbojar

1

ちょうど私の2セント。

あなたの質問のために:

しかし、システムの「静的な」部分についてはどうですか?それは渡されません。たとえば、「ファイルを保存する」メカニズムです。静的クラスではなくオブジェクトに実装する必要があるのはなぜですか?

あなたは、サウンドを再生システムの一部、またはファイルにデータを保存し、システムの一部のメカニズム場合は、自分自身に尋ねることができWILL CHANGE。答えが「はい」の場合は、abstract class/ を使用してそれらを抽象化する必要があることを示していますinterface。あなたは尋ねるかもしれません、私はどうすれば将来のことを知ることができますか?絶対にできません。したがって、ステートレスの場合、などの「静的クラス」を使用できますjava.lang.Math。それ以外の場合は、オブジェクト指向のアプローチを使用できます。


ほっそりしたアドバイスがあります。3回のストライキとリファクタリングです。これは、変更が実際に必要になるまで延期できることを示唆しています。「変更」とは、複数のタイプのオブジェクトを保存する必要があることです。または、複数のファイル形式(保存タイプ)に保存する必要がある。または、保存されたデータの複雑さが大幅に増加します。もちろん、変更がすぐに発生すると確信している場合は、より柔軟な設計を事前に組み込むことができます。
rwong
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.