背景
OOPについて考えると、それはデータと動作を一緒にバインドしているように感じます。実際の例を考えると、同種のコレクションである配列データ型がすでにありますタイプとJavaでそれに対してニースの抽象化を構築しているため、完全に機能するclone
のようなメソッドがあります。つまり、データがあり、操作を関連付けました。これにより、クライアントがcode
シンプルになり、クローンのロジックがNice APIの背後にカプセル化されます。
うまくいけば、私は今まで正しいです。
質問
したがって、配列を並べ替える必要があるとしましょう。配列の要素を並べ替えるAPIとしてsort
メソッドを公開すると、配列の要素タイプに基づいて並べ替えを行うことができます。 Array ADTでのこのようなメソッドと、ここにドキュメントがいっぱいある混乱があります here には、staticメソッドの数がリストされています。私が適切に覚えている場合、静的メソッドを持つことはTDDコミュニティで最も嫌われているパターンです。それはテストを地獄にするので、彼らの懸念を無視しても、ここでOOP概念の違反、ここでは、すでにデータを持っているなら、配列自体にこれらすべてのメソッドがないのはなぜですか?
更新
ここに配列の例が存在しても、Javaの配列について混乱しているわけではありません。私の主な関心事は、静的メソッド全般に関するものです。通常、私は静的メソッドを使用するのが最善だと思う状況に身を置きますが、コミュニティがそれに反対することを何度も見てきました。その状況。
これはJava固有の設計です。たとえば in D の場合、sort
は配列のインスタンスメソッドであり、Javaのような静的メソッドではありません。
しかし、Java配列は特殊です。配列はオブジェクトとして定義されていますが、クラスのインスタンスではありません。それらが持つ唯一のインスタンスメソッドは、Object
から直接継承されたものです。定義できるArray
クラスがないため、配列固有のインスタンスメソッドではありません(リンクするArrays
クラスは単なるユーティリティクラスであることに注意してください-配列はこのインスタンスではありませんクラス。)
より論理的な設計は、配列をクラスArray<T>
のインスタンスにすることですが、Javaの初期バージョンにはジェネリックがないため、この設計は不可能でした。
ジェネリックリスト(List<E>
など)では、sort
メソッドはインスタンスメソッドです。したがって、Java= sort
はインスタンスメソッドであることが理想的であることに同意しますが、コア言語設計の制限により配列では不可能でした。
C#では別のデザインが選択され、配列は実際にはArray
クラスのインスタンスです。これにより、配列固有のインスタンスメソッドが許可されます(理由は何であれ、Sortはまだ静的として定義されています)。
要約すると、Javaの配列は「真の」オブジェクト指向ではないため、OO設計原則を確認できません。
静的メソッドを単体テストすることが不可能であるという考えは、殺すのが難しいことが証明されている神話です。
メソッドを単独でテストするのが難しいのは、隠れた依存関係や静的状態へのアクセスなどです。インスタンスメソッドの呼び出しがプライベートメンバーへのアクセスに使用される非表示のthis
参照パラメーターを取得するという事実を除いて、静的メソッドとインスタンスメソッドの間にはまったく違いはありません。
メソッドに正直なAPIがある場合、つまり、依存関係が明確に示されている場合、他のすべての意味で純粋な場合、静的かどうかに関係なく、簡単にテストできます。
メソッドがグローバル変数を更新し、データベース接続をnew
s更新してディスクに書き込む場合、インスタンスメソッドであるかどうかに関係なく、テストできません。
はい、(仮想)インスタンスメソッドはオーバーライドできますが、それは非常に重要です。グローバル状態にアクセスしてディスクに書き込み、メソッドを仮想としてマークしても、テスト可能にはなりません。それは単にスタブ可能にするので、他のものをテストするときにそれから切り離すことができます。そして、それは豊富な偽物やモックやスパイなどがそれ自体でテスト用アンチパターンであり、厳格で脆弱なテストコードにつながるという事実についてさえ言及していません。
重要なのは、一貫性のある分離されたモジュールとクラスを作成することです。 APIについて正直であり、手を差し伸べたり、空から物事を引き出したりしない限り、問題はありません。
sort
のようなメソッドをインスタンスメソッドではなく静的ヘルパーメソッドにする理由については、いくつかの理由が考えられます。結局のところ、それは単なる設計上の決定であり、どちらにしてもかまいません。エンティティにあまりにも多くの(または実際に任意の)ユーティリティメソッドを配置することに反対する1つのことは、それらのAPIが簡単に肥大化する可能性があることです。クラスも成長します。誰もが異なるヘルパーを必要とするので、どのヘルパーが型を付けるのに十分「重要」であり、どのヘルパーを外部クラスに入れるかをどのように決定しますか?そしてプログラマーとして、どこを見ればわかるのでしょうか?
純粋なデータを運ぶクラスを計算/処理クラスから分離するために(もちろん、カプセル化を壊さない限り)行うべき良いケースがあります。これにより、List<? extends Comparable>
を実装するすべての型の一般的な並べ替えアルゴリズムを作成できます。配列、配列リスト、リンクリストなどに1つ書き込む必要はありません(共通の抽象ベースを使用する必要はありません)。クラス)。
簡単にするために:
配列については、他の回答が言及しているように、Javaで奇抜です。他のコレクションも、それらに対する並べ替えメソッドではなく_Collections.sort
_によって並べ替えられます。根拠はコレクションはそれ自体がどのようにソートされるかを認識してはならないことに注意してください。これは、さまざまな言語がコレクションで直接これを公開するため(JavaScriptのsomeArray.sort()
、またはSwiftのsomeArray.sort()
など)、より議論の余地があります。またはC#の_.Sort
_および_.OrderBy
_)。
この決定はデザイナー次第であり、どちらのアプローチも問題なく、メンタルモデルに依存します。コレクションに何かを追加したり、コレクションから何かを削除したりすることを議論すると、より明確になります。この場合、不変式を適用できるように、基になるコレクションが担当する必要があると思います。並べ替えは別の話です。反復できるものは何でも、抽象化を使用できるレイヤーである並べ替えができるからです。 (ただし、効率的ではなく、多態的なディスパッチが私の意見ではまだ理にかなっています)。
静的クラス部分の非OOPの性質について、私はあなたと一緒です。いくつかの欠点があります。
それは静的性の性質そのものによって隠された依存関係です。クラスを注入することはできません。オブジェクトのみを注入することができます。最も鮮明な例は、@ kaiがコメントで述べたように、ユーティリティクラスです。
クラスは大規模から大規模になる傾向があります。静的メソッドを持つクラスはオブジェクトとは何の関係もないので、彼らは彼らが誰であるか、何をすべきか、何をすべきでないかを知りません。境界がぼやけているので、1つの命令を次々に記述します。タスクが完了するまでやめられません。それは必然的で非OOPプロセスです。
したがって、クラスはますますまとまりが少なくなっています。クラスに多くの依存関係がある場合は、必要以上に機能する可能性があります。
静的メソッドは、どこからでも呼び出すことができることを意味します。多くのコンテキストから呼び出すことができます。彼らはたくさんのクライアントを持っています。したがって、1つのクラスで静的メソッドに実装する少し特別な動作が必要な場合は、他のクライアントが壊れていないことを確認する必要があります。したがって、そのような再利用は単に機能しません。マイクロサービスを作成して再利用しようとする気高い(そして失敗した)試みと比較できます。結果として得られるクラスは一般的すぎるため、完全に保守できません。これにより、システム全体が密結合されます。
私のポイントを説明するために、私はそうです。並べ替えの例を見てみましょう。コードを宣言型にしたいので、コードの作成と作業を分離します。したがって、sort
メソッドを使用する代わりに、Sorted
クラスを実装してArray
インターフェースを実装します(特定の言語については説明していません)。したがって、私のコードは次のようになります。
$array =
new Sorted(
new Filtered(
new Mapped(
[1, 2, 3],
function (int $i) {
return $i * 3;
}
),
function (int $i) {
return $i %2 == 0;
}
),
function ($a, $b) {
return $b > $a;
}
)
;
print_r($array->value()); // here the objects actually work