
ちょうど今日、私は同僚とインターフェースについて議論しました。 彼は彼の面白いトリックについて教えてくれた、私は彼について私の話をしたが、帰り道だけで、特にそれらを組み合わせた場合、これらのトリックのフルパワーを実感した。
便利な自動化とMVCパターンのファン-猫の下でお願いします。
トリック1.スマートな弱いリンク
知らない人のために-弱い(弱い)リンク-カウンターを増加させないリンク。 木があるとしましょう:
INode = interface function GetParent: INode; function ChildCount: Integer; function GetChild(Index: Integer): INode; end;
INodeインターフェイスを実装するクラス内にある場合、親と子は次のように保存されます。
TNode = class(TInterfacedObject, INode) private FParent: INode; FChild: array of INode; end;
その木は決して破壊されません。 親は子へのリンクを保持し(それによってカウンターを増やします)、子は親にリンクします。 これは古典的な循環リンクの問題であり、この場合は弱いリンクに頼ります。 新しいXEデルファイでは、次のように記述できます。
TNode = class(TInterfacedObject, INode) private [weak] FParent: INode; FChild: array of INode; end;
そして古い-彼らはポインターを保存します:
TNode = class(TInterfacedObject, INode) private FParent: Pointer; FChild: array of INode; end;
これにより、カウンターの自動インクリメントをバイパスすることができ、親へのポインターを失うと、ツリー全体が取得されます。
弱いリンクには別の側面があります。 オブジェクトが突然破壊され、誰かがそのオブジェクトへの弱いリンクを保持している場合、追跡できません。 実際、ガベージポインタがあれば、アクセスするとエラーが発生します。 そしてこれはひどいです。 これらのリンクには、何らかのクリーニングシステムを作成する必要があります。
しかし、非常にエレガントなソリューションがあります。 そして、これがその仕組みです。 弱いリンクインターフェイスとそれを実装するクラスを作成します。
IWeakRef = interface function IsAlive: Boolean; function Get: IUnknown; end; TWeakRef = class(TInterfacedObject, IWeakRef) private FOwner: Pointer; public procedure _Clean; function IsAlive: Boolean; function Get: IUnknown; end; procedure TWeakRef._Clean; begin FOwner := nil; end; function TWeakRef.Get: IUnknown; begin Result := IUnknown(FOwner); end; function TWeakRef.IsAlive: Boolean; begin Result := Assigned(FOwner); end;
これが通常のPointerへの型キャストです。 上記で説明したのは、その弱いリンクです。 ただし、キーメソッドはIsAliveです 。これは、 Trueを返します(弱い参照によって参照されるオブジェクトが存在する場合)。 FOwnerをきれいに掃除する方法を理解するだけです。
インターフェイスを作成します。
IWeakly = interface ['{F1DFE67A-B796-4B95-ADE1-8AA030A7546D}'] function WeakRef: IWeakRef; end;
弱いリンクを返し、このインターフェイスを実装するクラスを記述します。
TWeaklyInterfacedObject = class(TInterfacedObject, IWeakly) private FWeakRef: IWeakRef; public function WeakRef: IWeakRef; destructor Destroy; override; end; destructor TWeaklyInterfacedObject.Destroy; begin inherited; FWeakRef._Clean; end; function TWeaklyInterfacedObject.WeakRef: IWeakRef; var obj: TWeakRef; begin if FWeakRef = nil then begin obj := TWeakRef.Create; obj.FOwner := Self; FWeakRef := obj; end; Result := FWeakRef; end;
全員に1つの弱いリンクを与えるメソッドを追加しました。 また、オブジェクト自体は常に弱いリンクを認識しているため、デストラクタで単純にクリーンアップします。 あとは 、 TInterfacedObjectではなくTWeaklyInterfacedObjectから継承するだけです。 安全でないキャスト、脚のショット、および口汚い言葉はもうありません。
トリック2.サブスクライバーメカニズム
Delphiでプラグインのシステムを循環させておらず、MVCパターンを使用していない場合は、幸運です。 Delphiでは、すべてのイベントは関数(およびインスタンス)への1つまたは2つのポインターにすぎません。 したがって、クラスを作成してOnBlaBlaプロパティにした場合、このBlaBlaが最終的に発生したことがわかるのは1人だけです。 したがって、誰もが自分のサブスクリプションメカニズムを見始め、しばしばこれらのサブスクリプションのデバッグにsubscriptionれます。
通常、インターフェイスベースのイベントはこれを実装します。 別のイベントインターフェイスを作成します。たとえば、次のとおりです。
IMouseEvents = interface procedure OnMouseMove(...); procedure OnMouseDown(...); procedure OnMouseUp(...); end;
オブジェクトの古典的な手順の代わりに、それを渡します。 たとえば、Subscribe / Unsubscribeメソッドのペアで:
IForm = interface procedure SubscribeMouse(const subscriber: IMouseEvents); procedure UnsubscribeMouse(const subscriber: IMouseEvents); end;
コードが大きくなり、 IMouseEventsインターフェイスが少し変更されると (たとえば、メソッドが追加されると)、リファクタリングが非常に負担になり始めます。 たとえば、同じIMouseEventsが IForm 、 IButton 、 IImageおよびその他の悪霊で使用されています。 サブスクリプションを正しく修正したり、サブスクライバーのクロールを追加したりする必要があるすべての場所
私は次のトリックを使用しています。 インターフェイスを作成します。
IPublisher = interface ['{CDE9EE5C-021F-4942-A92A-39FC74395B4B}'] procedure Subscribe (const ASubscriber: IUnknown); procedure Unsubscribe(const ASubscriber: IUnknown); end;
このインターフェイスを実装するクラス( TBasePublisherとします)は、リストから一部のインターフェイスのみを追加および削除できます。 将来、私は放送局と呼ぶクラスを作成します。 イベントインターフェイスがあります。
IGraphEvents = interface ['{2C7EF06A-2D63-4F25-80BC-7BA747463DB6}'] procedure OnAddItem(const ASender: IGraphList; const AItem: TGraphItem); procedure OnClear(const ASender: IGraphList); end;
TBasePublisherを継承し、次のブロカスターを実装します。
TGraphEventsBroadcaster = class(TBasePublisher, IGraphEvents) private procedure OnAddItem(const ASender: IGraphList; const AItem: TGraphItem); procedure OnClear(const ASender: IGraphList); end; procedure TGraphEventsBroadcaster.OnAddItem(const ASender: IGraphList; const AItem: TGraphItem); var arr: TInterfacesArray; i: Integer; ev: IGraphEvents; begin arr := GetItems; for i := 0 to Length(arr) - 1 do if Supports(arr[i], IGraphEvents, ev) then ev.OnAddItem(ASender, AItem); end; procedure TGraphEventsBroadcaster.OnClear(const ASender: IGraphList); var arr: TInterfacesArray; i: Integer; ev: IGraphEvents; begin arr := GetItems; for i := 0 to Length(arr) - 1 do if Supports(arr[i], IGraphEvents, ev) then ev.OnClear(ASender); end;
つまり、ブロードキャスター自体がイベントインターフェイスを実装し、実装では同じイベントをすべてのサブスクライバーに送信するだけです。 利点は、すべてが1か所に実装され、少なくともIGraphEventsを変更するとコンパイルされないことです。 IForm 、 IButton 、 IImage zooは、単にTFormEventsBroadcasterを内部に作成し、そのメソッドを呼び出します。 これは 、 IFormにサブスクライバが 1つしかないかのようになります。
トリック3.スマートウィークリンク+サブスクライバーメカニズム
しかし、加入者について上記で説明したことはすべて悪いことです。 事実、ここには循環リンクがあり、ファイナライズとサブスクライブ解除の手順を把握しようとしています。 弱いリンクを追加しますが、ガベージリンクのデバッグが面倒になります。 これは、冒頭で説明したスマートな弱いリンクが役立つ場所です。 この発行者インターフェイスを記述するだけです(記事の冒頭からIWeaklyを受け入れます)。
IPublisher = interface ['{CDE9EE5C-021F-4942-A92A-39FC74395B4B}'] procedure Subscribe (const ASubscriber: IWeakly); procedure Unsubscribe(const ASubscriber: IWeakly); end;
TBasePublisherパブリッシャーは、内部に弱いリンクの配列を格納していますTWeakRefArr = IWeakRefの配列。
TBasePublisher = class(TInterfacedObject, IPublisher) private FItems: TWeakRefArr; protected function GetItems: TWeakRefArr; public procedure Subscribe (const ASubscriber: IWeakly); procedure Unsubscribe(const ASubscriber: IWeakly); end;
そして今、放送局は活力への弱いリンクだけをチェックし、正常になり、イベントをそれに向けます。 Broadcaststerは次のように変更されました。
procedure TGraphEventsBroadcaster.OnAddItem(const ASender: IGraphList; const AItem: TGraphItem); var arr: TWeakRefArr; i: Integer; ev: IGraphEvents; begin arr := GetItems; for i := 0 to Length(arr) - 1 do if IsAlive(arr[i]) then if Supports(arr[i].Get, IGraphEvents, ev) then ev.OnAddItem(ASender, AItem); end; procedure TGraphEventsBroadcaster.OnClear(const ASender: IGraphList); var arr: TWeakRefArr; i: Integer; ev: IGraphEvents; begin arr := GetItems; for i := 0 to Length(arr) - 1 do if IsAlive(arr[i]) then if Supports(arr[i].Get, IGraphEvents, ev) then ev.OnClear(ASender); end;
今、私たちは、サブスクライブ解除の順序についてまったく心配していません。 退会を忘れた場合-大丈夫です。 あるべき姿がすべて透明になりました。
トリック4.役立つようにリロードする
最後の仕上げ:
TAutoPublisher = packed record Publisher: IPublisher; class operator Add(const APublisher: TAutoPublisher; const ASubscriber: IWeakly): Boolean; class operator Subtract(const APublisher: TAutoPublisher; const ASubscriber: IWeakly): Boolean; end; class operator TAutoPublisher.Add(const APublisher: TAutoPublisher; const ASubscriber: IWeakly): Boolean; begin APublisher.Publisher.Subscribe(ASubscriber); Result := True; end; class operator TAutoPublisher.Subtract(const APublisher: TAutoPublisher; const ASubscriber: IWeakly): Boolean; begin APublisher.Publisher.Unsubscribe(ASubscriber); Result := True; end;
言葉がなければ明確だと思います。 MyForm.MyEvents + MySubscriberを実行するだけです。 -サインアップしました。 減算: MyForm.MyEvents-MySubscriber ; -未登録。
これがどのように機能するかの例を提供しなければ、この記事は完全ではありません。 だからここに例があります 。 要するに:
プログラムは4つのウィンドウを作成します。 どのウィンドウでも、マウスで描画できます。 描画されたオブジェクトがリストに追加され、サブスクリプションメカニズムを通じてすべてのウィンドウに変更が通知されます。 したがって、描画された図はすべてのフォームに表示されます。 各フォームで、トラックバーを使用して独自の線の太さを選択できます。
IntfEx.pas-スマートな弱いリンクの実装、弱いリンク上のパブリッシャーTBasePublisherの基本クラス+ TAutoPublisher構造によるオーバーロード
Datas.pas-描画オブジェクトのリスト+このリストを変更するときのイベントインターフェイス
DrawForm.pas-描画できるフォームを実装するクラス。 イベントのサブスクリプションがあります。
HiddenForm.pas-非表示のメインフォーム(アプリケーションがウィンドウループをスピンするためにのみ必要)
さて、プロジェクトファイルはわずかに変更されています(描画できるフォームが作成されます)
弱いリンクのアイデアは、Maxidix sroのDmitry Ilyinsによって発明され、私によって完成されました。
更新しました。 このリリース以降、Delphi 10.1では本格的な[弱い]リンクが導入されました。 内部では、上記の弱リンクとの類推によって実装され、インターフェースがマージされると自動的にnilになります。 詳細はこちら: habr.com/en/post/282035