はじめに
C#における(≒.NETにおける)インタフェースの命名パターンを個人的に整理するとともに、それぞれのインタフェースの役割と存在意義を考えてみます。
標準ライブラリのインタフェース
パターン1 : I
+ 動詞-able
単一のメソッドを定義するインタフェースはこのように命名されることが多いです。
**『〇〇できる』**の意味のインタフェースですので、メソッドもそれに応じたものを持ちます。
- 例
System.IDisposable
System.IComparable<T>
System.Collections.Generic.IEnumerable<T>
サブバターン1-1
動詞と同名のメソッドを1つだけ持つパターンです。
個人的な見解ですが、このパターンがインタフェースの基礎である、と考えるとインタフェースそのものの理解がしやすいと思います。
// 『破棄できる』インタフェース
public interface IDisposable {
// 《自分自身を》破棄する
void Dispose();
}
// 『比較できる』インタフェース
public interface IComparable<in T> {
// 《自分自身を》T型の他のオブジェクトと『比較する』
int CompareTo(T other);
}
サブバターン1-2
別のオブジェクトを取得するメソッドを1つだけ持つパターンです。
取得したオブジェクトは後述の_サブパターン2-2_のインタフェースを持ちます。
動詞で表される操作が複雑な場合に利用されます。
// 『列挙できる』インタフェース
public interface IEnumerable<out T> {
// 『《自分自身を》「列挙する人」を取得する』
IEnumerator<T> GetEnumerator();
}
// ややこしいので非ジェネリクスのIEnumerableの継承は省略
パターン2 : I
+ 動詞-or/動詞-er
**『〇〇する者』**を意味するインタフェースです。
サブパターンによって性質が少し異なります。
- 例
System.ICustomFormatter
System.Collections.Generic.IEnumerator<T>
サブバターン2-1
動詞と同名のメソッドを1つだけ持つパターンです。
_サブパターン1-1_に似ていますが、動詞の対象が別オブジェクトであることが大きく違います。
// 『フォーマットする人』インタフェース
public interface ICustomFormatter {
// 受け取った引数を元に『フォーマットする』
String Format (String format, Object arg, IFormatProvider formatProvider);
}
サブバターン2-2
メソッドが複数含まれているパターンです。
複数のメソッドを組み合わせて、1つの機能を実現します。
操作対象から予め操作を任されている、とも取れます。
// 『列挙する人』インタフェース
public interface IEnumerator<out T> : IDisposable {
// 《列挙する対象を》次の要素へ移動する
bool MoveNext();
// 《列挙する対象を》現在の要素を取得する
T Current { get; }
// 《列挙する対象を》初期状態に戻す
void Reset();
}
// ややこしいので非ジェネリクスのIEnumeratorの継承は省略
パターン3 : I
+ 名詞
名詞に対応する標準的なクラスの操作を定義したものです。
このパターンのインタフェースは良く設計されていると強力です。
- 例
System.Collections.Generic.IList<>
System.Collections.Generic.ICollection<>
// 『コレクション』を表すインタフェース
public interface ICollection<T> : IEnumerable<T> {
// 《自分自身の》持つ要素数
int Count { get; }
// 《自分自身が》読み取り専用か否か
bool IsReadOnly { get; }
// 《自分自身に》T型の新しい要素を追加する
void Add(T item);
// 《自分自身の》要素を空にする
void Clear();
// 《自分自身に》対象の要素が含まれているか判断する
bool Contains(T item);
// 《自分自身の》arrayIndex番目の要素以降をarrayにコピーする
void CopyTo(T[] array, int arrayIndex);
// 《自分自身から》対象の要素を削除する
bool Remove(T item);
}
// 『リスト』を表すインタフェース
// 『リスト』は『コレクション』に加えて以下のことが可能
public interface IList<T> : ICollection<T> {
// 《自分自身の》index番目の要素を取得、設定する
T this[int index] { get; set; }
// 《自分自身の》何番目に対象の要素があるか判断する
int IndexOf(T item);
// 《自分自身の》index番目に対象の要素を挿入する
void Insert(int index, T item);
// 《自分自身の》index番目の要素を削除する
void RemoveAt(int index);
}
ちなみに、LinkedList
は ICollection<T>
は実装するけれど、 IList<T>
は実装していません。このあたりも掘り下げてみると面白そうですね。
自作する場合
さて、各パターンを使った自作インタフェースの例を挙げることで、想像の幅を広げてみましょう。
1-1
サブパターン1-1 はシンプルですね。この例のほかにも、動物を例にした_IFlyable(飛べる)_インタフェースなんかも例えに向いています。
// 『印刷できる』
interface IPrintable {
// 《自分自身を》印刷する
void Print();
}
// 例えば、印刷できるドキュメント型
class Document : IPrintable { /*...*/ }
2-1
サブパターン2-1 もシンプルです。メソッドが引数を持つのが特徴ですね。
// 『カッター(切るもの)』
interface ICutter {
// 《他のものを》切る
Paper[] Cut(Paper paper);
}
// 例えば、他の物を切ることができるハサミ型
class Scissor : ICutter { /*...*/ }
1-2 と 2-2
サブパターン1-2,2-2 はイメージを膨らませるために、合わせて作ってみました。
「テレビ」と「リモコン」みたいなイメージが個人的にはしっくりきます。
一つのテレビに対して複数のリモコンを作れるのは、少し感覚が違うかもしれませんね。より良い例えを思いついたら更新します(^^;
// 『リモコンで操作できる』
interface IRemoteControllable {
// 《自分自身を》操作するためのリモコンを取得する
IRemoteController GetController();
}
// 『リモコン(遠隔操作するもの)』
interface IRemoteController {
// 『《遠隔操作する対象の》電源を入切する』
void Power(bool turningOn);
// 『《遠隔操作する対象の》チャネルを変更する』
void ChangeChannel(int channel);
}
// リモコン操作可能なテレビ型
class Television : IRemoteControllable {
bool _powerOn;
int _channel;
// 自分自身を対象とするリモコンを返却
public IRemoteController GetController() {
return new RemoteController(this);
}
// リモコン型
class RemoteController : IRemoteController {
Television _television;
// コンストラクタで対象のテレビを受け取る
public RemoteController(Television television) {
_television = television;
}
public void ChangeChannel(int channel) {
_television._channel = channel;
}
public void Power(bool turningOn) {
_television._powerOn = turningOn;
}
}
}
3
パターン3 はメソッドが多いパターンで、教科書的な説明ではこのパターン3が使われることも多いです《IList<int> list = new List<int>();
のようなパターン》
一方で、このパターンで説明すると、インタフェースの意義は分かりにくかったりします。
もしあなたがオブジェクト指向言語の教育係になったなら、このパターンではなく、サブパターン1-1のようなインタフェースを例として挙げることをお勧めします。
ちょっと否定的な書き方をしてしまいましたが、それでもDIなどで出番が多いのがこのパターンです。上手く付き合ってインタフェースのスペシャリストになりましょう。
以下、良い例とは言い難いのですが、例を示しておきます。
// 『車』
interface ICar {
// 『《自分自身の》エンジンを開始する』
void StartEngine();
// 『《自分自身を》加速する』
void Accel();
// 『《自分自身を》減速する』
void Break();
}
// 様々な車
class Car : ICar { /* ・・・*/ }
class Bus : ICar { /* ・・・*/ }
まとめ
No | パターン | 特徴 | Method |
---|---|---|---|
1-1 | -able | 動詞と同名のメソッド | 少 |
1-2 | -able | 複雑な操作をする別オブジェクト _(パターン2-2)_を生成する |
少 |
2-1 | -or / -er | 他のオブジェクトを操作するメソッド | 少 |
2-2 | -or / -er | 状態を管理するメソッド | 多 |
3 | 名詞 | 一揃いのメソッド群 | 多 |
上記以外の例もあるとは思いますが、大雑把な分類としては有効でしょう。
今回まとめられてないパターン
今回は省略したパターン
今後何かで記事を書くかもしれません…
-
System.ComponentModel.INotifyPropertyChanged
- Notify なので、動詞句型の命名
-
PropertyChanged
というevent
を持つ
おわりに
命名パターンごとに、少しずつ性格が違うインタフェースであることが分かります。標準のクラスライブラリを学習する場合にも、命名に着目することで理解がより容易になると思います。
また、自分でインタフェースを設計するときには、命名を考えることで設計の手助けになるはずです。
- 偉そうに書いていますが、色々と突っ込みを頂けると嬉しいです!
- 誤字脱字から高度な内容まで、なにかあればコメントお願いします。