マーカーインターフェイスの目的は何ですか?
回答:
これは、「ミッチウィート」の反応に基づく接線です。
一般に、人々がフレームワークの設計ガイドラインを引用しているのを目にするときはいつでも、次のことを述べておきます。
通常、フレームワークの設計ガイドラインはほとんど無視する必要があります。
これは、フレームワークの設計ガイドラインに問題があるためではありません。.NETフレームワークは素晴らしいクラスライブラリだと思います。その素晴らしさの多くは、フレームワークの設計ガイドラインに基づいています。
ただし、設計ガイドラインは、ほとんどのプログラマーが記述したほとんどのコードには適用されません。彼らの目的は、何百万人もの開発者が使用する大きなフレームワークの作成を可能にすることであり、ライブラリの記述をより効率的にすることではありません。
その中の多くの提案は、次のことを行うようにあなたを導くことができます:
.netフレームワークは非常に大きいです。それは非常に大きいので、誰もがそのすべての側面について詳細な知識を持っていると仮定することは絶対に無理です。実際、ほとんどのプログラマは、これまでに使用したことがないフレームワークの部分に頻繁に遭遇すると想定する方がはるかに安全です。
その場合、APIデザイナーの主な目標は次のとおりです。
フレームワーク設計ガイドラインは、開発者にこれらの目標を達成するコードを作成するように促します。
つまり、コードの複製を意味する場合でも、継承のレイヤーを回避することや、共有ヘルパーを使用するのではなく、すべての例外をスローしてコードを「エントリポイント」にプッシュすることにより(デバッガーでスタックトレースがより意味をなすようになります)、多くのことを行います。他の同様のもの。
これらのガイドラインがマーカーインターフェイスの代わりに属性を使用することを推奨する主な理由は、マーカーインターフェイスを削除すると、クラスライブラリの継承構造がはるかに扱いやすくなるためです。15のタイプと2つの階層の階層を持つクラス図と比較して、30のタイプと6つの階層の継承階層を持つクラスダイアグラムは非常に困難です。
実際にAPIを使用している何百万人もの開発者がいる場合、またはコードベースが非常に大きい場合(たとえば、LOCが10万を超える場合)、これらのガイドラインに従うことは非常に役立ちます。
500万人の開発者がAPIの学習に60分を費やすのではなく15分を費やすと、結果として428人年の正味の節約になります。それは長い時間です。
ただし、ほとんどのプロジェクトには、何百万人もの開発者や10万以上のLOCは含まれません。典型的なプロジェクトでは、たとえば4人の開発者と約50Kの場所で、想定のセットは大きく異なります。チームの開発者は、コードがどのように機能するかをよりよく理解できます。つまり、高品質のコードを迅速に作成し、バグの量と変更に必要な労力を削減するために最適化する方がはるかに理にかなっています。
.netフレームワークと整合性のあるコードの開発に1週間を費やすのに対し、変更が簡単でバグの少ないコードを8時間で作成すると、次のような結果になります。
コストを吸収する他の開発者が4,999,999人いないと、通常、それだけの価値はありません。
たとえば、マーカーインターフェイスのテストは1つの「is」式になり、属性を探すコードが少なくなります。
だから私のアドバイスは:
virtual protected
テンプレートメソッドDoSomethingCore
に名前DoSomething
を付けることはそれほど多くの追加作業ではなく、テンプレートメソッドであることを明確に伝えます... IMNSHO、APIを考慮せずにアプリケーションを作成する人々(But.. I'm not a framework developer, I don't care about my API!
)は、正確に大量の複製(また、ドキュメント化されておらず、通常は読み取り不能なコードです。その逆ではありません。
マーカーインターフェイスは、クラスの機能を実行時に特定のインターフェイスを実装するものとしてマークするために使用されます。
インターフェイスデザインと.NET型設計ガイドライン-インターフェイスデザインは、 C#の属性を使用しての賛成でマーカー・インターフェースの使用を思いとどまらが、@Jay Bazuziが指摘するように、属性のよりマーカインタフェースをチェックする方が簡単です:o is I
これの代わりに:
public interface IFooAssignable {}
public class FooAssignableAttribute : IFooAssignable
{
...
}
.NETガイドラインでは、次のことを行うことを推奨しています。
public class FooAssignableAttribute : Attribute
{
...
}
[FooAssignable]
public class Foo
{
...
}
他のすべての回答では「回避する必要がある」と述べているため、理由を説明しておくと役立ちます。
まず、マーカーインターフェイスが使用される理由:マーカーインターフェイスは、それを実装するオブジェクトを使用しているコードが、そのインターフェイスを実装しているかどうかを確認し、実装している場合はオブジェクトを異なる方法で処理できるようにするために存在します。
このアプローチの問題は、カプセル化が壊れることです。オブジェクト自体が、外部での使用方法を間接的に制御できるようになりました。さらに、それは使用されるシステムの知識を持っています。マーカーインターフェイスを適用することにより、クラス定義は、マーカーの存在をチェックする場所で使用されることを期待していることを示唆しています。使用環境に関する暗黙の知識があり、使用方法を定義しようとしています。これは、独自のスコープの外に完全に存在するシステムの一部の実装に関する知識を持っているため、カプセル化の概念に反します。
実用レベルでは、これにより移植性と再利用性が低下します。クラスが別のアプリケーションで再利用される場合、インターフェースもコピーする必要があり、新しい環境では意味がなく、完全に冗長になる場合があります。
したがって、「マーカー」はクラスに関するメタデータです。このメタデータはクラス自体では使用されず、オブジェクトを特定の方法で処理できるように、(一部の!)外部クライアントコードに対してのみ意味があります。これはクライアントコードに対してのみ意味があるため、メタデータはクラスAPIではなくクライアントコード内にある必要があります。
「マーカーインターフェース」と通常のインターフェースの違いは、メソッド付きのインターフェースはそれがどのように使用できるかを外の世界に伝えるのに対し、空のインターフェースはそれがどのように使用されるべきかを外の世界に伝えているということです。
IConstructableFromString<T>
で、クラスT
がIConstructableFromString<T>
静的メンバーを持っている場合にのみ実装できることを指定している場合
public static T ProduceFromString(String params);
、インターフェイスのコンパニオンクラスはメソッドを提供できますpublic static T ProduceFromString<T>(String params) where T:IConstructableFromString<T>
。クライアントコードにのようなメソッドがある場合T[] MakeManyThings<T>() where T:IConstructableFromString<T>
、クライアントコードを変更する必要なく、クライアントコードで機能する新しいタイプを定義できます。メタデータがクライアントコードに含まれている場合、既存のクライアントで使用する新しいタイプを作成することはできません。
T
それを使用するクラスの間のコントラクトは、IConstructableFromString<T>
いくつかの動作を記述するインターフェイス内にメソッドがあるため、マーカーインターフェイスではありません。
ProduceFromString
上記の例で静的メソッドを検索して実行する実際のプロセスには、必要な機能を実装するために期待されるクラスを示すマーカーとして使用されることを除いて、インターフェースは何らかの方法で使用されます。
言語が差別的な和集合をサポートしていない場合、マーカーインターフェイスが必要な悪になる場合があります体型をます。
型がA、B、またはCのいずれかでなければならない引数を必要とするメソッドを定義するとします。多くの関数優先言語(F#など)では、そのような型は次のように明確に定義できます。
type Arg =
| AArg of A
| BArg of B
| CArg of C
ただし、C#などのOO優先言語では、これは不可能です。ここで同様のことを行う唯一の方法は、インターフェースIArgを定義し、それを使ってA、B、Cに「マーク」を付けることです。
もちろん、単に「オブジェクト」型を引数として受け入れることでマーカーインターフェイスの使用を回避できますが、表現力とある程度の型の安全性が失われます。
識別された共用体型は非常に有用であり、少なくとも30年間関数型言語で存在しています。奇妙なことに、今日まで、すべての主流のOO言語はこの機能を無視しています。ただし、実際には関数型プログラミング自体とは関係ありませんが、型システムに属しています。
Foo<T>
あらゆるタイプの静的フィールドの別のセットを持ってT
、それは一般的なクラスを持って処理するためのデリゲートを含む静的フィールドを含むことは難しいことではありませんT
、そしてクラスがあるすべてのタイプを処理するための機能で、これらのフィールドに事前で動作するはずです。型にジェネリックインターフェイス制約を使用するとT
、コンパイラー時に、提供された型が有効であると少なくとも主張していることを確認します。
マーカーインターフェイスは、空のインターフェイスにすぎません。クラスは、何らかの理由で使用されるメタデータとしてこのインターフェイスを実装します。C#では、他の言語でマーカーインターフェイスを使用するのと同じ理由で、より一般的に属性を使用してクラスをマークアップします。
マーカーインターフェイスを使用すると、すべての子孫クラスに適用される方法でクラスにタグを付けることができます。「純粋な」マーカーインターフェイスは、何も定義または継承しません。より有用なタイプのマーカーインターフェイスは、別のインターフェイスを "継承"するものの、新しいメンバーを定義しないタイプです。たとえば、「IReadableFoo」というインターフェースがある場合、「IImmutableFoo」というインターフェースを定義することもできます。これは「Foo」のように動作しますが、それを使用する人は誰もその値を変更しないと約束します。IImmutableFooを受け入れるルーチンは、IReadableFooと同様にそれを使用できますが、ルーチンはIImmutableFooの実装として宣言されたクラスのみを受け入れます。
「純粋な」マーカーインターフェースの多くの用途は考えられません。タイプがIEqualityComparerも実装している場合でも、IDoNotUseEqualityComparerを実装したすべてのタイプのEqualityComparer(of T).DefaultがObject.Equalsを返すかどうかは、私が考えることができる唯一のものです。これにより、Liskov置換の原則に違反することなく、封印されていない不変の型を持つことができます。型が等価性テストに関連するすべてのメソッドを封印している場合、派生型はフィールドを追加して変更可能にすることができますが、そのようなフィールドの変更はできませんベースタイプのメソッドを使用して表示されます。封印されていない不変のクラスがあり、EqualityComparer.Defaultの使用を回避するか、派生クラスを信頼してIEqualityComparerを実装しないことは、恐ろしいことではありません。
これら2つの拡張メソッドは、スコットが属性よりもマーカーインターフェイスを支持することを主張するほとんどの問題を解決します。
public static bool HasAttribute<T>(this ICustomAttributeProvider self)
where T : Attribute
{
return self.GetCustomAttributes(true).Any(o => o is T);
}
public static bool HasAttribute<T>(this object self)
where T : Attribute
{
return self != null && self.GetType().HasAttribute<T>()
}
今あなたは持っています:
if (o.HasAttribute<FooAssignableAttribute>())
{
//...
}
対:
if (o is IFooAssignable)
{
//...
}
Scottが主張しているように、APIの構築に2番目のパターンと比較して最初のパターンの5倍の時間がかかるかどうかはわかりません。