2013年12月19日木曜日

Delphi の Interface

Delphi Advent Calendar 2013 12/19 の記事です。

※本文最後に追記あり(2013-12-20)

Delphi の Interface が使えないとほざいているのは誰だぁっ!

ということで、とりあえずできることを、つらつらっと書いてみましたよ。
詳しくは、コメントを見てください!

program Project1;
 
uses
System.SysUtils;
 
type
// GUID を指定すると Interface と GUID を結びつけることができます。
// これは主に COM をサポートするための機能です。
// (COM は Interface を GUID で管理します)
// にも関わらず GUID を付けることが推奨されています。
// 例えば GUID をキーに Dictionary として管理したりするためです。
// TPlatformServices.AddPlatformService のソースが参考になります。
IFoo = interface
['{174C7089-888D-4B3A-A348-DBAEC0AA70A5}']
// Property も宣言できますが、Interface は変数を宣言できないので
// reader / writer はメソッドのみ指定できます
function GetBar: String;
property Bar: String read GetBar;
end;
 
IDummy = interface
['{7820C3D8-0DBC-4506-81FF-4FB9B21F6959}']
function GetBar: String;
end;
 
// IFoo を実装するクラス
// TAggregatedObject は後述する TInterfacedObject の Reference Counter を
// 共有するクラスです
TFooImpl = class(TAggregatedObject, IFoo)
private
function GetBar: String;
end;
 
// IFoo を実装するクラス2
// IDummy という実験のためのダミークラスも実装してみています。
// 異なる Interface が同じメソッド名を持つ場合は、こんな風に解決できます。
TFooImpl2 = class(TAggregatedObject, IFoo, IDummy)
private
function IFooGetBar: String;
function IDummyGetBar: String;
function IFoo.GetBar = IFooGetBar; // IFoo と IDummy の GetBar に
function IDummy.GetBar = IDummyGetBar; // それぞれの実装を代入している
end;
 
// TFooImpl か TFooImpl2 に IFoo の実装を委譲してるクラス
// 委譲すると自身は IFoo を実装しなくていい!
// TInterfacedObject を継承すると RefCounter によって自動的に破棄されるよ
// 流行りの ARC と同じ仕組みを随分前から実装してたんだよ!
// (COM がそうなんだけど)
TBaz = class(TInterfacedObject, IFoo)
private
FFoo: IFoo;
public
constructor Create; reintroduce;
property FooIntf: IFoo read FFoo implements IFoo;
end;
 
{ TFooImple }
function TFooImpl.GetBar: String;
begin
Result := 'Bar';
end;
 
{ TFooImple2 }
function TFooImpl2.IDummyGetBar: String;
begin
Result := 'Dummy !';
end;
 
function TFooImpl2.IFooGetBar: String;
begin
Result := 'Bar 2!';
end;
 
 
{ TBaz }
constructor TBaz.Create;
begin
inherited;
 
Randomize;
 
// (ここではランダムだけど)目的に応じて委譲先を変更できる!
if (Random(2) = 0) then
FFoo := TFooImpl.Create(Self) // TAggregatedObject は RefCounter を
else // 共有するインスタンスを要求するよ
FFoo := TFooImpl2.Create(Self);
end;
 
{ Main }
var
Foo: IFoo;
GUID: TGUID;
Obj: TObject;
begin
// Inteface に代入, TBaz 自体は IFoo を実装していないのに代入できる!
Foo := TBaz.Create;
Writeln(Foo.Bar);
 
// Inteface を TGUID に代入できる!
GUID := IFoo;
Writeln(GUIDToString(GUID));
 
// Inteface から元の型を調べてみる
Writeln((Foo as TObject).ClassName); // Implements からでも元の型が取れる!
 
// Inteface から元の型を取りだして、再生成したりもできちゃう
// (まあこれは Inteface 関係ないけど…)
Obj := (Foo as TObject).ClassType.Create;
Writeln(Obj.ClassName);
 
Readln;
(* 実行結果 - Bar 2! と出ているところは Bar と出ることもあるよ
 
Bar 2!
{174C7089-888D-4B3A-A348-DBAEC0AA70A5}
TBaz
TBaz
 
*)
end.

個人的に面白いなあと思うのは「委譲」の仕組みと「Interface から元の型を取り出せる」ところかな-。
Java では Interface から元の Class を取り出すなんて不可能だからね!(間違ってました。本文最後に追記)
※ヘルプには「委譲は Win32 のみ」と書いてあるけど、普通に Win/OSX/iOS/Android で動作しました。

あと、今回の iOS / Android 対応で TinterfacedObject の仕組みが非常に活きているのが感慨深い…
COM のために実装した様々なことがここに来てすごく活きている!
同じく COM のために実装した dispinterface は、プラットフォーム依存ですと警告が出るようになってたよ…
ちなみに、Interface は FireMonkey でも使われまくっていてるんですよ!!
各 Platform 依存部と、それを一般化する部分では Interface 無しには実装できませんぜ!

追記:0213-12-20
Java でも、次のようにすれば Interface から元の Class を取り出せるよ!と教えていただきました。
MyInterface intf = new MyClass();
System.out.println(((Object)intf).getClass().getName());
僕の Java スキルもまだまだです……

0 件のコメント:

コメントを投稿