Delphi (およびおそらく他の多くの言語) には、クラス ヘルパーがあります。これらは、既存のクラスに追加のメソッドを追加する方法を提供します。サブクラスを作成せずに。
では、クラス ヘルパーにはどのような用途があるでしょうか。
私はそれらを使用しています:
TStrings クラスにメソッドを追加して、派生リストと TStringList で同じメソッドを使用できるようにします。
TGpStringListHelper = class helper for TStringList
public
function Last: string;
function Contains(const s: string): boolean;
function FetchObject(const s: string): TObject;
procedure Sort;
procedure Remove(const s: string);
end; { TGpStringListHelper }
レコード フィールドへのアクセスを簡素化し、キャストを削除します。
最初は、クラス ヘルパーについて懐疑的でした。しかし、興味深いブログ エントリを読んで、今ではそれらが実際に役立つと確信しています。
たとえば、既存のインスタンス クラスに追加機能が必要で、何らかの理由で既存のソースを変更できない場合です。この機能を追加するクラス ヘルパーを作成できます。
例:
type
TStringsHelper = class helper for TStrings
public
function IsEmpty: Boolean;
end;
function TStringsHelper.IsEmpty: Boolean;
begin
Result := Count = 0;
end;
毎回、TStrings (のサブクラス) のインスタンスを使用しており、TStringsHelper はスコープ内にあります。IsEmpty メソッドにアクセスできます。
例:
procedure TForm1.Button1Click(Sender: TObject);
begin
if Memo1.Lines.IsEmpty then
Button1.Caption := 'Empty'
else
Button1.Caption := 'Filled';
end;
ノート:
これは、C#3 (および VB9) の拡張メソッドと非常によく似ています。私が見た中で最もよく使われているのは、LINQ が任意のシーケンスに対して動作できるようにするIEnumerable<T>
(and ) の拡張機能です。IQueryable<T>
var query = someOriginalSequence.Where(person => person.Age > 18)
.OrderBy(person => person.Name)
.Select(person => person.Job);
(またはもちろん、何でも)。拡張メソッドを使用すると、戻り値と同じ型を取る静的メソッドへの呼び出しを効果的に連鎖できるため、これらすべてが実行可能です。
それらはプラグインに非常に役立ちます。たとえば、プロジェクトが特定のデータ構造を定義し、特定の方法でディスクに保存されているとします。しかし、他のプログラムが非常に似たようなことを行いますが、データ ファイルは異なります。しかし、多くのユーザーが使用する必要のない機能のために、一連のインポート コードで EXE を肥大化させたくはありません。プラグイン フレームワークを使用して、次のように機能するプラグインにインポーターを配置できます。
type
TCompetitionToMyClass = class helper for TMyClass
public
constructor Convert(base: TCompetition);
end;
次に、コンバーターを定義します。1 つの注意点: クラスヘルパーはクラスフレンドではありません。この手法は、パブリック メソッドとプロパティを使用して新しい TMyClass オブジェクトを完全にセットアップできる場合にのみ機能します。しかし、それができれば、それは非常にうまく機能します。
あなたが「クラス ヘルパー」と呼んでいるものを初めて経験したのは、Objective C を学習しているときでした。Cocoa (Apple の Objective C フレームワーク) は、「カテゴリ」と呼ばれるものを使用します。
カテゴリを使用すると、サブクラス化せずに独自のメソッドを追加して、既存のクラスを拡張できます。実際、Cocoa は可能な限りサブクラス化を避けることを推奨しています。多くの場合、サブクラス化は理にかなっていますが、多くの場合、カテゴリを使用して回避できます。
Cocoa でのカテゴリの使用の良い例は、「キー バリュー コード (KVC)」および「キー バリュー オブザービング (KVO)」と呼ばれるものです。
このシステムは、2 つのカテゴリ (NSKeyValueCoding と NSKeyValueObserving) を使用して実装されます。これらのカテゴリは、任意のクラスに追加できるメソッドを定義および実装します。たとえば、Cocoa は、これらのカテゴリを使用して次のようなメソッドを NSArray に追加することにより、KVC/KVO に「準拠」を追加します。
- (id)valueForKey:(NSString *)key
NSArray クラスには、このメソッドの宣言も実装もありません。ただし、カテゴリを使用することにより。そのメソッドは、任意の NSArray クラスで呼び出すことができます。KVC/KVO 準拠を得るために NSArray をサブクラス化する必要はありません。
NSArray *myArray = [NSArray array]; // Make a new empty array
id myValue = [myArray valueForKey:@"name"]; // Call a method defined in the category
この手法を使用すると、独自のクラスに KVC/KVO サポートを簡単に追加できます。Java インターフェイスを使用するとメソッド宣言を追加できますが、カテゴリを使用すると、実際の実装を既存のクラスに追加することもできます。
このコメントを読んだので、それらを使用することはお勧めしません。
「クラス ヘルパーの最大の問題は、独自のアプリケーションでクラス ヘルパーを使用するという観点からすると、特定のクラスに対して常に 1 つのクラス ヘルパーしかスコープ内に存在しない可能性があるという事実です。」... 「つまり、スコープ内に 2 つのヘルパーがある場合、コンパイラによって認識されるのは 1 つだけです。非表示になっている可能性のある他のヘルパーに関する警告やヒントさえも得られません。」
http://davidglassborow.blogspot.com/2006/05/class-helpers-good-or-bad.html
GameCat が示すように、TStrings はいくつかの入力を避けるための良い候補です。
type
TMyObject = class
public
procedure DoSomething;
end;
TMyObjectStringsHelper = class helper for TStrings
private
function GetMyObject(const Name: string): TMyObject;
procedure SetMyObject(const Name: string; const Value: TMyObject);
public
property MyObject[const Name: string]: TMyObject read GetMyObject write SetMyObject; default;
end;
function TMyObjectStringsHelper.GetMyObject(const Name: string): TMyObject;
var
idx: Integer;
begin
idx := IndexOf(Name);
if idx < 0 then
result := nil
else
result := Objects[idx] as TMyObject;
end;
procedure TMyObjectStringsHelper.SetMyObject(const Name: string; const Value:
TMyObject);
var
idx: Integer;
begin
idx := IndexOf(Name);
if idx < 0 then
AddObject(Name, Value)
else
Objects[idx] := Value;
end;
var
lst: TStrings;
begin
...
lst['MyName'] := TMyObject.Create;
...
lst['MyName'].DoSomething;
...
end;
レジストリ内の複数行の文字列にアクセスする必要がありましたか?
type
TRegistryHelper = class helper for TRegistry
public
function ReadStrings(const ValueName: string): TStringDynArray;
end;
function TRegistryHelper.ReadStrings(const ValueName: string): TStringDynArray;
var
DataType: DWord;
DataSize: DWord;
Buf: PChar;
P: PChar;
Len: Integer;
I: Integer;
begin
result := nil;
if RegQueryValueEx(CurrentKey, PChar(ValueName), nil, @DataType, nil, @DataSize) = ERROR_SUCCESS then begin
if DataType = REG_MULTI_SZ then begin
GetMem(Buf, DataSize + 2);
try
if RegQueryValueEx(CurrentKey, PChar(ValueName), nil, @DataType, PByte(Buf), @DataSize) = ERROR_SUCCESS then begin
for I := 0 to 1 do begin
if Buf[DataSize - 2] <> #0 then begin
Buf[DataSize] := #0;
Inc(DataSize);
end;
end;
Len := 0;
for I := 0 to DataSize - 1 do
if Buf[I] = #0 then
Inc(Len);
Dec(Len);
if Len > 0 then begin
SetLength(result, Len);
P := Buf;
for I := 0 to Len - 1 do begin
result[I] := StrPas(P);
Inc(P, Length(P) + 1);
end;
end;
end;
finally
FreeMem(Buf, DataSize);
end;
end;
end;
end;
利用可能なクラスメソッドをクラス間で一貫させるために使用されるのを見てきました。ActiveプロパティとVisibleプロパティだけでなく、特定の「タイプ」のすべてのクラスにOpen/CloseとShow/Hideを追加します。
Dephi が拡張メソッドをサポートしている場合、私が望む用途の 1 つは次のとおりです。
TGuidHelper = class
public
class function IsEmpty(this Value: TGUID): Boolean;
end;
class function TGuidHelper(this Value: TGUID): Boolean;
begin
Result := (Value = TGuid.Empty);
end;
だから私は呼び出すことができますif customerGuid.IsEmpty then ...
。
IDataRecord
もう 1 つの良い例は、パラダイム (私が大好きです) を使用して XML ドキュメント (または、そのようなことに興味がある場合は JSON) から値を読み取ることができることです。
orderGuid := xmlDocument.GetGuid('/Order/OrderID');
どちらがはるかに優れています:
var
node: IXMLDOMNode;
node := xmlDocument.selectSingleNode('/Order/OrderID');
if Assigned(node) then
orderID := StrToGuid(node.Text) //throw convert error on empty or invalid
else
orderID := TGuid.Empty; // "DBNull" becomes the null guid