如何建立这样的类?(见内容) ( 积分: 100 )

  • 主题发起人 主题发起人 echolan
  • 开始时间 开始时间
E

echolan

Unregistered / Unconfirmed
GUEST, unregistred user!
开发中希望建立一个模型,要求类的一个对象本身能有很多同类的对象组成,组成一个对象的多个对象应该以有序的数组表示.这样的类该如何实现呢?
大致应该像如下定义
TMyClass = class;
private
Prop1:String;
......
MyArr : Array of TMyClass;
......
public
......
constructor Create(...);Overload;
end;
如果照上面的形式定义,应该增加哪些过程或Create该如何重载?
 
开发中希望建立一个模型,要求类的一个对象本身能有很多同类的对象组成,组成一个对象的多个对象应该以有序的数组表示.这样的类该如何实现呢?
大致应该像如下定义
TMyClass = class;
private
Prop1:String;
......
MyArr : Array of TMyClass;
......
public
......
constructor Create(...);Overload;
end;
如果照上面的形式定义,应该增加哪些过程或Create该如何重载?
 
Treeview的TTreeNode就是最好的范例
 
to xfz8124:
你说的是不是TTreeNode里面的这个成员?:
private
FOwner: TTreeNodes

看了一下代码
constructor TTreeNode.Create(AOwner: TTreeNodes);
begin
inherited Create;
FOverlayIndex := -1;
FStateIndex := -1;
FOwner := AOwner;
end;
值得参考,不过这里的并不是同类子对象,TTreeNode中的FOwner是TTreeNodes类型。

to 楼主:
想了一下,如果直接在Create中创建同类子对象,那么同类子对象中又包含子对象,这些
子对象就无法创建了,也就是你不知道要创建多少子对象的时候,直接在Create函数中创
建只能进行到第一层子对象,否则将会一直创建直至内存耗尽(实际上也并不容易设计)。
所以我是这么设计的:需要的时候再创建同类子对象(指定对象数组元素个数)。如下
type
TTest = class;
TTestAry = array of TTest;

TTest = class(TObject)
private
FTestAry :TTestAry;
FTag :integer;
public
constructor Create;
destructor Destroy;override;
procedure FreeTestAry;
procedure SetTestAry(Num :integer);

property TestAry :TTestAry read FTestAry write FTestAry;
property Tag :integer read FTag write FTag;
end;
implementation
{ TTest }
constructor TTest.Create;
begin
inherited Create;
FTestAry :=nil
//初始时设定为nil
end;

destructor TTest.Destroy;
begin
FreeTestAry
//调用自定义过程
inherited;
end;

procedure TTest.FreeTestAry;
var
i :integer;
begin
if assigned(TestAry) then //判断TestAry是否为nil(即是否有元素,有则释放)
for i :=Low(TestAry) to High(TestAry) do
begin
TestAry.Destroy;
end;
end;

procedure TTest.SetTestAry(Num: integer)
//自定义的创建过程,供需要时调用
var
ATestAry :TTestAry;
i :integer;
begin
SetLength(ATestAry,Num*sizeof(TTestAry));
for i :=Low(ATestAry) to High(ATestAry) do
begin
ATestAry :=TTest.Create;
ATestAry.Tag :=i*2 + 1;
end;
TestAry :=ATestAry
//创建的子对象数组赋值给数组成员FTestAry
end;

测试代码如下:
var
Form1: TForm1;
ATest :TTest
//变量

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
ATest :=TTest.Create
//创建
ATest.SetTestAry(5)
//设置子对象
end;

procedure TForm1.Button2Click(Sender: TObject);
var
i :integer;
begin
for i :=1 to 4 do
begin
showmessage(inttostr(ATest.TestAry.Tag))
//测试子对象的Tag值
end;
ATest.Free
//释放后不能再访问此ButtonClick
end;

以上设计可能不是太严谨,供参考,请指正。
 
可以参照 TComponent 的例子,定义
局部变量 TList的类委托:

procedure TComponent.Insert(AComponent: TComponent);
begin
if FComponents = nil then FComponents := TList.Create;
FComponents.Add(AComponent);
AComponent.FOwner := Self;
end;

procedure TComponent.Remove(AComponent: TComponent);
begin
AComponent.FOwner := nil;
FComponents.Remove(AComponent);
if FComponents.Count = 0 then
begin
FComponents.Free;
FComponents := nil;
end;
end;
分别生成后再 Insert。
 
用TCollection
 
to Jonson_sunshine
这样做是可以实现对象内包含同类对象,但是在多层嵌套的情况(尤其是数组中各个子对象内嵌套层数不相同的情况)下,层层深入调用ATest.SetTestAry(Num)很不方便,也不实际。如果在事先知道对象个数的情况下不知道下面的代码能否正常运行,不进入死循环。
TTest = class(TObject)
private
FSubNum :Byte;// 数组的长度
FTestAry :TTestAry;
FTag :integer;
public
constructor Create;
destructor Destroy;override;
procedure GetSubNum;//获得数组长度
procedure FreeTestAry;
procedure SetTestAry(Num :integer);
property SubNum :Byte read FSubNum;
property TestAry :TTestAry read FTestAry write FTestAry;
property Tag :integer read FTag write FTag;
end;
implementation
{ TTest }
constructor TTest.Create;
begin
inherited Create;
GetSubNum;
SetTestAry(FSubNum);
end;

destructor TTest.Destroy;
begin
FreeTestAry
//调用自定义过程
inherited;
end;

procedure TTest.FreeTestAry;
var
i :integer;
begin
if assigned(TestAry) then //判断TestAry是否为nil(即是否有元素,有则释放)
for i :=Low(TestAry) to High(TestAry) do
begin
TestAry.Destroy;
end;
end;

procedure TTest.GetSubNum;
begin
......
FSubNum:=n//n是根据不同对象生成的不同的值
end;


procedure TTest.SetTestAry(Num: integer)
//自定义的创建过程,供需要时调用
var
ATestAry :TTestAry;
i :integer;
begin
SetLength(ATestAry,Num);
for i :=Low(ATestAry) to High(ATestAry) do
begin
ATestAry :=TTest.Create;
ATestAry.Tag :=i*2 + 1;
end;
TestAry :=ATestAry
//创建的子对象数组赋值给数组成员FTestAry
end;
 
刚试了一下,用修改后的TTest是可以的!只要遇到SubNum=0时就会终止!
to 5207
用TCollection怎么做?我不了解TCollection,能详细解释一下吗?
 
不要在 TTest的 Create 里再创建立 TTest, 这样会引起递归调用,不会符合原来的意思的。

可以尝试将 Create该为 Create(FSubNum: integer);
然后在procedure TTest.SetTestAry(Num: integer)
中的 TTest.Create(0);避免子类重复创建。

//不过还是建议类似 TComponent的方法,分别创建再组合,这样灵活性才好。
 
用TMyFactory负责复数个子对象的生成,可以按需要增加适当的方法:

//功能越写越夸张。。不过基本按 TComponent 的写法去做。

TMyClass = class
private
FOwner: TMyClass;
FMyClasses: TList;
function GetMyClassCount: integer;
function GetMyClassAllCount: integer;
function GetMyClasses(Index: integer): TMyClass;
procedure ReleaseMyClasses;
procedure ReleaseSelfInOwner;
public
procedure InsertMyClass(aMyClass: TMyClass);
property MyClasses[Index: integer]: TMyClass read GetMyClasses;
property MyClassCount: integer read GetMyClassCount;
property MyClassAllCount: integer read GetMyClassAllCount;
constructor Create
virtual;
destructor Destroy
override;
end;

TMyFactory = class
class function CreateMyClass(FSubNum: integer): TMyClass;
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

{ TMyClass }

constructor TMyClass.Create;
begin
FOwner := nil;
end;

destructor TMyClass.Destroy;
begin
ReleaseMyClasses;
ReleaseSelfInOwner;
inherited;
end;

function TMyClass.GetMyClasses(Index: integer): TMyClass;
begin
if FMyClasses = nil then Result := nil
else Result := FMyClasses[Index];
end;

function TMyClass.GetMyClassCount: integer;
begin
if FMyClasses = nil then Result := 0
else Result := FMyClasses.Count;
end;

function TMyClass.GetMyClassAllCount: integer;
var
i: integer;
begin
Result := GetMyClassCount;
for i := 0 to Result - 1 do
if MyClasses <> nil then
Result := Result + MyClasses.MyClassCount;
end;

procedure TMyClass.InsertMyClass(aMyClass: TMyClass);
begin
if aMyClass = nil then Exit;
if FMyClasses = nil then FMyClasses := TList.Create;
aMyClass.FOwner := self;
FMyClasses.Add(aMyClass);
// Pointer(aMyClass) := nil
//Cut of Source Link
end;

procedure TMyClass.ReleaseMyClasses;
var
i: integer;
begin
if FMyClasses <> nil then
for i := FMyClasses.Count - 1 downto 0 do
TObject(FMyClasses).Free;
FMyClasses.Free;
end;

procedure TMyClass.ReleaseSelfInOwner;
begin
if (FOwner <> nil) and (FOwner.FMyClasses <> nil) then
begin
with FOwner.FMyClasses do Delete(IndexOf(self));
if FOwner.FMyClasses.Count = 0 then
FreeAndNil(FOwner.FMyClasses);
end;
end;

{ TMyFactory }
class function TMyFactory.CreateMyClass(FSubNum: integer): TMyClass;
var
i: integer;
begin
Result := TMyClass.Create;
for i := 0 to FSubNum - 1 do
Result.InsertMyClass(TMyClass.Create);
end;

{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin
with TMyFactory.CreateMyClass(4) do
try
InsertMyClass(TMyFactory.CreateMyClass(3));
InsertMyClass(TMyClass.Create);
InsertMyClass(TMyFactory.CreateMyClass(5));
MyClasses[1].Free;
MyClasses[2].Free;
TButton(Sender).Caption := Format('%d, %d, %d',
[MyClasses[4].MyClassCount, MyClassCount, MyClassAllCount]);
finally
Free;
end;
end;
 
to 楼主:
对于 “FSubNum:=n//n是根据不同对象生成的不同的值”和“只要遇到SubNum=0时就会终
止!”倒是有些不太明白,因为你的参数 n 是怎么传进去的,什么时候有SubNum=0?
个人认为,Delphi中的对象都是显式创建的,这样有助于编程时提醒注意对象的管理,所
以倾向于使用procedure TTest.SetTestAry(Num: integer);在需要时创建同类成员,这样
对第几层子对象可以把握,如果令Create带参数 Create(FSubNum: integer);则容易形成同
类子对象递归调用Create,中止条件不好设计,管理也不方便,而且参数(FSubNum)如何传
递是个问题。
总之,我认为无论委托,还是需要时显式创建,都是先在内部留个位,然后外部创建后进
行赋值,自己根据具体需要管理好同类子对象。把握好基本原则就行:避免重复创建,对创
建的对象心中有数(什么时候创建的、什么时候销毁)。
 
多人接受答案了。
 
后退
顶部