PDA

View Full Version : مقاله: حل مشکل Circular unit reference توسط Class Helper ها



Felony
یک شنبه 30 مهر 1391, 15:07 عصر
سلام ،

در اکثر موارد وقتي تصميم ميگيريم که برنامه رو به کلاس هاي مجزا تقسيم کنيم و هر کلاس رو در کتابخانه اي جدا پياده کنيم در بخش هايي نياز ميشه که از کلاسي در کلاس ديگه استفاده بشه ، در اين مواقع بارها شده که با پيغاه خطا Circular unit reference مواجه شديد ، اين پيغام زماني مشاهده ميشه که از دو کتابخانه داخل يکديگر استفاده کرده باشيم ، مثلا :

unit Unit1;

interface

uses Unit2;

implementation

end.

unit Unit1;

interface

uses Unit1;

implementation

end.

يکي از راه حل ها در اين مواقع استفاده از Class Helper ها هست ( البته راه حل هايي مثل جدا کردن ساختارهايي که زياد استفاده ميشن در يک کتابخانه جداگانه يا طراحي مجدد و اصولي کلاسي که نوشته شده قبل از اين راه حل پيشنهاد ميشه ! )

خوب فرض کنيد کتابخانه هايي مثل اين :

unit Dog;

interface

type
TDog = class
Name: String;
end;

implementation

end.

و اين داريم :

unit Master;

interface

type
TMaster = class
Name: String;
end;

implementation

end.

خوب حالا تو برنامه براي استفاده از اين دو برادر عزيز ازشون نمونه سازي ميکنيم :

uses
Dog, Master;

var
Dog: TDog;
Master: TMaster;
begin
Dog := TDog.Create;
Master := TMaster.Create;
try
Dog.Name := 'Bobby';
Master.Name := 'Joe';
finally
Dog.Free;
Master.Free;
end;
end;

حالا تصميم ميگيريم به کلاس TDog متدي با نام Master اضافه کنيم ( از نوع TMaster ) که مشخصات مربي اين سگ رو نگهداري کنه :

unit Dog;

interface

uses Master;

type
TDog = class
Name: String;
Master: TMaster;
end;

implementation

end.

تا اينجا همه چي آرومه ، من چقدر خوشحالم ... :چشمک:

خوب حالا نياز داريم تو کلاس مربي هم يک متد با نام Dog داشته باشيم ( از نوع TDog ) که مشخصات سگ اين مربي رو نگهداري کنه ، خوب کد زير رو مينويسم :

unit Master;

interface

uses
Dog;

type
TMaster = class
Name: String;
Dog: TDog;
end;

implementation

end.

خوب ؟! همونطور که میبینید با پیغام خطا Circular unit reference عملیات کامپایل متوقف میشه .

اولین چیزی که به ذهن میرسه این هست که متد Dog رو از نوع TObject در نظر بگیریم ، یعنی :

unit Master;

interface

type
TMaster = class
Name: String;
Dog: TObject;
end;

implementation

end.

کد زیر بدون مشکل کامپایل میشه ولی بدیش این هست که برای هر کاری باید Typecast های متعددی انجام بدید ، مثلا برای نمایش نام مربی یک سگ :

Writeln(TDog(Master.Dog).Name);

و حالا راه حلی با استفاده از Class Helper ها ؛

اوین قدم تعریف Dog به صورت یک فیلد Protected در کلاس TMaster هست ، یعنی کلاس TMaster رو به صورت زیر مینویسم :

unit Master;

interface

type
TMaster = class
protected
fDog: TObject;
public
Name: String;
end;

implementation

end.

حالا کافیه به وسیله Class Helper ها یک پراپرتی برای کلاس TMaster بنویسیم تا شئ Dog رو به صورت یک نمونه از کلاس TDog به ما تحویل بده :

unit MasterClassHelper;

uses
Dog, Master;

type
TMasterClassHelper = class helper for TMaster
private
procedure SetDog(Value: TDog);
function GetDog: TDog;
public
property Dog: TDog read GetDog write SetDog;
end;

function TMasterClassHelper.GetDog: TDog;
begin
Result := TDog(FDog); // cast the TObject(FDog) to TDog!
end;

procedure TMasterClassHelper.SetDog(Value: TDog);
begin
FDog := Value;
end;

و حالا تو برنامه خیلی راحت بدون هیچ Typecast ی میتونیم از کلاس TMaster و TDog استفاده کنیم :

Writeln(Master.Dog.Name);
Writeln(Dog.Master.Name);

حتی :
Writeln(Dog.Master.Dog.Master.Dog.Master.Dog.Name) ;

:چشمک: آقا جنبه داشته باش !

همچنین با use کردن کتابخانه MasterClassHelper در کتابخانه Master علاوه بر کلاس TMaster میتونید به کلاس TDog هم دسترسی داشته باشید :

Unit unit1;

type
TMaster = class
protected
FDog: TObject;
public
Name: String;
procedure RenameDog;
end;

implementation

uses
MasterClassHelper;

procedure TMaster.RenameDog;
begin
Dog.Name := 'Buddy';
end;

در آخر اگر مجبور به استفاده از این مطلب شدید بهتره یه جفت کتاب در مورد اصول برنامه نویسی شئ گرا بخونید و تجدید نظری در مورد طراحی کلاس هاتون بکنید .

یا حق .

BORHAN TEC
یک شنبه 30 مهر 1391, 15:20 عصر
سلام

مقاله رو هنوز کامل نخوندم ولی یک راه حل خیلی ساده هم برای رفع خطای Circular unit reference وجود داره و اون هم اینه که یکی از یونیت ها رو در قسمت Uses مربوط به Interface بنویسیم و دیگری رو در قسمت Uses مربوط به Implementation. البته این راه حل زیاد جالب نیست ولی در برخی موارد مثل مسکن عمل می کنه.
مثال:

کد مربوط به یونیت A:
unit A;

Interface
uses
B;
//...

implementation

//...

end.


کد مربوط به یونیت B:
unit B;

Interface

//...

implementation

uses
A;
//...

end.



موفق باشید...

Felony
یک شنبه 30 مهر 1391, 15:28 عصر
در این صورت نمیتونیم از کلاسی که مثلا در کتابخانه اولی تعریف کردیم در کلاس کتابخانه دوم به عنوان Data Type یک فیلد استفاده کنیم ، مثلا :

کتابخانه اول :

unit Unit2;

interface

uses Unit3;

type
TDog = class
Name: String;
Master: TMaster;
end;

implementation

end.

کتابخانه دوم :

unit Unit3;

interface

implementation

uses
unit2;

type
TMaster = class
protected
fDog: TDog;
public
Name: String;
end;

end.

Scope تعریف کلاس ها باعث میشه که همدیگرو نتونن ببینن .