干し草の山からどうやって針を見つけるのですか?
-
09-06-2019 - |
質問
オブジェクト指向の方法で干し草の山の針探しを実装する場合、基本的に 3 つの選択肢があります。
1. needle.find(haystack)
2. haystack.find(needle)
3. searcher.find(needle, haystack)
どちらが好みですか、またその理由は何ですか?
3 番目のオブジェクトの導入を避けるため、2 番目の選択肢を好む人もいると思います。しかし、少なくとも「現実世界」をモデル化することが目的である場合には、3 番目のアプローチの方が概念的には「正しい」と感じずにはいられません。
この例のサーチャーなど、ヘルパー オブジェクトを導入することが正当化されるのはどのような場合だと思いますか?また、ヘルパー オブジェクトを避けるべきなのはどのような場合ですか?
解決
通常、アクションは、アクションを実行している対象に適用される必要があります...この場合は干し草の山なので、オプション 2 が最も適切だと思います。
選択肢 3 よりも優れていると思われる 4 番目の選択肢もあります。
haystack.find(needle, searcher)
この場合、アクションの一部として検索する方法を指定できるため、操作されているオブジェクトでアクションを維持できます。
他のヒント
3 つのうち、私はオプション #3 を好みます。
の 単一責任の原則 DTO やモデルに検索機能を追加したくないのです。彼らの責任はデータであることであり、自分自身を見つけることではありません。また、針が干し草の山について知る必要はなく、干し草の山も針について知る必要はありません。
実際のところ、ほとんどの OO 実践者は、なぜ #3 が最良の選択であるかを理解するのに長い時間がかかると思います。おそらく本当に理解するまで、私は 10 年間 OO をやっていました。
@wilhelmtell 氏によると、C++ は、そのようなシステムを実際に動作させる、テンプレートに特化した数少ない言語の 1 つです。ほとんどの言語にとって、汎用の「find」メソッドは恐ろしいアイデアです。
もう 1 つの代替方法があります。これは、C++ の STL で使用されるアプローチです。
find(haystack.begin(), haystack.end(), needle)
「あなたの顔に!」と叫ぶC ++の素晴らしい例だと思います。 ooopに。OOP はいかなる種類の特効薬でもない、という考えです。物事は、アクションの観点から最もよく説明される場合もあれば、オブジェクトの観点から説明される場合もあり、どちらも説明されない場合もあれば、両方の観点から説明される場合もあります。
Bjarne Stroustrup 氏は TC++PL で、システムを設計するときは、効果的かつ効率的なコードの制約の下で現実を反映するよう努めるべきであると述べました。私にとって、これは決して盲目的に従うべきではないということを意味します。手元にあるもの(干し草の山、針)と、私たちがいる文脈(検索、それがこの表現の意味するところ)について考えてください。
検索に重点を置く場合は、検索に重点を置くアルゴリズム (アクション) を使用します (つまり、干し草の山、海洋、砂漠、リンクされたリストに柔軟に適合します)。干し草の山に重点を置く場合は、find メソッドを干し草の山オブジェクト内にカプセル化するなどします。
そうは言っても、時には迷ったり、選択するのに苦労したりすることもあります。この場合、オブジェクト指向になります。後で気が変わった場合は、アクションをオブジェクトとクラスに分割するよりも、オブジェクトからアクションを抽出する方が簡単だと思います。
これらのガイドラインに従えば、コードはより明確になり、さらに美しくなります。
選択肢1は完全にアウトだと思います。コードは、それが何をするのかがわかるように読む必要があります。選択肢 1 では、この針が干し草の山を探しに行くのではないかと思います。
干し草の山に針が含まれる場合は、オプション 2 が適しています。ListCollection には常に ListItem が含まれるため、collection.find(item) を実行するのは自然で表現力豊かです。
私は、次のような場合にヘルパー オブジェクトの導入が適切であると考えます。
- 問題のオブジェクトの実装を制御できない
IE:search.find(ObsecureOSObject, ファイル) - オブジェクト間には規則的または合理的な関係がありません
IE:nameMatcher.find(家、木.名前)
この件に関しては私もブラッドと一緒です。非常に複雑なシステムに取り組むほど、オブジェクトを真に分離する必要性が見えてきます。彼は正しい。針が干し草の山について何も知らないはずがないのは明らかなので、1 は間違いなくアウトです。しかし、干し草の山は針について何も知らないはずです。
干し草の山をモデル化している場合は、それをコレクションとして実装するかもしれませんが、 干し草 または ストロー -- 針のコレクションではありません。ただし、干し草の山の中に物が紛れていることは考慮に入れておきますが、それが具体的に何なのかについては何も知りません。干し草の山自体にアイテムを探させない方が良いと思います(方法 頭いい とにかく干し草の山です)。私にとって正しいアプローチは、干し草の山に、その中にあるものの、わらや干し草など、干し草の山に本質を与えるものではないもののコレクションを提示させることです。
class Haystack : ISearchableThingsOnAFarm {
ICollection<Hay> myHay;
ICollection<IStuffSmallEnoughToBeLostInAHaystack> stuffLostInMe;
public ICollection<Hay> Hay {
get {
return myHay;
}
}
public ICollection<IStuffSmallEnoughToBeLostInAHayStack> LostAndFound {
get {
return stuffLostInMe;
}
}
}
class Needle : IStuffSmallEnoughToBeLostInAHaystack {
}
class Farmer {
Search(Haystack haystack,
IStuffSmallEnoughToBeLostInAHaystack itemToFind)
}
実際にはインターフェイスに入力して抽象化する予定がさらにありましたが、その後、自分がどれほどクレイジーになっていることに気づきました。大学のCSのクラスにいたような気分でした...:P
わかりますね。できるだけ疎結合にするのが良いことだと思いますが、少し調子に乗っていただけかもしれません。:)
Needle と Haystack の両方が DAO である場合、オプション 1 と 2 は問題外です。
その理由は、DAO はモデリングしている現実世界のオブジェクトのプロパティの保持のみを担当し、ゲッター メソッドとセッター メソッド (またはプロパティへの直接アクセス) のみを持つ必要があるためです。これにより、コードにこれらのヘルパー メソッドをスキップするための "if" ステートメントが大量に含まれないため、DAO をファイルにシリアル化したり、汎用比較/汎用コピーのメソッドを作成したりすることが簡単になります。
これで残るのはオプション 3 だけですが、これが正しい動作であるとほとんどの人が同意するでしょう。
オプション 3 にはいくつかの利点がありますが、最大の利点は単体テストです。これは、Needle オブジェクトと Haystack オブジェクトの両方を簡単にモックアップできるようになりましたが、オプション 1 または 2 を使用した場合、検索を実行する前に Needle または Haystack のいずれかの内部状態を変更する必要があるためです。
次に、サーチャーが別のクラスになったことにより、共通の検索コードを含むすべての検索コードを 1 か所に保持できるようになりました。一方、検索コードが DAO に入れられた場合、一般的な検索コードは複雑なクラス階層に格納されるか、とにかく Searcher Helper クラスに格納されます。
これは、何が変化し、何が変わらないかによって完全に異なります。
たとえば、私は (非 OOP) に取り組んでいます。 フレームワーク ここで、検索アルゴリズムは針と干し草の山の両方のタイプに応じて異なります。これは、オブジェクト指向環境では二重ディスパッチが必要になるという事実とは別に、どちらを書いても意味がないことも意味します。 needle.find(haystack)
または書く haystack.find(needle)
.
一方、アプリケーションは検出を両方のクラスのいずれかに喜んで委任することも、1 つのアルゴリズムに完全に固執することもできます。その場合、決定は任意になります。その場合、私は、 haystack.find(needle)
なぜなら、その発見を干し草の山に適用する方が論理的だと思われるからです。
ヘイスタックの針検索をオブジェクト指向の方法で実装する場合、本質的に3つの選択肢があります。
針.find(干し草の山)
haystack.find(針)
searcher.find(針、干し草の山)
どちらが好みですか、またその理由は何ですか?
間違っていたら訂正してください。ただし、3 つの例すべてですでに 持っている 探している針を参照するのは、鼻の上にメガネがかかっているときにメガネを探すのと同じようなものではないでしょうか?:p
冗談はさておき、それは与えられたドメイン内で干し草の山の責任をどのように考えるかによって決まると思います。針が入っているもの(要はコレクション)という意味で気にしているだけでしょうか?それなら、haystack.find(needlePredicate) で大丈夫です。それ以外の場合は、farmBoy.find(predicate, haystack) の方が適切な場合があります。
偉大な著者の言葉を引用すると、 SICP,
プログラムは人が読めるように書かれなければなりませんが、偶然に機械が実行できるようにする必要があります。
方法 1 と 2 の両方を手元に用意しておきたいと思います。Ruby を例に挙げると、次のものが付属します。 .include?
これはこのように使用されます
haystack.include? needle
=> returns true if the haystack includes the needle
ただし、純粋に読みやすくするために、内容を反転させたい場合もあります。Rubyには付属していない in?
という方法ですが、ワンライナーなので、私はよくこれを行います。
needle.in? haystack
=> exactly the same as above
干し草の山や検索の操作を強調することが「より重要」である場合、私は次のように書くことを好みます。 include?
。しかし、多くの場合、干し草の山も検索も実際に関心があるのは、オブジェクトが存在することだけです。この場合、私は次のように感じました。 in?
プログラムの意味がよりよく伝わります。
それはあなたの要件によって異なります。
たとえば、検索者のプロパティを気にしない場合 (例:サーチャーの強さ、視力など)を考慮した場合、haystack.find(needle) が最もクリーンなソリューションになると思います。
ただし、サーチャーのプロパティ (またはその他のプロパティ) が気になる場合は、ISearcher インターフェイスを haystack コンストラクターまたは関数に挿入して、それを容易にします。これにより、オブジェクト指向設計 (干し草の山には針がある) と制御の反転 / 依存性注入 (「find」関数の単体テストが容易になります) の両方がサポートされます。
最初の 2 つのフレーバーのいずれかが意味をなす状況が思い当たります。
Knuth-Morris-Pratt アルゴリズムのように、針の前処理が必要な場合は、
needle.findIn(haystack)
(またはpattern.findIn(text)
) アルゴリズムが効果的に動作するために作成された中間テーブルが針オブジェクトに保持されているため、これは理にかなっています。たとえばトライなど、干し草の山に前処理が必要な場合、
haystack.find(needle)
(またはwords.hasAWordWithPrefix(prefix)
) より良く動作します。
上記のどちらの場合でも、針または干し草の山のいずれかが検索を認識します。また、二人はお互いを意識しています。針と干し草の山がお互いを認識したり、検索を認識したりしないようにしたい場合は、 searcher.find(needle, haystack)
が適切でしょう。
簡単:干し草の山を燃やせ!後は針だけが残ります。また、磁石を試してみるのもいいかもしれません。
さらに難しい質問:針のプールの中から特定の 1 本の針をどのように見つけますか?
答え:それぞれに糸を通し、糸の各ストランドの他端をソートされたインデックスに接続します(つまり、ポインタ)
この質問に対する答えは、実際には、ソリューションが実装されるドメインによって異なります。
物理的な干し草の山で物理的な検索をシミュレートすると、次のようなクラスが得られるかもしれません。
- 空間
- ストロー
- 針
- シーカー
空間
どのオブジェクトがどの座標に位置するかを知っています
自然法則を実行します (エネルギーの変換、衝突の検出など)
針, ストロー
宇宙に位置しています
力に反応する
シーカー
空間と相互作用します:
手を動かす、磁場を加える、干し草を燃やす、X線を当てる、針を探す...
したがってseeker.find(needle, space)
または seeker.find(needle, space, strategy)
干し草の山は、針を探している場所にたまたまあります。スペースを一種の仮想マシンとして抽象化する場合 (次のように考えてください:行列)スペースの代わりに干し草の山を使用して上記を取得できます (解決策 3/3b):
seeker.find(needle, haystack)
または seeker.find(needle, haystack, strategy)
しかし、マトリックスはドメインであり、他の場所に針を置くことができない場合にのみ、干し草の山に置き換えるべきです。
そしてまたしても、それはただの例え話でした。興味深いことに、これによりまったく新しい方向への心が開かれます。
1.そもそもなぜ針が抜けてしまったのでしょうか?プロセスを変更して、紛失しないようにすることはできますか?
2.紛失した針を見つけなければなりませんか、それとも別の針を手に入れて、最初の針のことは忘れることができるでしょうか?(しばらくしたら針が溶けてくれればいいのですが)
3.定期的に針を紛失してしまい、再度見つける必要がある場合は、次のようにするとよいでしょう。
自分自身を見つけることができる針を作ります。彼らは定期的に次のように自問します。私は迷っているでしょうか?答えが「はい」の場合、彼らは GPS で計算された位置を誰かに送信するか、ビープ音などを鳴らし始めます。
needle.find(space)
またはneedle.find(haystack)
(解決策1)各わらにカメラを付けた干し草の山を設置したら、干し草の山の巣の心に最近針を見たかどうか尋ねることができます。
haystack.find(針) (解決策2)針に RFID タグを取り付ければ、針を簡単に三角測量できます
それはすべて、実装でそれを言うだけです あなた 針と干し草の山、そしてほとんどの場合、ある種のレベルのマトリックスを作りました。
したがって、ドメインに応じて決定します。
- 干し草の山の目的は針を入れることですか?次に、解決策 2 に進みます。
- どこにでも針がなくなるのは当然なのでしょうか?次に、解決策 1 に進みます。
- 針が偶然干し草の山の中に落ちてしまったのでしょうか?次に、解決策 3 に進みます。(または別の回復戦略を検討してください)
haystack.find(needle) ですが、検索フィールドがあるはずです。
依存関係注入が大流行していることは知っているので、@Mike Stone の haystack.find(needle, searcher) が受け入れられたことには驚きません。しかし、私は同意しません。どのサーチャーを使用するかの選択は、干し草の山に対する決定であるように私には思えます。
2 つの ISearcher について考えてみましょう。MagneticSearcher はボリューム上で反復処理を行い、磁石の強さに応じて磁石を移動およびステップさせます。QuickSortSearcher は、サブパイルの 1 つに針が現れるまで、スタックを 2 つに分割します。サーチャーの適切な選択は、干し草の山の大きさ (たとえば、磁場との関係)、針がどのように干し草の山に入ったのか (つまり、針の位置は本当にランダムなのか、それとも偏っているのか) などによって決まります。
Haystack.find(針、検索者)がある場合、「選択は最良の検索戦略であり、HayStackのコンテキストの外で最もよく行われる」と言っています。私はそれが正しいとは思わないと思います。「ヘイスタックは自分自身を検索するのが最善の方法を知っている」可能性が高いと思います。セッターを追加すると、オーバーライドまたはテストする必要がある場合は、検索者を手動で注入できます。
個人的には 2 番目の方法が好きです。私の理由は、私がこれまでに扱ってきた主要な API がこのアプローチを使用しており、それが最も理にかなっていると感じたからです。
もののリスト (干し草の山) がある場合は、針を検索 (find()) します。
@ピーター・マイヤー
わかりますね。できるだけ疎結合にするのが良いことだと思いますが、少し調子に乗っていただけかもしれません。:)
エラー...うん...私は思います IStuffSmallEnoughToBeLostInAHaystack
一種の危険信号です:-)
選択肢 3 よりも優れていると思われる 4 番目の選択肢もあります。
haystack.find(needle, searcher)
あなたの言いたいことはわかりますが、次の場合はどうでしょうか searcher
干し草の山以外の種類のオブジェクトを検索したり、その中に針以外のものを見つけたりできる検索インターフェイスを実装していますか?
このインターフェイスは、次のようなさまざまなアルゴリズムで実装することもできます。
binary_searcher.find(needle, haystack)
vision_searcher.find(pitchfork, haystack)
brute_force_searcher.find(money, wallet)
ただし、他の人がすでに指摘しているように、これは実際に複数の検索アルゴリズム、または複数の検索可能または検索可能なクラスがある場合にのみ役立つと思います。そうでない場合は、同意します haystack.find(needle)
はそのシンプルさの点で優れているため、そのためにある程度の「正しさ」を犠牲にすることはいといません。
class Haystack {//whatever
};
class Needle {//whatever
}:
class Searcher {
virtual void find() = 0;
};
class HaystackSearcher::public Searcher {
public:
HaystackSearcher(Haystack, object)
virtual void find();
};
Haystack H;
Needle N;
HaystackSearcher HS(H, N);
HS.find();
まさに、2と3の混合です。
一部の干し草の山には、特別な検索戦略がありません。この例は配列です。何かを見つける唯一の方法は、最初から始めて、目的のものが見つかるまで各項目をテストすることです。
この種のことには、おそらく無料の関数が最適です (C++ など)。
一部の干し草の山には、特殊な検索戦略が課せられている場合があります。配列は並べ替えることができ、たとえばバイナリ検索を使用できるようになります。無料関数 (または無料関数のペア、例:sort と binary_search) がおそらく最良のオプションです。
一部の干し草の山には、実装の一部として統合された検索戦略があります。たとえば、連想コンテナ (ハッシュまたは順序付きセットとマップ) はすべてこれに該当します。この場合、見つけることはおそらく必須の検索方法であるため、おそらくメソッドである必要があります。haystack.find(針)。
ヘイスタックにはものを含めることができます1つのタイプのものは針のファインダーです。
したがって、柔軟な解決策としては、次のようなことを行うとよいでしょう。IStuff インターフェース
haystack = ilistu003CIStuff>針:いもの
Finder .Find(iStuff Stufftolookfor、ilistu003CIStuff> Stuffstolookin)
この場合、ソリューションは針と干し草の山だけに結び付けられることはありませんが、インターフェイスを実装するあらゆるタイプに使用できます。
ですから、海の中で魚を見つけたいなら、それができます。
var results = Finder.Find(魚, 海)
針オブジェクトへの参照がある場合、なぜそれを検索するのでしょうか?:)問題ドメインとユースケースは、干し草の山で針の正確な位置を必要としないことを示しています(list.indexof(要素)から得ることができるように)、針が必要です。そして、あなたはまだそれを持っていません。したがって、私の答えは次のようなものです
Needle needle = (Needle)haystack.searchByName("needle");
または
Needle needle = (Needle)haystack.searchWithFilter(new Filter(){
public boolean isWhatYouNeed(Object obj)
{
return obj instanceof Needle;
}
});
または
Needle needle = (Needle)haystack.searchByPattern(Size.SMALL,
Sharpness.SHARP,
Material.METAL);
さまざまな検索戦略に基づいた、より多くの可能な解決策があることに同意します。そのため、searcher が導入されています。これについてはたくさんのコメントがあったので、ここでは気にしません。私が言いたいのは、上記のソリューションはユースケースを忘れているということです。すでに参照しているものを検索する意味は何でしょうか?最も自然な使用例では、まだニードルがないため、変数ニードルは使用しません。
Brad Wilson は、オブジェクトには単一の責任があるべきだと指摘しています。極端な場合、オブジェクトには 1 つの責任があり、状態はありません。そうすれば、それは...になる可能性があります機能。
needle = findNeedleIn(haystack);
あるいは、次のように書くこともできます。
SynchronizedHaystackSearcherProxyFactory proxyFactory =
SynchronizedHaystackSearcherProxyFactory.getInstance();
StrategyBasedHaystackSearcher searcher =
new BasicStrategyBasedHaystackSearcher(
NeedleSeekingStrategies.getMethodicalInstance());
SynchronizedHaystackSearcherProxy proxy =
proxyFactory.createSynchronizedHaystackSearcherProxy(searcher);
SearchableHaystackAdapter searchableHaystack =
new SearchableHaystackAdapter(haystack);
FindableSearchResultObject foundObject = null;
while (!HaystackSearcherUtil.isNeedleObject(foundObject)) {
try {
foundObject = proxy.find(searchableHaystack);
} catch (GruesomeInjuryException exc) {
returnPitchforkToShed(); // sigh, i hate it when this happens
HaystackSearcherUtil.cleanUp(hay); // XXX fixme not really thread-safe,
// but we can't just leave this mess
HaystackSearcherUtil.cleanup(exc.getGruesomeMess()); // bug 510000884
throw exc; // caller will catch this and get us to a hospital,
// if it's not already too late
}
}
return (Needle) BarnyardObjectProtocolUtil.createSynchronizedFindableSearchResultObjectProxyAdapterUnwrapperForToolInterfaceName(SimpleToolInterfaces.NEEDLE_INTERFACE_NAME).adapt(foundObject.getAdaptable());
干し草の山は針のことを知っているべきではありませんし、針も干し草の山のことを知っているべきではありません。検索者は両方について知っている必要がありますが、干し草の山自体が検索方法を知っているべきかどうかが本当の争点です。
したがって、私は 2 と 3 を組み合わせたものを使用します。干し草の山は他の人にそれを検索する方法を伝えることができ、検索者はその情報を使って干し草の山を検索できる必要があります。
haystack.magnet().filter(needle);
コードは特定の針を見つけようとしているのでしょうか、それとも任意の針を見つけようとしているのでしょうか?愚かな質問のように聞こえますが、問題は変わります。
特定の針を探すと、質問のコードが意味を成します。針を探すと、次のようになります
needle = haystack.findNeedle()
または
needle = searcher.findNeedle(haystack)
いずれにせよ、私はそのクラスにサーチャーがいる方が好きです。干し草の山は検索方法を知りません。CS の観点から見ると、これは望ましくないものがたくさんある単なるデータ ストアです。
私見ですが、間違いなく3番目です。
干し草の山の中の針の問題は、他のオブジェクトの大規模なコレクションの中から 1 つのオブジェクトを見つけようとする試みの一例です。これは、複雑な検索アルゴリズム (おそらく磁石または (より可能性の高い) 子プロセスを含む) が必要であることを示していますが、それは必要ありません。干し草の山にスレッド管理や複雑な検索の実装を期待するのはあまり意味がありません。
ただし、検索オブジェクトは検索専用であり、高速バイナリ検索のために子スレッドを管理する方法、または検索対象要素のプロパティを使用して領域を絞り込む方法 (つまり、次のこと) を知っていることが期待できます。鉄製品を見つけるための磁石)。
別の可能な方法は、検索可能なオブジェクト用に 2 つのインターフェイスを作成することです。haystack と ToBeSearched オブジェクト 例:針。ということで、このようにすれば出来ます
public Interface IToBeSearched
{}
public Interface ISearchable
{
public void Find(IToBeSearched a);
}
Class Needle Implements IToBeSearched
{}
Class Haystack Implements ISearchable
{
public void Find(IToBeSearched needle)
{
//Here goes the original coding of find function
}
}
haystack.iterator.findFirst(/* pass here a predicate returning
true if its argument is a needle that we want */)
iterator
コレクションには共通のものがあるため、不変のコレクションへのインターフェイスとして使用できます。 findFirst(fun: T => Boolean)
仕事を行うメソッド。干し草の山が不変である限り、有用なデータを「外部」から隠す必要はありません。そしてもちろん、カスタムの非自明なコレクションの実装と、それを実行する他のものを結び付けるのは良くありません。 haystack
. 。分割して征服してください、いいですか?
ほとんどの場合、コア オブジェクトに対してこのような単純なヘルパー操作を実行できることが望ましいのですが、言語によっては、問題のオブジェクトに十分なメソッドや賢明なメソッドが利用できない場合があります。
のような言語でも JavaScript) 組み込みオブジェクトを拡張/拡張できるようになりますが、便利であると同時に問題もあることがわかりました (例:言語の将来のバージョンで、カスタム メソッドによってオーバーライドされるより効率的なメソッドが導入された場合)。
この記事 は、そのようなシナリオの概要をうまく説明しています。