1. 程式人生 > >淺拷貝與深度拷貝(原型模式)

淺拷貝與深度拷貝(原型模式)

Delphi的VCL從TPersistent開始支援RTTI(RuntimeTypeInfo)執行時型別資訊,它是通過{$M+}編譯指令提供了RTTI的功能.M開啟以後,Delphi在編譯該物件時,會把物件的型別資訊編譯進可執行檔案,這樣在執行時就可以動態地獲得物件的屬性和方法等資訊.因為所有的VCL視覺化元件都是從TPersistent派生的,因此可以將元件資訊儲存到dfm也可以動態載入.

Delphi還定義了一個虛方法Assign,

procedure Assign(Source:TPersistent);virtual;

這個方法就是用來把源物件屬性複製到目標物件中的.預設的TPersistent物件的Assign方法只是簡單的呼叫源物件的AssignTo方法來複制屬性,而TPersistent的AssignTo方法只是丟擲一個異常,沒有實現有意義的功能,那麼派生自TPersistent的物件要想提供克隆的功能都需要過載TPersistent的Assign或AssignTo來實現自定義的複製功能.

獲取類的屬性列表:要引用TypInfo//rtti,contnrs,classes,sysutils;//{$M+}要開啟.

procedure GetPropNames(AObject:TObject;var List:TStringList);
var
  I,Count:Integer;
  PropList:PPropList;//陣列型指標,陣列又是指向屬性的紀錄型資訊的指標型陣列.
  PKinds:TTypeKinds;//型別資訊的集合
begin
  List.Clear;
  PKinds := [tkUnknow,tkInteger,tkChar,tkEnumeration,tkFloat,tkString,tkSet,tkClass,tkMethod,tkWChar,tkLString,tkWString,
tkVariant,tkArray,tkRecord,tkInterface,tkInt64,tkDynArray];
  Count := GetPropList(AObject.ClassInfo,pKinds,nil);
  GetMem(PropList,Count*SizeOf(Pointer));
  GetPropList(AObject.ClassInfo,PKinds,PropList);
  for I :=0 to count-1 do
  List.Add(PropList^[i].Name);
  FreeMem(PropList,Count*SizeOf(Pointer));
end;
 
procedure CloneObject(SrcObj,DesObj:TPersistent);
var
   NameList:TStringList;
  I:Integer;
  V:Variant;
begin
  if srcObj.ClassName<>DesObj.ClassName then
  raise Exception.Create('不同型別的物件,無法克隆');
  if (not Assigned(SrcObje)) or not (Assigned(DesObj)) then
  raise Exception.Create('物件不能為空');
  NameList := TStringlist.create;
  GetPropNames(SrcObj,NameList);
 try
   for I:= 0 to Namelist.Count-1 do
  begin
      V:= GetPropValue(SrcObj,Namelist.Strings[I]);
     SetPropValue(DesObj,NameList.Strings[I],V);
finally
  Namelist.free;
end;
end;

其中GetPropName函式呼叫Delphi的TypeInfo單元的Rtti函式獲得要克隆物件的保護級別為Published的屬性名稱字串列表.而CloneObject則遍歷物件的屬性列表,使用RTTI函式GetPropValue通過屬性名獲得物件的屬性值,然後通過RTTI函式的SetPropValue將獲得源物件值賦值給目標物件.注意RTTI函式只對Published屬性有效,其它保護級別的屬性無效.

上面的物件複製函式對於複合的物件如下級物件的TreeView,TStrings是無效的,對於這類物件還必須手工完成.procedure TStrings.Assign(Source:TPersistent);

begin
  if Source is TStrings then
  begin
     Beginupdate;
    try
      Clear;
      FDefined:=TStrings(Source).FDefined;
      FNameValueSeparator := TStrings(Source).FNameValueSeparator;
      FQuoteChar := TStrings(Source).FQuoteChar;
      FDelimiter :=  TStrings(Source).FDelimiter;
      AddStrings(TStrings(Source));
    finally
      EndUpdate;
    end;
   Exit;
  end;
  inherited Assign(Source);
end;
 

delphi的Assign方法除了可以實現同樣型別物件的克隆之外,還可以實現不同物件之間的克隆,最典型的就是剪貼簿TClipBoard了,Windows的剪貼簿可以存放很不同型別的資料,如文字,點陣圖,圖元等,為了實現將剪貼簿中的點陣圖資料直接複製給對應的TBitmap或者TMetafile類,VCL過載了TClipboard類的AssignTo方法來實現將資料複製給不同的物件:

procedure TClipboard.AssignTo(Dest: TPersistent);
begin
  if Dest is TPicture then
     AssignToPicture(TPicture(Dest))
  else if Dest is TBitmap then
     AssignToBitmap(TBitmap(Dest))
  else if Dest is TMetafile then
     AssignToMetafile(TMetafile(Dest))
  else inherited AssignTo(Dest);
end;
procedure TClipboard.AssignToBitmap(Dest: TBitmap);
var
  Data: THandle;
  Palette: HPALETTE;
begin
  Open;
  try
     Data := GetClipboardData(CF_BITMAP);
     Palette := GetClipboardData(CF_PALETTE);
     Dest.LoadFromClipboardFormat(CF_BITMAP, Data, Palette);
  finally
     Close;
  end;
end;
procedure TClipboard.AssignToMetafile(Dest: TMetafile);
var
//省略…
begin
//省略…
end;
procedure TClipboard.AssignToPicture(Dest: TPicture);
var
//…
Begin
  //省略…
end;

 Delphi的VCL從TPersistent開始支援RTTI(RuntimeTypeInfo)執行時型別資訊,它是通過{$M+}編譯指令提供了RTTI的功能.M開啟以後,Delphi在編譯該物件時,會把物件的型別資訊編譯進可執行檔案,這樣在執行時就可以動態地獲得物件的屬性和方法等資訊.因為所有的VCL視覺化元件都是從TPersistent派生的,因此可以將元件資訊儲存到dfm也可以動態載入.
Delphi還定義了一個虛方法Assign,procedure Assign(Source:TPersistent);virtual;
這個方法就是用來把源物件屬性複製到目標物件中的.預設的TPersistent物件的Assign方法只是簡單的呼叫源物件的AssignTo方法來複制屬性,而TPersistent的AssignTo方法只是丟擲一個異常,沒有實現有意義的功能,那麼派生自TPersistent的物件要想提供克隆的功能都需要過載TPersistent的Assign或AssignTo來實現自定義的複製功能.

獲取類的屬性列表:要引用TypInfo//rtti,contnrs,classes,sysutils;//{$M+}要開啟.

procedure GetPropNames(AObject:TObject;var List:TStringList);
var
  I,Count:Integer;
  PropList:PPropList;//陣列型指標,陣列又是指向屬性的紀錄型資訊的指標型陣列.
  PKinds:TTypeKinds;//型別資訊的集合
begin
  List.Clear;
  PKinds := [tkUnknow,tkInteger,tkChar,tkEnumeration,tkFloat,tkString,tkSet,tkClass,tkMethod,tkWChar,tkLString,tkWString,
tkVariant,tkArray,tkRecord,tkInterface,tkInt64,tkDynArray];
  Count := GetPropList(AObject.ClassInfo,pKinds,nil);
  GetMem(PropList,Count*SizeOf(Pointer));
  GetPropList(AObject.ClassInfo,PKinds,PropList);
  for I :=0 to count-1 do
  List.Add(PropList^[i].Name);
  FreeMem(PropList,Count*SizeOf(Pointer));
end;
 
procedure CloneObject(SrcObj,DesObj:TPersistent);
var
   NameList:TStringList;
  I:Integer;
  V:Variant;
begin
  if srcObj.ClassName<>DesObj.ClassName then
  raise Exception.Create('不同型別的物件,無法克隆');
  if (not Assigned(SrcObje)) or not (Assigned(DesObj)) then
  raise Exception.Create('物件不能為空');
  NameList := TStringlist.create;
  GetPropNames(SrcObj,NameList);
 try
   for I:= 0 to Namelist.Count-1 do
  begin
      V:= GetPropValue(SrcObj,Namelist.Strings[I]);
     SetPropValue(DesObj,NameList.Strings[I],V);
finally
  Namelist.free;
end;
end;


其中GetPropName函式呼叫Delphi的TypeInfo單元的Rtti函式獲得要克隆物件的保護級別為Published的屬性名稱字串列表.而CloneObject則遍歷物件的屬性列表,使用RTTI函式GetPropValue通過屬性名獲得物件的屬性值,然後通過RTTI函式的SetPropValue將獲得源物件值賦值給目標物件.注意RTTI函式只對Published屬性有效,其它保護級別的屬性無效.
上面的物件複製函式對於複合的物件如下級物件的TreeView,TStrings是無效的,對於這類物件還必須手工完成.
procedure TStrings.Assign(Source:TPersistent);
begin
  if Source is TStrings then
  begin
     Beginupdate;
    try
      Clear;
      FDefined:=TStrings(Source).FDefined;
      FNameValueSeparator := TStrings(Source).FNameValueSeparator;
      FQuoteChar := TStrings(Source).FQuoteChar;
      FDelimiter :=  TStrings(Source).FDelimiter;
      AddStrings(TStrings(Source));
    finally
      EndUpdate;
    end;
   Exit;
  end;
  inherited Assign(Source);
end;
 

delphi的Assign方法除了可以實現同樣型別物件的克隆之外,還可以實現不同物件之間的克隆,最典型的就是剪貼簿TClipBoard了,Windows的剪貼簿可以存放很不同型別的資料,如文字,點陣圖,圖元等,為了實現將剪貼簿中的點陣圖資料直接複製給對應的TBitmap或者TMetafile類,VCL過載了TClipboard類的AssignTo方法來實現將資料複製給不同的物件:

procedure TClipboard.AssignTo(Dest: TPersistent);
begin
  if Dest is TPicture then
     AssignToPicture(TPicture(Dest))
  else if Dest is TBitmap then
     AssignToBitmap(TBitmap(Dest))
  else if Dest is TMetafile then
     AssignToMetafile(TMetafile(Dest))
  else inherited AssignTo(Dest);
end;
procedure TClipboard.AssignToBitmap(Dest: TBitmap);
var
  Data: THandle;
  Palette: HPALETTE;
begin
  Open;
  try
     Data := GetClipboardData(CF_BITMAP);
     Palette := GetClipboardData(CF_PALETTE);
     Dest.LoadFromClipboardFormat(CF_BITMAP, Data, Palette);
  finally
     Close;
  end;
end;
procedure TClipboard.AssignToMetafile(Dest: TMetafile);
var
//省略…
begin
//省略…
end;
procedure TClipboard.AssignToPicture(Dest: TPicture);
var
//…
Begin
  //省略…
end;