はじめに
その昔DelphiやJavaを使っている時に、インターフェースの理解がモヤモヤしている時期がありました。
ググっていても、ふと気付くと何故かCOMの話になっていたりと、説明を読んでも良くわからない、メリットがいまいちピンと来ないという状態でした。
そして実際にインターフェースがなくても特に困っていなかったので、その頃の私には必要ないものだったのでしょう(※)。
※今振り返ると「あそこは使えたな」という事もあったりしますが(^^;
当時、一番しっくりこなかったのが、
var
x: IInterface; //インターフェースを代入できる???
begin
...
...のように「変数xにインターフェースが代入できる」という表現でした。
実装のないインターフェースを代入できるって、どういう事!? ...と混乱していました。
前提
多重継承NGでインターフェースが使える言語なら、大体当てはまると思います。
最近はC#の人なので以降はC#で記述しますが、Delphi、Java等も考え方は同じかと。
インターフェースじゃなければ出来ない事...?
そんな訳で、自分設計のクラスを使っている分には別にインターフェースがなくても全然困ってなかった私ですが...
ある時、他人設計のクラスを魔改造する必要が生じました。
具体的には.NETのWinFormsのコントロール(MS製やGrapeCity製だったり)に、カーソルキー(←↑↓→)移動の機能を追加する事になったのです(※)。
※縦横に並んだ入力コントロールから、Tab移動以外で思った方向に進めるための機能です。
Windowsアプリの流儀から外れるのは百も承知ですので、その点へのご批判はナシでお願い致します。
TextBox、ComboBox、CheckBox等を継承して、カーソルキーの移動先のコントロールを設定して移動できるようにしました。
public class XXXTextBox : TextBox
{
[Browsable(true)]
[Description("左カーソルを押した時の移動先を設定します。")]
[Category("移動先")]
public Control ToLeft { get; set; }
...
//こんな感じで各コントロールを継承して
//上下左右の移動先を記憶、押された方向に進める
//C#なのでプロパティ使ってますが、
//他言語なら各setter & getter を書けば良いです
因みに移動先の"Control"には、今回の魔改造コントロール以外も設定可能にしています。
視覚的に表現されるコンポーネントであるコントロールの基本クラスを定義します。
(MSのControlクラスの説明より引用)
ここまでなら、別にインターフェースがなくても困りませんでした。
夜明け前...
各継承コントロールに移動する機能を実装してヤレヤレと思っていた所、今度は無効な項目をスキップして進めたいという仕様追加が発生...
項目Bで↓を押した時、項目Eが無効なら項目Gに進めたい!
そこで、インターフェースに開眼していない私は、
「移動先が無効な場合、条件分岐で移動先が魔改造コントロールなら、更にその先を追って...」
...とか考えていたのですが。
「あ、これって魔改造コントロールの種類が増える度にメンテしないとダメなヤツだ...」
Control toDown = null;
if (ctrl is XXXTextBox textBox) //魔改造コントロール個別に判断、
{
toDown = textBox.ToDown; //個別に取得
}
else if (ctrl is XXXComboBox comboBox) //魔改造コントロール個別に判断、
{
toDown = comboBox.ToDown; //個別に取得
}
... //新しい魔改造コントロールが増える度に要対応...
//※C#の場合、リフレクションで判定&取得って手もあるけど(^^;
...と思った瞬間に、ここがインターフェースの出番なのかっ!とビビッと開眼!
開眼!インターフェースじゃなければ出来ない事!
開眼した私は下記のインターフェースを作成、
public interface IXXXControl
{
Control ToLeft { get; set; }
//こんな感じで上下左右を記憶するインターフェースを作成
//C#なのでプロパティ使ってますが、
//他言語なら各setter & getter を書けば良いです
各継承コントロールにインターフェースを追加。
public class XXXTextBox : TextBox, IXXXControl
{
[Browsable(true)]
[Description("左カーソルを押した時の移動先を設定します。")]
[Category("移動先")]
public Control ToLeft { get; set; }
//クラス宣言にIXXXControlを書き足しただけ。
//インターフェースの内容は元々実装してあるので無問題。
こうしておくと先の判定も、
Control toDown = null;
if (ctrl is IXXXControl xxxCtrl)
//新しい魔改造コントロールが増えても無問題!
{
toDown = xxxCtrl.ToDown; //IXXXControlの実装が保証されている!
}
...で魔改造コントロールの判定が出来てメンテナンスフリーになるやん!
まとめ
という事で大分遅まきながらインターフェースにしか出来ない事が腹落ちした瞬間でした。
自分が直接手出しできないクラスツリーに介入して、インターフェース単位でグルーピングできる!
昔しっくりこなかった下記コードも、今はすっかり馴染むようになりました!
var
x: IInterface; //×インターフェースを代入できる???
//〇インターフェースを実装したインスタンスを代入できる
// &インターフェースの機能が使える
// (インターフェースにない機能は使えない)
begin
~
※近年Delphiを触る機会はないですが...(^^;
個人的にオブジェクト指向の中で、インターフェースの理解が一番引っかかった部分でした。
同じようなモヤモヤを抱えている方の参考になれば幸いです!
更新
2021.07.15 PlantUMLのイメージを、画像からMarkdown記述に変更