この場合、instanceofを置き換えるにはどうすればよいでしょうか?
-
05-07-2019 - |
質問
CompareCriteriaを比較しようとしています。「between」と「inArray」または「greaterThan」などの単純なもの。これらのクラスにはポリモーフィズムを使用します。CompareCriteria インターフェイスから共有されるメソッドの 1 つは、「matchCompareCriteria」です。
私が避けようとしているのは、各クラスが照合する必要があるcompareCriteriaのタイプをチェックすることです。たとえば、inArray オブジェクトは、matchCompareCriteria が inArray オブジェクトに渡されたかどうかをチェックし、そうでない場合は、比較方法がわかっている場合は false を返します。
おそらくこの場合、instanceof は完全に正当です (オブジェクトは自分自身のことを知っています) が、それでもそれを回避する可能な方法を検討しています。何か案は?
疑似コードの例:
betweenXandY = create new between class(x, y)
greaterThanZ = create new greaterThan class(z)
greaterThanZ.matchCompareCriteria(betweenXandY)
X と Y が Z より大きい場合は true を返します。
編集:
1)instanceof は、今のところ matchCompareCriteria メソッドで必要とされるものです。取り除きたいのですが
2) matchCompareCritera は、compareCriteria が別の CompareCriteria に含まれているかどうかをチェックします。一方の可能な値がすべて他方に含まれている場合、true を返します。CompareCriteria の多くの組み合わせでは、それらを比較することさえ意味がないため、 false が返されます (betweenAlfa と betweenNum が互換性がないように)。
解決
あなたが説明している問題は次のように呼ばれます 二重発送. 。この名前は、2 つのオブジェクトのタイプに基づいてコードのどの部分を実行 (ディスパッチ) するかを決定する必要があるという事実に由来しています (したがって、次のようになります)。ダブル)。
通常、OO では単一のディスパッチが行われます。つまり、オブジェクトのメソッドを呼び出すと、そのオブジェクトのメソッドの実装が実行されます。
あなたの場合、2 つのオブジェクトがあり、実行される実装は両方のオブジェクトの型に応じて異なります。基本的に、これまで標準的な OO 状況のみを扱ってきた場合には、これによって「間違っていると感じる」という結合が暗示されています。しかし、それは本当に間違っているわけではありません。OO の基本機能が解決に直接適している問題領域からわずかに外れるだけです。
動的言語 (または、この目的には十分動的であるリフレクションを備えた静的型付け言語) を使用している場合は、基本クラスのディスパッチャ メソッドを使用してこれを実装できます。疑似コードでは次のようになります。
class OperatorBase
{
bool matchCompareCriteria(var other)
{
var comparisonMethod = this.GetMethod("matchCompareCriteria" + other.TypeName);
if (comparisonMethod == null)
return false;
return comparisonMethod(other);
}
}
ここでは、言語のすべてのクラスに、という組み込みメソッドがあると想像しています。 GetMethod
これにより、名前でメソッドを検索したり、オブジェクトの型の名前を取得するすべてのオブジェクトの TypeName プロパティを検索したりすることができます。したがって、他のクラスが GreaterThan
, 派生クラスには matchCompareCriteriaGreaterThan というメソッドがあるので、そのメソッドを呼び出します。
class SomeOperator : Base
{
bool matchCompareCriteriaGreaterThan(var other)
{
// 'other' is definitely a GreaterThan, no need to check
}
}
したがって、正しい名前のメソッドを記述するだけでディスパッチが行われます。
引数の型によるメソッドのオーバーロードをサポートする静的型付け言語では、連結された命名規則を考案する必要を避けることができます。たとえば、C# では次のようになります。
class OperatorBase
{
public bool CompareWith(object other)
{
var compare = GetType().GetMethod("CompareWithType", new[] { other.GetType() });
if (compare == null)
return false;
return (bool)compare.Invoke(this, new[] { other });
}
}
class GreaterThan : OperatorBase { }
class LessThan : OperatorBase { }
class WithinRange : OperatorBase
{
// Just write whatever versions of CompareWithType you need.
public bool CompareWithType(GreaterThan gt)
{
return true;
}
public bool CompareWithType(LessThan gt)
{
return true;
}
}
class Program
{
static void Main(string[] args)
{
GreaterThan gt = new GreaterThan();
WithinRange wr = new WithinRange();
Console.WriteLine(wr.CompareWith(gt));
}
}
新しいタイプをモデルに追加する場合は、以前のすべてのタイプを調べて、何らかの方法で新しいタイプと対話する必要があるかどうかを自問する必要があります。その結果 毎 型は対話方法を定義する必要があります ほかのすべての type - インタラクションが非常に単純なデフォルト (「返す以外は何もしない」など) であっても、 true
")。その単純なデフォルトであっても、意図的に選択しなければならないことを表しています。これは、最も一般的なケースでは明示的にコードを記述する必要がないという利便性によって隠蔽されています。
したがって、すべての型間の関係をすべてのオブジェクトに分散させるよりも、外部テーブルにすべての型間の関係を取得する方が合理的である可能性があります。一元化することの価値は、タイプ間の重要なやり取りを見逃していないかどうかを即座に確認できることです。
したがって、型を別の辞書にマップする辞書/マップ/ハッシュテーブル (言語での呼び方は何でも) を作成できます。2 番目の辞書は、2 番目のタイプをこれら 2 つのタイプの適切な比較関数にマップします。一般的な CompareWith 関数は、そのデータ構造を使用して、呼び出す適切な比較関数を検索します。
どのアプローチが正しいかは、モデル内に最終的にどれだけのタイプが含まれる可能性があるかによって決まります。
他のヒント
instanceof
を参照しているため、ここではJavaで作業していると想定しています。これにより、オーバーロードを利用できます。 SomeInterface
と呼ばれるインターフェースを考えてみましょう。このインターフェースには単一のメソッドがあります:
public interface SomeInterface {
public boolean test (SomeInterface s);
}
今、 SomeInterface
を実装する2つの(巧妙な名前の)クラスを定義します: Some1
と Some2
。 Some2
は退屈です: test
は常にfalseを返します。ただし、Some1は、 Some2
:
test
関数をオーバーライドします。
public class Some1 implements SomeInterface {
public boolean test (SomeInterface s) {
return false;
}
public boolean test (Some2 s) {
return true;
}
}
これにより、ifステートメントの行ごとに型チェックを行う必要がなくなります。しかし、注意点があります。次のコードを検討してください:
Some1 s1 = new Some1 ();
Some2 s2 = new Some2 ();
SomeInterface inter = new Some2 ();
System.out.println(s1.test(s2)); // true
System.out.println(s2.test(s1)); // false
System.out.println(s1.test(inter)); // false
その3番目のテストを参照してください。 inter
は Some2
型ですが、代わりに SomeInterface
として扱われます。オーバーロードの解決は、Javaでのコンパイル時に決定されるため、まったく役に立たない可能性があります。
これにより、1つ目の場所に戻ります。 instanceof
(実行時に評価されます)を使用します。その方法でそれをしても、それはまだ悪い設計です。各クラスは、他のすべてのクラスについて知る必要があります。別のクラスを追加する場合、既存のすべてのクラスに戻って、新しいクラスを処理する機能を追加する必要があります。これは急いでひどく維持できなくなります。これは、設計が悪いことを示す良い兆候です。
再設計は順調ですが、多くの情報がなければ、適切な方向に特に良いプッシュを与えることはできません。
Criteriaというスーパークラスまたはインターフェイスを作成する必要があります。その後、各具象サブクラスはCriteriaインターフェースを実装します。間に、より大きいなどが基準です。
Criteriaクラスは、Criteriaを受け入れるmatchCompareCriteriaメソッドを指定します。実際のロジックはサブクラスにあります。
戦略設計パターンまたはテンプレート設計パターンのいずれかを探しています。
私がよく理解していれば、メソッドは型チェックに依存しています。それを避けるのは非常に難しく、多態性は問題の解決に失敗します。この例では、inArrayはパラメーターのタイプをチェックする必要があります。これは、メソッドの動作がこれに依存するためです 。ポリモーフィズムによってそれを行うことはできません。つまり、クラスにポリモーフィックメソッドを配置してこのケースを処理することはできません。これは、matchCompareCriteriaがパラメーターの動作ではなく、パラメーターの type に依存するためです。
instanceof
を使用しないルールは、オブジェクトのタイプをチェックして、どの動作を行うかを選択するときに有効です。明らかに、その動作は、タイプをチェックするさまざまなオブジェクトに属します。ただし、この場合、 your オブジェクトの動作は、渡されるオブジェクトのタイプに依存し、以前のように呼び出されたオブジェクトではなく、呼び出し元のオブジェクトに属します。このケースは、 equals()
をオーバーライドする場合と似ています。渡されたオブジェクトが this
オブジェクトと同じ型になるように型チェックを行い、動作を実装します。テストが失敗した場合はfalseを返します。それ以外の場合は、同等性テストを実行します。
結論:この場合、 instanceof
を使用しても問題ありません。
こちらは、Steve Yeggeによる記事です。シンプルでわかりやすい例を使用すると思います。これはあなたの問題にぴったりだと思います。
覚えておいてください:ポリモーフィズムは、そうでない場合を除いて優れています。 :)
Smalltalkのアプローチは、階層により多くのレイヤーを導入することです。したがって、 between と greaterThan は rangedCompareCriteria (または何か)のサブクラスであり、 rangeCompareCriteria :: matchCompareCriteria は< strong> true 自身の2つのインスタンスが比較可能であるかどうかを尋ねられた場合。
と言えば、おそらく&quot; matchCompareCriteria&quot;の名前を変更したいでしょう。意図をもう少し良く表現するものに。