馬の群れを考えると、すべてのユニコーンの平均角の長さを見つけるにはどうすればよいですか?


30

上記の質問は、レガシーコードで発生する一般的な問題、またはより正確には、この問題を解決するための以前の試みに起因する問題の抽象的な例です。

Enumerable.OfType<T>メソッドのように、この問題に対処することを目的とした少なくとも1つの.NETフレームワークメソッドを考えることができます。しかし、最終的に実行時にオブジェクトの型を調べることになってしまうという事実は、私には正しくありません。

それぞれの馬に「ユニコーンですか?」次のアプローチも思い浮かびます。

  • ユニコーン以外の角の長さを取得しようとすると例外がスローされます(各馬に適切でない機能が公開されます)
  • 非ユニコーンの角の長さのデフォルト値または魔法の値を返します(すべてユニコーンではない可能性のある馬のグループでホーンの統計情報をクランチするコード全体にデフォルトチェックが必要です)
  • 継承を廃止して、馬がユニコーンであるかどうかを示す別のオブジェクトを馬に作成します(潜在的に同じ問題をレイヤーに押し込んでいます)。

これは「無回答」で最もよく答えられると感じています。しかし、この問題にどのようにアプローチし、それが依存する場合、あなたの決定の背景は何ですか?

また、この問題が関数型コードにまだ存在するかどうか(または、可変性をサポートする関数型言語にのみ存在するかどうかに関する洞察にも興味がありますか?)

これは、次の質問の重複の可能性としてフラグが立てられました: ダウンキャストを回避する方法

その質問への答えは、HornMeasurerすべてのホーン測定を行わなければならないaを所有していることを前提としています。しかし、それは、誰もが馬の角を自由に測定できるという平等主義の原則の下で形成されたコードベースに対する非常に強い課しです。

aがないHornMeasurer場合、受け入れられる回答のアプローチは、上記の例外ベースのアプローチを反映しています。

また、馬とユニコーンが両方とも馬であるかどうか、またはユニコーンが馬の魔法の亜種であるかどうかについてのコメントにもいくらか混乱がありました。両方の可能性を考慮する必要があります-おそらく一方が他方よりも望ましいですか?


22
馬には角がないので、平均は未定義(0/0)です。
スコットホイットロック

3
@moarboilerplate 10から無限までの任意の場所。
乳母

4
@StephenP:この場合、数学的には機能しません。これらの0はすべて平均を歪めます。
メイソンウィーラー

3
あなたの質問が非回答で最もよく回答される場合、それはQ&Aサイトに属していません。reddit、quora、またはその他のディスカッションベースのサイトは、非回答タイプのもの用に構築されています...それは、@ MasonWheelerが提供したコードを探している場合は明らかに答えられるかもしれないと思いますあなたが求めていること..
ジミーホッファ

3
@JimmyHoffa「あなたはそれを間違っています」は許容できる「無回答」であり、「まあ、これを行うことができる1つの方法」よりも優れていることがよくあります。詳細な議論は必要ありません。
-moarboilerplate

回答:


11

Unicorn特別な種類のとして扱いたいHorse場合、基本的に2つの方法でモデル化できます。より伝統的な方法は、サブクラスの関係です。コードをリファクタリングするだけで、タイプのチェックとダウンキャストを避け、重要なコンテキストで常にリストを分離し、Unicorn特性を気にしないコンテキストでのみリストを結合することができます。言い換えれば、そもそも馬の群れからユニコーンを抽出する必要がある状況に陥らないように調整します。これは最初は難しいように見えますが、99.99%のケースで可能です。通常、実際にはコードがずっときれいになります。

ユニコーンをモデル化するもう1つの方法は、すべての馬にオプションのホーンの長さを与えることです。次に、角の長さがあるかどうかを確認してユニコーンかどうかをテストし、(Scalaで)すべてのユニコーンの平均角の長さを見つけることができます。

case class Horse(val hornLength: Option[Double])

val horse = Horse(None)
val unicorn = Horse(Some(12.0))
val anotherUnicorn = Horse(Some(6.0))

val herd = List(horse, unicorn, anotherUnicorn)
val hornLengths = herd flatMap {_.hornLength}
val averageLength = hornLengths.sum / hornLengths.size

この方法には、単一のクラスでより簡単であるという利点がありますが、拡張性がはるかに低く、「ユニコーン」を確認するための回り道のような方法があるという欠点があります。このソリューションを使用する場合のコツは、頻繁に拡張を開始するときに、より柔軟なアーキテクチャに移行する必要があることを認識することです。この種のソリューションはflatMapNoneアイテムを簡単に除外するシンプルで強力な機能を備えた関数型言語ではるかに人気があります。


7
もちろん、これは普通の馬とユニコーンの唯一の違いが角であることを前提としています。そうでない場合、事態は非常に迅速に複雑になります。
メイソンウィーラー

@MasonWheelerは、提示された2番目の方法でのみ。
-moarboilerplate

1
ユニコーンを気にしない状況になるまで、継承シナリオで非ユニコーンとユニコーンを一緒に書くべきではないというコメントを見つけてください。確かに、.OfType()は問題を解決して機能させることができますが、そもそも存在しないはずの問題を解決しています。2番目のアプローチについては、オプションがnullに依存して何かを暗示するよりもはるかに優れているため、機能します。ユニコーンの特性をスタンドアロンのプロパティにカプセル化し、非常に警戒している場合、2番目のアプローチはOOで妥協して達成できると思います。
-moarboilerplate

1
ユニコーンの特性をスタンドアロンのプロパティにカプセル化し、非常に警戒している場合は妥協 -なぜ自分の人生を難しくするのか。typeofを直接使用して、大量の将来の問題を保存します。
gbjbaanb

@gbjbaanbそのアプローチは、貧血に特性とホーンの長さのある種の特性Horseがあるシナリオにのみ本当に適切であると考えます(質問で述べたライダー/グリッターのスケーリング時)。IsUnicornUnicornStuff
-moarboilerplate

38

あなたはほとんどすべてのオプションをカバーしました。特定のサブタイプに依存する動作があり、他のタイプと混ざっている場合、コードはそのサブタイプを認識する必要があります。それは単純な論理的推論です。

個人的には、私はただ行きhorses.OfType<Unicorn>().Average(u => u.HornLength)ます。コードの意図を非常に明確に表現します。誰かが後でそれを維持しなければならなくなるので、これはしばしば最も重要なことです。


ラムダ構文が正しくない場合はご容赦ください。私はC#のコーダーではないので、このような難解な詳細をまっすぐに保つことはできません。しかし、私が意味することは明らかです。
メイソンウィーラー

1
心配する必要はありません。リストにUnicornとにかくs のみが含まれていれば、問題はほとんど解決されます(レコードは省略できますreturn)。
-moarboilerplate

4
これは、問題を迅速に解決したい場合に私が求める答えです。しかし、コードをより妥当なものにリファクタリングしたい場合の答えではありません。
アンディ

6
これは、ばかげたレベルの最適化が必要でない限り、間違いなく答えです。それの明快さと読みやすさは、他のほとんどすべてを意味のないものにします。
デビッドは、モニカを

1
@DavidGrinbergこのきれいで読みやすいメソッドを書くと、以前は存在しなかった継承構造を最初に実装する必要があったとしたらどうでしょうか?
-moarboilerplate

9

.NETには何の問題もありません。

var unicorn = animal as Unicorn;
if(unicorn != null)
{
    sum += unicorn.HornLength;
    count++;
}

同等のLinqを使用しても問題ありません。

var averageUnicornHornLength = animals
    .OfType<Unicorn>()
    .Select(x => x.HornLength)
    .Average();

タイトルで尋ねた質問に基づいて、これは私が見つけると予想されるコードです。質問が「角のある動物の平均とは何か」のような何かを尋ねた場合、それは異なるでしょう:

var averageHornedAnimalHornLength = animals
    .OfType<IHornedAnimal>()
    .Select(x => x.HornLength)
    .Average();

Linqを使用している場合、enumerableが空で、T型がnull可能でない場合、Average(およびMinand Max)は例外をスローすることに注意してください。これは、平均が実際には未定義(0/0)であるためです。本当にこのようなものが必要です:

var hornedAnimals = animals
    .OfType<IHornedAnimal>()
    .ToList();
if(hornedAnimals.Count > 0)
{
    var averageHornLengthOfHornedAnimals = hornedAnimals
        .Average(x => x.HornLength);
}
else
{
    // deal with it in your own way...
}

編集

これを追加する必要があると思います...このような質問がオブジェクト指向のプログラマーとうまく合わない理由の1つは、データ構造をモデル化するためにクラスとオブジェクトを使用していると仮定していることです。元のSmalltalk風のオブジェクト指向のアイデアは、オブジェクトとしてインスタンス化されたモジュールからプログラムを構築し、メッセージを送信したときにサービスを実行することでした。クラスとオブジェクトを使用してデータ構造をモデル化できるという事実は(有用な)副作用ですが、これらは2つの異なるものです。後者でもオブジェクト指向プログラミングと見なすべきだとは思わない。なぜなら、a で同じことをすることができるからであるがstruct、それほどきれいではないだろう。

オブジェクト指向プログラミングを使用して何かを行うサービスを作成している場合、そのサービスが実際に他のサービスであるか、具体的な実装であるかを照会することは、一般的に正当な理由で嫌われています。インターフェイスが与えられ(通常、依存関係の注入を通じて)、そのインターフェイス/契約にコーディングする必要があります。

一方、クラス/オブジェクト/インターフェイスのアイデアを(誤って)使用してデータ構造またはデータモデルを作成している場合、個人的にはis-aアイデアを最大限に使用することに問題はありません。ユニコーンは馬のサブタイプであり、ドメイン内で完全に理にかなっていると定義している場合は、絶対に先に進み、群れの中の馬に問い合わせてユニコーンを見つけます。結局のところ、このような場合、通常、ドメイン固有の言語を作成して、解決しなければならない問題の解決策をよりよく表現しようとしています。その意味では、.OfType<Unicorn>()etcに何も問題はありません。

最終的に、アイテムのコレクションを取得し、タイプでフィルタリングすることは、実際には単なる機能プログラミングであり、オブジェクト指向プログラミングではありません。ありがたいことに、C#のような言語は両方のパラダイムを快適に処理できるようになりました。


7
あなたはすでにそれanimal がであることを知っていUnicorn ます; を使用するのasではなく、単にキャストするか、潜在的にさらに使いやすくしてas から、nullをチェックします。
フィリップケンドール

3

しかし、最終的に実行時にオブジェクトの型を調べることになってしまうという事実は、私には正しくありません。

このステートメントの問題は、使用するメカニズムに関係なく、常にオブジェクトに問い合わせて、それがどのタイプであるかを知ることです。RTTIの場合もあれば、ユニオンまたはプレーンデータ構造の場合もありますif horn > 0。正確な仕様はわずかに変化しますが、意図は同じです。オブジェクトに何らかの方法でそれ自体について問い合わせ、さらに問い合わせる必要があるかどうかを確認します。

それを考えると、あなたの言語のサポートを使用してこれを行うのは理にかなっています。typeofたとえば、.NETで使用します。

これを行う理由は、単にあなたの言語をうまく使うこと以上のものです。別のオブジェクトのように見えるが、小さな変更を加えたオブジェクトがある場合、時間の経過とともにより多くの違いが見つかる可能性があります。あなたのユニコーン/馬の例では、角の長さだけがあると言うかもしれません...しかし、明日は潜在的なライダーが処女であるか、うんちがきらきらしているかどうかを確認します。(古典的な実世界の例は、共通のベースから派生するGUIウィジェットであり、チェックボックスとリストボックスを別々に探す必要があります。データのすべての可能な順列を保持する単一のスーパーオブジェクトを単純に作成するには違いが大きすぎます)。

実行時にオブジェクトのタイプを確認してもうまくいかない場合は、ユニコーン/馬の単一の群れを保存する代わりに、異なるオブジェクトを最初から分割するのが2つのコレクションです-1つは馬用、1つはユニコーン用。これは、特殊なコンテナ(キーがオブジェクトタイプであるマルチマップなど)に格納する場合でも非常にうまく機能しますが、2つのグループに格納しても、オブジェクトタイプの問い合わせにすぐに戻ります。 !)

例外ベースのアプローチは間違いです。通常のプログラムフローとして例外を使用するのはコードの匂いです(ユニコーンの群れと、頭に貝殻がテープで留められたロバがいた場合、例外ベースのアプローチは大丈夫ですが、ユニコーンの群れがある場合はそして、馬がユニコーンであることを確認することは予想外ではありません。例外は、複雑なif声明ではなく、例外的な状況のためです。いずれにせよ、この問題に例外を使用するのは、実行時にオブジェクトタイプを調べるだけです。ここでのみ、言語機能を誤用してユニコーン以外のオブジェクトをチェックしています。同様にコーディングするかもしれませんif horn > 0 少なくともコレクションを迅速かつ明確に処理し、コードの行数を減らして、他の例外がスローされたときから生じる問題を回避します(たとえば、空のコレクション、またはそのロバの貝殻を測定しようとします)


レガシーコンテキストでif horn > 0は、この問題が最初に解決される方法とほぼ同じです。その後、通常発生する問題は、ライダーとグリッターをチェックしたい場合でありhorn > 0、無関係なコードにいたるところに埋もれています(また、ホーンが0の場合のチェックの欠如により、コードには謎のバグがあります)。また、事実の後に馬をサブクラス化することは通常最も高価な命題であるため、リファクタリングの最後にまだ一緒に書かれているのであれば、私はそれをやろうとはしません。だから、確かに「代替手段は
どれほどい

@moarboilerplateあなたはそれを自分で言って、安くて簡単な解決策で行くと、それは混乱に変わります。これが、この種の問題の解決策としてオブジェクト指向言語が発明された理由です。サブクラスの馬は、最初は高価に思えるかもしれませんが、すぐに費用を負担します。単純ではあるが濁ったソリューションを継続すると、時間とともにコストが増加します。
gbjbaanb

3

質問にはfunctional-programmingタグが付いているため、合計タイプを使用して、2つの種類の馬とパターンマッチングを反映させ、それらの間の曖昧さをなくすことができます。たとえば、F#の場合:

type Equine =
| Horse
| Unicorn of hornLength: float

module equines =

  let averageHornLength (equines : Equine list) =
    equines 
    |> List.choose (fun x -> 
      match x with
      | Unicorn u -> Some(u)
      | _ -> None)
    |> List.average

let herd = [ Horse ; Horse ; Unicorn(35.0) ; Horse ; Unicorn(50.0) ]

printfn "Average horn length in herd : %f" (equines.averageHornLength herd) // prints 42.5

OOPよりも、FPにはデータ/関数の分離という利点があります。これにより、スーパータイプのオブジェクトのリストから特定のサブタイプにダウンキャストするときに、抽象化レベルに違反する(不当な?)

他の回答で提案されたオブジェクト指向ソリューションとは対照的に、パターンマッチングは、別のHorned種がEquineいつか現れた場合に簡単な拡張ポイントも提供します。


2

最後に同じ回答の短い形式を使用するには、本またはWeb記事を読む必要があります。

訪問者パターン

この問題には、馬とユニコーンが混在しています。(リスコフ置換の原則に違反することは、レガシーコードベースの一般的な問題です。)

horseおよびすべてのサブクラスにメソッドを追加します

Horse.visit(EquineVisitor v)

馬のビジターインターフェースは、java / c#では次のようになります。

interface EquineVisitor {
  void visitHorse(Horse z);
  void visitUnicorn(Unicorn z);
}

Unicorn.visit(EquineVisitor v){
   v.visitUnicorn(this);
}

Horse.visit(EquineVisitor v){
   v.visitHorse(this);
}

ホーンを測定するために、今書いています....

class HornMeasurer implements EquineVistor {
    void visitHorse(Horse h){} // ignore horses
    void visitUnicorn(Unicorn u){
         double len = u.getHornLength();
         totalLength+=len;
         unicornCount++;
    }

    double getAverageLength(){
          return totalLength/unicornCount;
    }

    double totalLength=0;
    int unicornCount=0;
}

訪問者のパターンは、リファクタリングと成長を難しくすることで批判されています。

短い回答:デザインパターンの訪問者を使用して、二重ディスパッチを取得します。

https://en.wikipedia.org/wiki/Visitor_patternご覧ください

訪問者の議論については、http://c2.com/cgi/wiki?VisitorPatternも参照してください

GammaらによるDesign Patternsも参照してください。


私は自分で訪問者パターンで答えようとしていました。誰かがすでに言及しているかどうかを見つけるために、驚くべき方法でスクロールダウンしなければなりませんでした!
ベンサーリー

0

あなたのアーキテクチャでユニコーンは馬の亜種Horseであり、それらのいくつかが存在する可能性のあるコレクションを取得する場所に遭遇すると仮定するとUnicorn、私は個人的に最初の方法(.OfType<Unicorn>()...)に行きます。それはあなたの意図を表現する最も簡単な方法だからです。後でやってくる人(3か月後の自分を含む)にとって、そのコードで何を達成しようとしているのかすぐにわかります。馬の中からユニコーンを選び出します。

リストした他の方法は、「ユニコーンですか?」という質問をする別の方法のように感じます。たとえば、ある種の例外に基づいてホーンを測定する方法を使用する場合、次のようなコードがあります。

foreach (var horse in horses)
{
    try
    {
        var length = horse.MeasureHorn();
        //...
    }
    catch (NoHornException e)
    {
        continue;
    }
}

そのため、例外は何かがユニコーンではないという指標になります。そして今、これは本当に例外的な状況ではありませんが、通常のプログラムフローの一部です。そして、代わりに例外を使用するifことは、型チェックを行うよりもさらに汚いようです。

馬の角をチェックするための魔法の値のルートに行くとしましょう。したがって、クラスは次のようになります。

class Horse
{
    public double MeasureHorn() { return -1; }
    //...
}

class Unicorn : Horse
{
    public override double MeasureHorn { return _hornLength; }
    //...
}

今、あなたのHorseクラスはクラスについて知っUnicornていて、気にしないものに対処するための追加のメソッドを持たなければなりません。ここで、から継承するがを持っているPegasusと想像してください。今必要などの方法を、などそしてクラスには、あまりにもこれらのメソッドを取得します。これで、すべてのクラスが相互に認識し合う必要があり、型システムに「これはユニコーンですか?」と尋ねるのを避けるためだけに存在すべきではないメソッドでクラスを汚染しましたZebraHorseHorseFlyMeasureWingsCountStripesUnicorn

では、Horsesに何かを追加して、何かがUnicornすべてのホーン測定を処理するかどうかを言うのはどうでしょうか?さて、このオブジェクトの存在をチェックして、何かがユニコーンであるかどうかを確認する必要があります(これは、あるチェックを別のチェックに置き換えているだけです)。また、水を少し濁らせます。List<Horse> unicornsそれは本当にすべてのユニコーンを保持していますが、型システムとデバッガーは簡単にそれを伝えることはできません。「しかし、それはすべてユニコーンだと知っています」とあなたは言います。さて、名前の名前が不適切だったらどうでしょうか?または、あなたはそれが本当にすべてユニコーンであるという仮定で何かを書いたが、その後要件が変わり、今ではペガシが混ざっていることもあるだろうか?(特にレガシーソフトウェア/ sarcasmでは、このようなことは何も起きないためです。)これで、型システムはユニコーンとのペガシをうまく入れます。List<Unicorn>ペガシやウマを混ぜようとした場合、変数がコンパイラ(またはランタイム環境)として宣言されていれば適切です。

最後に、これらのメソッドはすべて、型システムチェックの代わりにすぎません。個人的には、ここで車輪を再発明するのではなく、他の何千人ものコーダーによって何千回も組み込まれ、テストされたものと同じように私のコードが機能することを願っています。

最終的に、コードは理解できる必要があります。コンピュータは、あなたがそれをどのように書くかに関係なくそれを理解します。あなたはそれをデバッグし、それについて推論することができる人です。仕事を楽にする選択をしてください。何らかの理由で、これらの他の方法のいずれかが、表示されるいくつかのスポットでより明確なコードを上回る利点を提供する場合、それを選択してください。しかし、それはコードベースに依存します。


サイレント例外は間違いなく悪いです-私の提案はチェックされif(horse.IsUnicorn) horse.MeasureHorn();、例外はキャッチされません- !horse.IsUnicornユニコーンを測定するコンテキストで、またはユニコーンではないときにトリガーされますMeasureHorn。こうすることで、例外がスローされたときにエラーをマスクせず、完全に爆発し、修正が必要な兆候です。明らかに、特定のシナリオにのみ適していますが、例外のスローを使用して実行パスを決定しない実装です。
moarboilerplate

0

セマンティックドメインにはIS-A関係があるように見えますが、サブタイプ/継承を使用してこれをモデル化することには少し注意が必要です(特にランタイムタイプの反映のため)。しかし、あなたは間違ったことを怖がっていると思います。サブタイプには確かに危険が伴いますが、実行時にオブジェクトを照会しているという事実は問題ではありません。意味がわかります。

オブジェクト指向プログラミングはIS-A関係の概念に非常に強く依存しており、おそらく2つの有名な重要な概念につながっていると思われます。

しかし、IS-Aの関係を見るための、より機能的なプログラミングベースの別の方法があり、おそらくこれらの困難はないと思います。まず、プログラムで馬とユニコーンをモデル化するため、a HorseUnicorntypeを作成します。これらのタイプの値は何ですか?まあ、私はこれを言うだろう:

  1. これらのタイプの値は、それぞれ馬とユニコーンの表現または説明です。
  2. これらは、スキーマ化された表現または説明です。自由形式ではなく、非常に厳しいルールに従って構築されています。

それは当たり前のように聞こえるかもしれませんが、人々が円楕円問題のような問題に取り組む方法の1つは、これらの点を十分に気にしないことです。すべての円は楕円ですが、それは、円のすべての図式化された説明が、異なるスキーマに従って楕円の図式化された説明になることを意味しません。つまり、円が楕円だからといって、a Circleがであるという意味ではありませんEllipse。しかし、それは次のことを意味します:

  1. すべての(スキーム化された円の説明)を、同じ円を説明する(異なるタイプの説明)に変換する合計関数があります。CircleEllipse
  2. を取り、円を記述する場合、対応するを返す部分関数がありEllipseますCircle

したがって、関数型プログラミングの用語では、Unicorn型はまったくサブタイプである必要はなくHorse、次のような操作が必要です。

-- Convert any unicorn-description of into a horse-description that
-- describes the same unicorns.
toHorse :: Unicorn -> Horse

-- If the horse described by the given horse-description is a unicorn,
-- then return a unicorn-description of that unicorn, otherwise return
-- nothing.
toUnicorn :: Horse -> Maybe Unicorn

そして、以下のtoUnicorn正反対である必要がありますtoHorse

toUnicorn (toHorse x) = Just x

HaskellのMaybeタイプは、他の言語で「オプション」タイプと呼ばれるものです。たとえば、Java 8 Optional<Unicorn>タイプは、an Unicornまたはnoneです。2つの選択肢(例外をスローするか、「デフォルト値またはマジック値」を返す)は、オプションタイプに非常に似ていることに注意してください。

基本的に、ここでやったことは、サブタイプや継承を使用せずに、タイプと機能の観点からIS-A関係の概念を再構築することです。私がこれから取り上げるのは:

  1. モデルには型が必要Horseです。
  2. Horse型は、任意の値がユニコーンを記述しているかどうかを明確に決定するのに十分な情報を符号化する必要があります。
  3. Horseタイプの一部の操作では、その情報を公開して、タイプのクライアントが特定Horseのユニコーンであるかどうかを確認できるようにする必要があります。
  4. このHorseタイプのクライアントは、実行時にこれらの後者の操作を使用して、ユニコーンと馬を区別する必要があります。

したがって、これは基本的に「Horseユニコーンかどうかを尋ねる」モデルです。あなたはそのモデルに警戒していますが、私は間違っていると思います。Horsesのリストを提供する場合、タイプが保証するのは、リスト内のアイテムが記述するものが馬であるということだけです。したがって、必然的に、それらのどれがユニコーンであるかを判断するために実行時に何かをする必要があります。したがって、それを回避することはできません。それを実現する操作を実装する必要があると思います。

オブジェクト指向プログラミングでは、これを行うためのよく知られた方法は次のとおりです。

  • Horseタイプを持っている;
  • 持っているUnicornのサブタイプとしてHorse
  • ランタイムタイプリフレクションを、特定のHorseがであるかどうかを識別するクライアントアクセス可能な操作として使用しますUnicorn

上記に示した「ものと説明」の角度から見ると、これには大きな弱点があります。

  • あなたは何を持っている場合はHorseユニコーンを記述していないが、インスタンスUnicornのインスタンスを?

初めに戻ると、これは、このIS-A関係のモデリングにサブタイピングとダウンキャストを使用することについて本当に恐ろしいことだと思います。ランタイムチェックを行う必要はありません。タイポグラフィを少し乱用し、HorseそれがUnicornインスタンスであるHorseかどうかを尋ねることは、それがユニコーンであるかどうかを尋ねることと同義ではありません(ユニコーンHorseでもある馬の説明であるかどうか)。HorsesクライアントがHorseユニコーンを記述するを構築しようとするたびにUnicornクラスがインスタンス化されるように、構築するコードをカプセル化するためにプログラムが非常に長い時間をかけていない限り、そうではありません。私の経験では、プログラマがこれを慎重に行うことはめったにありません。

したがって、HorsesをUnicornsに変換するダウンキャスト以外の明示的な操作があるアプローチを採用します。これは、次のHorseタイプのメソッドのいずれかです。

interface Horse {
    // ...
    Optional<Unicorn> toUnicorn();
}

...または、外部オブジェクト(「馬がユニコーンであるかどうかを通知する馬上の別個のオブジェクト」)の場合もあります。

class HorseToUnicornCoercion {
    Optional<Unicorn> convert(Horse horse) {
       // ...
    }
}

これらのどちらを選択するかは、プログラムの編成方法に依存します。どちらの場合も、Horse -> Maybe Unicorn上記の操作と同等であり、異なる方法でパッケージ化するだけです(Horseタイプに必要な操作に波及効果があることは明らかです)クライアントに公開します)。


-1

別の回答でのOPのコメントは、質問を明確にしたと思いました

それも質問の質問の一部です。私が馬の群れを持ち、そのうちのいくつかが概念的にユニコーンである場合、問題をあまりにも多くの負の影響なしにきれいに解決できるように、それらはどのように存在するべきですか?

そういう風に言いましたが、もっと情報が必要だと思います。答えはおそらくいくつかのことに依存します:

  • 私たちの言語施設。例えば、私はおそらくルビー、javascript、Javaでこれに異なるアプローチをするでしょう。
  • 概念そのもの:何馬と何であるユニコーン?それぞれにどのデータが関連付けられていますか?ホーン以外はまったく同じですか、それとも他の違いがありますか?
  • ホーンの長さの平均を取る以外に、他にどのように使用しますか?そして、群れはどうですか?たぶん、私たちもそれらをモデル化するべきですか?他の場所で使用しますか? herd.averageHornLength()概念モデルと一致しているようです。
  • 馬とユニコーンのオブジェクトはどのように作成されますか?リファクタリングの範囲内でそのコードを変更していますか?

ただし、一般的には、ここでは継承とサブタイプについても考えません。オブジェクトのリストがあります。これらのオブジェクトの一部は、おそらくhornLength()メソッドを持っているため、ユニコーンとして識別できます。このユニコーン固有のプロパティに基づいてリストをフィルタリングします。現在、問題はユニコーンのリストのホーンの長さの平均化に縮小されています。

OP、まだ誤解している場合は教えてください...


1
公正なポイント。問題がさらに抽象化されないようにするには、いくつかの合理的な仮定を立てる必要があります:1)強く型付けされた言語2)群れは馬を1つの型に制限します。 。変更できるものについては、必ずしも制限はありませんが、変更の種類ごとに固有の結果があります
...-moarboilerplate

群れが馬を1つのタイプに制限する場合、唯一の選択肢は継承ではありません(そのオプションは好きではありません)または、ラッパーオブジェクト(たとえばHerdMember)を馬またはユニコーンで初期化します(馬とユニコーンをサブタイプの関係から解放します))。 HerdMemberその後、自由に実装isUnicorn()できますが、適切であると思われ、私が提案するフィルタリングソリューションを次に示します。
ヨナ

一部の言語では、hornLength()を混在させることができます。その場合、有効な解決策になる可能性があります。ただし、タイピングの柔軟性が低い言語では、同じことを行うためにいくつかのハック手法に頼る必要があります。または、馬にホーンレングスを付けるなど、馬がコードを混乱させる可能性があるため、概念的にはホーンがあります。また、デフォルト値を含む数学的計算を行うと、結果が
歪む

ただし、ミックスインは、実行されていない限り、別の名前で継承されます。「馬には概念的に角がない」というコメントは、馬とユニコーンをモデル化する方法とそれらの相互関係を含める必要がある場合、それらが何であるかをもっと知る必要があるという私のコメントに関連しています。デフォルト値を含むソリューションは、手に負えない間違ったものです。
ヨナ

この問題の特定の症状に対する正確な解決策を得るには、多くのコンテキストが必要です。角のある馬についてのあなたの質問に答えて、それをミックスインに結びつけるために、私はユニコーンではない馬にhornLengthが混入するシナリオを考えていました。例外をスローするhornLengthのデフォルト実装を持つScalaトレイトを検討してください。ユニコーン型はその実装をオーバーライドでき、馬がそれをhornLengthが評価されるコンテキストにした場合、それは例外です。
-moarboilerplate

-2

IEnumerableを返すGetUnicorns()メソッドは、私にとって最もエレガントで柔軟で普遍的なソリューションのようです。この方法で、クラスタイプや特定のプロパティの値だけでなく、馬がユニコーンとして通過するかどうかを決定する特性(の組み合わせ)を処理できます。


これに同意する。メイソン・ウィーラーは彼の答えにも良い解決策がありますが、さまざまな場所でさまざまな理由でユニコーンを選び出す必要がある場合、コードには多くのhorses.ofType<Unicorn>...構造があります。GetUnicorns機能を持つことはワンライナーですが、呼び出し側の観点からは、馬とユニコーンの関係の変更に対してさらに抵抗力があります。
シェーズ

@Ryanを返す場合IEnumerable<Horse>、ユニコーンの条件は1か所にありますが、カプセル化されているため、呼び出し元はユニコーンが必要な理由を推測する必要があります(今日のスープを注文することでクラムチャウダーを入手できますが、それはありません) tは同じことをすることで明日手に入れることを意味します)。さらに、のホーンのデフォルト値を公開する必要がありますHorse。場合はUnicorn、独自のタイプである、あなたはオーバーヘッドを導入することができますタイプのマッピングを、新しい型を作成し、維持しなければなりません。
-moarboilerplate

1
@moarboilerplate:ソリューションをサポートするものはすべて検討します。美しさの部分は、ユニコーンの実装の詳細から独立していることです。データメンバー、クラス、または時刻に基づいて区別するかどうか(月が私が知っているすべての人にとって正しい場合、真夜中にこれらの馬はすべてユニコーンに変わる可能性があります)、解決策はありますが、インターフェイスは同じままです。
マーティンマート
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.