PDA

View Full Version : گفتگو: بررسی بازی Minesweeper از لحاظ شئ گرائی



Felony
چهارشنبه 04 خرداد 1390, 22:29 عصر
:لبخندساده: سلام ؛

چند وقتی هست که درگیر درس خوندن برای کنکورم و کمتر به اینجا سر میزنم و در این مدت در اوقات بیکاری و استراحت در مورد مباحث شئ گرائی مطالعه میکردم ؛ با توجه به اینکه هیچ چیزی جای تجربه رو نمیگیره دیشب تصمیم گرفتم بازی Minesweeper رو با رعایت قوانین شئ گرائی بنویسم ، چون تجربه حرفه ای در زمینه شئ گرائی ندارم با آقای کشاورز صحبت کردم و تصمیم گرفتم این پروژه رو اینجا قرار بدم و ایشون یا دوستانی که در زمینه شئ گرائی فعالیت حرفه ای داشتن مشکلات کار رو از دید شئ گرائی اعلام کنن تا اون ها رو برطرف کنم تا خودم و دوستانی که مایل هستن در این زمینه تجربه کسب کنن و یه تکونی هم به این بخش داده بشه ...

بازی هنوز کامل نیست ، چون دیدم اگر بخوام کاملش کنم حجم کدها بالا میره و چون از درست بودن تحلیل شئ گرائیم مطمئن نیستم ممکنه مجبور شم کلی از کدها رو تغییر بدم به همین دلیل تا همین جا نوشتم و منتظر هستم تا دوستان با تجربه نگاهی به کد بندازن و تا اینجا کار مشکلات رو بگن تا برطرف کنم تا بعد بریم سراغ قسمت های بعدی .

فعلا موارد زیر در بازی پیاده سازی شده :

- بازی جدید شامل ، ایجاد صفحه ، ایجاد خانه ها ، ایجاد بمب به صورت تصادفی
- تنظیمات

* خانه هایی که شامل بمب هستن با کاراکتر "." نمایش داده شدن تا برای تست کار آسون باشه و دنبال خانه های بمب دار نگردم ؛ در نسخه نهایی این مورد حذف خواهد شد .

* بازی با استفاده از Delphi XE نوشته شده ولی در اون از توابع یا کامپوننت خاصی استفاده نشده و میتونید تو نسخه های دیگه هم اجراش کنید .

* از دوستانی که در مورد بازی نظر میدن خواهش میکنم ترجیحا کدها رو به صورت عملی دستکارِی نکنن و قرار ندن ؛ فقط مشکل رو بگن تا هر کس برای خودش کدها رو تغییر بده .

javad p
چهارشنبه 04 خرداد 1390, 23:27 عصر
سلام و خسته نباشید

از دوستانی که در مورد بازی نظر میدن خواهش میکنم ترجیحا کدها رو به صورت عملی دستکار نکنن و قرار ندن ؛ فقط مشکل رو بگن تا هر کس برای خودش کدها رو تغییر بده
راسش من فقط نظرم رو میگم
به نظر من این کد خیلی ابتدایی هست (منظورم اینه که ابتدای راه هست) و فقط base کار رو ایجاد میکنه
مثلا بهتر بود برای شروع، هر کدوم از دکمه هایی که بمب نیستند بتونند تعداد بمبهای اطرافشون رو نمایش بدهند
یه فاکتور دیگه هم تو این بازی نسبت تعداد بمب به کل دکمه هاست. نظرم اینه که اگر بتونه متغییر باشه (مثلا برای تعیین میزان سختی یا آسونی) بهتره
موفق باشید

Felony
چهارشنبه 04 خرداد 1390, 23:53 عصر
خوب پیاده سازی اون مورد نمایش تعداد بمب های اطراف کاری نداره ولی همونطور که گفتم منتظرم ببینم تا همین جای کار چه قدر از لحاظ شئ گرائی اصولی پیش رفته ، در مورد نسبت تعداد بمب ها هم ایده جالبی هست ، به To Do List اضافه کردمش .

vcldeveloper
پنج شنبه 05 خرداد 1390, 05:18 صبح
خب گیرهای مختلفی میشه ازش گرفت؛ البته کار خوبیه که سورس کدی به اشتراک گذاشته بشه و تصحیح بشه، چون اصولا املای نانوشته غلط نداره! مواردی که من با یک بار مرور کد بهشون برخوردم رو بدون اولویت بندی خاصی، اینجا ذکر می کنم...

اولین موردی که جلب توجه میکنه، تعریف متغیرهای عمومی برای داده های برنامه هست که این کار اساسا خلاف شی گرایی هست، به عنوان مثال متغیرهایی مثل MinesweeperGame یا Option به طور عمومی (Global) تعریف شدند، نه نباید می شدند. اینها باید به عنوان فیلد private در کلاس های استفاده کننده ازشان تعریف می شدند.

مورد دیگه ایی که به چشم میاد، نوشتن کد برای برگشت دادن ModalResult برای فرم های Modal هست، که نیازی به نوشتن این کدها نبود و تنظیم خصوصیت ModalResult دکمه های مربوطه برای این کار کفایت می کرد.

مورد بعدی، نحوه پیاده سازی کلاس TOption هست که اصلا جالب نیست؛ در واقع متدهای این کلاس دارند مرتبا برای هر کار کوچیکی یک فایل INI رو برای خواندن و نوشتن باز می کنند! در حالی که کافی بود یک بارخواندن فقط در زمان ایجاد شی مربوطه و یک بار هم نوشتن در زمان آزاد کردن شی مربوطه انجام بشه.

پیاده سازی کلاس TfrmOptions هم چندان جالب نیست، هر چند که ساده هست.

یک چیز دیگه هم که من شخصا ازش خوشم نمیاد، نحوه فراخوانی فرم های Modal در برنامه هست، که من شخصا وجود یک class function در فرم مربوطه برای انجام اینجور فراخوانی ها رو ترجیح میدم.

استفاده از رویداد گردان ها (event-handlers) به عنوان متدهای اصلی نگهداری کننده منطق برنامه هم کار جالبی نیست؛ یعنی در رویداد گردان ها باید تا حد امکان کمتر کد بنویسید، و از آنها برای فراخوانی متدهایی که کار مربوطه را انجام میدهند استفاده کنید، اینطوری کد شما کمتر به کامپوننت ها یا اجزای رابط کاربر وابسته میشه و تغییر رابط کاربر در آن ساده تر میشه. به عنوان مثال، شما اومدید برای رویداد OnActivate فرم اصلی کد NewGame1.Click رو نوشتید، که این کد خودش میاد متد NewGame رو فراخوانی میکنه، در حالی که اصلا نیازی به وابسته کردن کد خودتان به NewGame1.Click نبود. الان اگر شما بخواید اون گزینه منو را حذف کنید، باید کد مربوط به OnActivate فرم تان را هم تغییر بدید. یا مورد دیگه، کدهایی که برای نمایش فرم های Modal پروژه نوشتید که حتی اگر قرار بود به این شکل استفاده بشند، بهتر بود متدهایی مثل ShowAbout، یا ShowOptions تعریف می کردید، و در رویداد های OnClick مربوطه این متدها را فراخوانی می کردید.

مورد دیگه نحوه تعامل کلاس TMinesweeperبا فرم اصلی هست که شخصا ترجیح می دادم به جای همچین کاری، TMinesweeper به صورت یک کامپوننت مستقل طراحی میشد؛ یک کامپوننت شامل یک پنل و تعدادی دکمه.

موارد دیگه ایی هم ممکنه باشه که با بررسی دقیق تر کد بشه متوجه شون شد.


نتیجه گیری...

در برنامه نویسی شی گرا، باید همیشه حواستون به چگونگی تعامل اشیاء با هم باشه؛ باید همیشه این براتون مسئله باشه که یک شی با چه اشیائی تعامل برقرار میکنه، و چه چیزهایی رو با اونها به اشتراک میذاره؟ هر چه تعاملات یک شی با سایر اشیاء بیشتر باشه، ایجاد تغییر در کدهای اون در آینده، و استفاده از اون شی در سایر کدها سخت تر و پیچیده تر میشه.

همیشه باید سعی کنید که نقاط ورود و خروج داده در یک شی رو در نظر داشته باشید، یک شی از نقطه یا نقاط مشخص شده داده مورد نیازش را تامیین کنه، در داخل خودش پردازش لازم را انجام بده، و خروجی را هم از نقطه یا نقاط مشخص شده در اختیار سایر اشیاء قرار بده. مثل گمرک، نه اینکه هر شی ایی هر چیزی رو از هر جایی استفاده کنه، و به هر چیزی در هر جایی خروجی بفرسته، این نتیجه اش میشه اسپاگتی کد!

Felony
پنج شنبه 05 خرداد 1390, 07:07 صبح
ممنون علی جان که وقت گذاشتی .


اولین موردی که جلب توجه میکنه، تعریف متغیرهای عمومی برای داده های برنامه هست که این کار اساسا خلاف شی گرایی هست، به عنوان مثال متغیرهایی مثل MinesweeperGame یا Option به طور عمومی (Global) تعریف شدند، نه نباید می شدند. اینها باید به عنوان فیلد private در کلاس های استفاده کننده ازشان تعریف می شدند.
در کلاس های استفاده کننده ؟ یعنی الان که من دارم ازشون تو کلاس TForm استفاده میکنم اونها رو در قسمت Private کلاس TForm مربوط به همون فرمی که ازشون استفاده میکنه قرار بدم ؟


مورد دیگه ایی که به چشم میاد، نوشتن کد برای برگشت دادن ModalResult برای فرم های Modal هست، که نیازی به نوشتن این کدها نبود و تنظیم خصوصیت ModalResult دکمه های مربوطه برای این کار کفایت می کرد.
تصحیح شد .


مورد بعدی، نحوه پیاده سازی کلاس TOption هست که اصلا جالب نیست؛ در واقع متدهای این کلاس دارند مرتبا برای هر کار کوچیکی یک فایل INI رو برای خواندن و نوشتن باز می کنند! در حالی که کافی بود یک بارخواندن فقط در زمان ایجاد شی مربوطه و یک بار هم نوشتن در زمان آزاد کردن شی مربوطه انجام بشه.
این مورد رو جور دیگه ای نمیشه پیاده کرد ، ببینید اون کلاس TOption تو بازی فقط توسط متد NewGame کلاس TMinesweeper صدا زده میشه ، یعنی هر وقت شما تصمیم به ساخت یک بازی جدید گرفتید متد NewGame اول تنظیمات داخل فایل رو میخونه و بعد با توجه به اون تنظیمات بازی رو میسازه .

یک جا هم در بخش نمایش و ذخیره تنظیمات در فرم تنضیمات صدا زده میشه .

برای این به صورتی که شما میگید پیاده سازیش نکردم که کاربر ممکنه وسط بازی توسط فرم تنظیمات یا به صورت دستی تنظیمات داخل فایل رو تغییر بده و اون وقت دیگه تا زمانی که بازی یک بار بسته و باز نشه تنظیمات جدید اعمال نمیشه و هر چقدر NewGame رو بزنه تنظیمات قبلی که موقع نمونه ساخت شئ انجام شده بود بار میشه .


پیاده سازی کلاس TfrmOptions هم چندان جالب نیست، هر چند که ساده هست.
خوب اخوی یه راهنمایی بکن ، الان من چه کاری بکنم تا درست شه ؟


یک چیز دیگه هم که من شخصا ازش خوشم نمیاد، نحوه فراخوانی فرم های Modal در برنامه هست، که من شخصا وجود یک class function در فرم مربوطه برای انجام اینجور فراخوانی ها رو ترجیح میدم.
منظورتون این هست که یک تابع در بخش Public کلاس مربوط به فرم مورد نظر تعریف کنم ، مثلا :

Procedure OptionModal;

و به صورت زیر پیاده سازیش کنم :
procedure TfrmOption.OptionModal;
begin
// Create an instance of TfrmOption & show it modaly & finally free it
frmOption := TfrmOption.Create(nil);
try
frmOption.ShowModal;
finally
frmOption.Free;
end;
end;

حالا هر جا که نیاز به فراخوانی فرم بود این تابع رو صدا بزنم ؟


استفاده از رویداد گردان ها (event-handlers) به عنوان متدهای اصلی نگهداری کننده منطق برنامه هم کار جالبی نیست؛ یعنی در رویداد گردان ها باید تا حد امکان کمتر کد بنویسید، و از آنها برای فراخوانی متدهایی که کار مربوطه را انجام میدهند استفاده کنید، اینطوری کد شما کمتر به کامپوننت ها یا اجزای رابط کاربر وابسته میشه و تغییر رابط کاربر در آن ساده تر میشه. به عنوان مثال، شما اومدید برای رویداد OnActivate فرم اصلی کد NewGame1.Click رو نوشتید، که این کد خودش میاد متد NewGame رو فراخوانی میکنه، در حالی که اصلا نیازی به وابسته کردن کد خودتان به NewGame1.Click نبود. الان اگر شما بخواید اون گزینه منو را حذف کنید، باید کد مربوط به OnActivate فرم تان را هم تغییر بدید. یا مورد دیگه، کدهایی که برای نمایش فرم های Modal پروژه نوشتید که حتی اگر قرار بود به این شکل استفاده بشند، بهتر بود متدهایی مثل ShowAbout، یا ShowOptions تعریف می کردید، و در رویداد های OnClick مربوطه این متدها را فراخوانی می کردید.
اون کار به دلیل اشتباه بودن پیاده سازی کلاسم بود که در زمان NewGame کردن بازی باید پارامترهای تنضمیات رو بهش میدادی که من مستقیما میومدم و فایل Option.ini رو در event handler میحوندم و بعد به NewGame پاس میدادم ؛ بعد که روش فکر کردم دیدم اشتباه هست و پیاده سازی تغییر دادم تا نیازی به اون کدها نباشه و مستقیما با فراخوانی NewGame بازی ساخته بشه ولی یادم رفت کد مربوط به OnActivate رو تغییر بدم .


مورد دیگه نحوه تعامل کلاس TMinesweeperبا فرم اصلی هست که شخصا ترجیح می دادم به جای همچین کاری، TMinesweeper به صورت یک کامپوننت مستقل طراحی میشد؛ یک کامپوننت شامل یک پنل و تعدادی دکمه.
بله ایده خوبیه ، اگر امکان داره فعلا با همین ترتیب کدها رو کامل کنم و بعد در پایان اگر شد کدها رو به صورت یک کامپوننت پیاده میکنم .

مشکلاتی که گفتید رو رفع و فایل جدید رو ضمیمه کردم ( پست شماره 10 )، لطفا بررسی کنیدش ، باز هم ممنون بابت وقتی که میزارید .

SAASTN
پنج شنبه 05 خرداد 1390, 12:50 عصر
آقا مجتبی خسته نباشی.

برای این به صورتی که شما میگید پیاده سازیش نکردم که کاربر ممکنه وسط بازی توسط فرم تنظیمات یا به صورت دستی تنظیمات داخل فایل رو تغییر بده و اون وقت دیگه تا زمانی که بازی یک بار بسته و باز نشه تنظیمات جدید اعمال نمیشه و هر چقدر NewGame رو بزنه تنظیمات قبلی که موقع نمونه ساخت شئ انجام شده بود بار میشه .
خوب من تصور می کنم TOption از نظر چرخه حیات باید طول عمرش برابر خود TMinesweeper باشه یعنی باید خودش پراپرتی TMinesweeper باشه. یعنی تو Create بازی ایجاد بشه و تو آزادسازی بازی هم آزاد بشه. تمام پراپرتی هاش هم تنها از فیلد بخونن، فقط موقع ایجاد یا آزادسازی با فایل کار کنن.
بازم اگه فکر می کنید اینجوری از نظر مالکیت درست نیست، باید یه کلاس بالاتری باشه که TOption و TMinesweeper هر دو پراپرتی های اون باشند.

خوب اخوی یه راهنمایی بکن ، الان من چه کاری بکنم تا درست شه ؟
من فرمای دیالوگم رو اینجوری پیاده سازی می کنم، دوستان هم یه نظری بدن ممنون می شم:
type
TFormMyOptions = class(TForm)
Button1: TButton;
procedure ControlsChange(Sender: TObject);
private
InternalOptions: TOption;
procedure ReadControls(aOptions: TOption);
procedure WriteControls(aOptions: TOption);
public
function GetOptions(aOptions: TOption): Integer;
end;

var
FormMyOptions: TFormMyOptions;

implementation

{$R *.dfm}

procedure TFormMyOptions.ControlsChange(Sender: TObject);
begin
ReadControls(InternalOptions);
end;

function TFormMyOptions.GetOptions(aOptions: TOption): Integer;
begin
InternalOptions.Assign(aOptions);
WriteControls(InternalOptions);
if ShowModal = mrOk then
aOptions.Assign(InternalOptions);
end;

procedure TFormMyOptions.ReadControls(aOptions: TOption);
begin
// setting aOptions properties
end;

procedure TFormMyOptions.WriteControls(aOptions: TOption);
begin
// setting Controls properties
end;

البته این کد برای شرایط عمومی تریه، اگه فرم مثل فرم شما استاتیک باشه اصلا نیازی به اون InternalOptions نیست، و نیازی هم به ست کردن رویدادای کنترلا نیست. این شرایط برای دیالوگیه که مثلا در حین تغییر یه مقداری تو فرم قراره یه Preview هم نمایش بده. اگه نیازی به این کار نباشه GetOptions به اینصورت درمیاد:
function TFormMyOptions.GetOptions(aOptions: TOption): Integer;
begin
WriteControls(aOptions);
if ShowModal = mrOk then
ReadControls(aOptions);
end;

بعد یه گیر الکی:
property Col: Byte read ColRead write ColWrite;

چرا برای اسم متدای پراپرتی ها تون از همون Get و Set پیشفرض استفاده نمی کنید؟
----
ضمنا تصور می کنم اون Square ها خودشون باید یه کلاس دیگه باشن که سطر و ستون و حاوی بمب بودن و خود دکمه، پراپرتی های اون کلاس هستند. بعد کلاس TMinesweeper هم یه لیست ازشون نگهداری کنه. الان دسترسی به اون دکمه های ایجاد شده از بین رفته، اگه بعدا بخواید براشون کد دیگه ای بنویسید نمی تونید.

Felony
پنج شنبه 05 خرداد 1390, 13:11 عصر
خوب من تصور می کنم TOption از نظر چرخه حیات باید طول عمرش برابر خود TMinesweeper باشه یعنی باید خودش پراپرتی TMinesweeper باشه. یعنی تو Create بازی ایجاد بشه و تو آزادسازی بازی هم آزاد بشه. تمام پراپرتی هاش هم تنها از فیلد بخونن، فقط موقع ایجاد یا آزادسازی با فایل کار کنن.
چرا ؟ وقتی با این کلاس کار نداریم چه دلیلی داره از اول کار تا آخر کار ساخته شده باشه و فضا اشغال کنه ؟ در کل بازی 3 جا از این کلاس استفاده میشه ( NewGame ، نمایش فرم Option ، ذخیره تغییرات در فرم Option ) که این امکانات تو یک بازی ممکنه فقط 1 بار استفاده بشه ! فکر نمیکنم نیاز به همچن چرخه حیات طولانی باشه .


چرا برای اسم متدای پراپرتی ها تون از همون Get و Set پیشفرض استفاده نمی کنید؟
خدائیش الکی بود ...

SAASTN
پنج شنبه 05 خرداد 1390, 14:01 عصر
وقتی با این کلاس کار نداریم چه دلیلی داره از اول کار تا آخر کار ساخته شده باشه و فضا اشغال کنه ؟
یعنی چی کاری نداریم؟ تا حالا کاری نداشتیم، ولی به محض اینکه بخوایم کوچکترین پیمایشی روی خانه های بازی بکنیم به مقادیر موجود در این کلاس احتیاج پیدا می کنیم. بعدشم خوب این اول کاره که کلاس Option در این حده، بعدا اگه بخواید گسترشش بدید چیزای دیگه ای هم باید اضافه بشه. کلا مفهوم تنظیمات تو برنامه ها یه مجموعه مقادیری هست که تو کل روند اجرای برنامه ممکنه بهشون احتیاج پیدا کنیم.

خدائیش الکی بود ...
آره ولی خوب اینام یجور قوانین نا نوشته هستن دیگه، دقیقا مثل همون T می مونه که اول انواع داده ای خودتون هم وجود داره، یه قرادادیه که هیچ جا ثبت نشده ولی من و شما جفتمون بهش عمل می کنیم، اگه یکی عمل نکنه آدم یه گیر الکی بهش میده...:بامزه:

Felony
پنج شنبه 05 خرداد 1390, 14:19 عصر
یعنی چی کاری نداریم؟ تا حالا کاری نداشتیم، ولی به محض اینکه بخوایم کوچکترین پیمایشی روی خانه های بازی بکنیم به مقادیر موجود در این کلاس احتیاج پیدا می کنیم. بعدشم خوب این اول کاره که کلاس Option در این حده، بعدا اگه بخواید گسترشش بدید چیزای دیگه ای هم باید اضافه بشه. کلا مفهوم تنظیمات تو برنامه ها یه مجموعه مقادیری هست که تو کل روند اجرای برنامه ممکنه بهشون احتیاج پیدا کنیم.
فعلا کد رو در این اندازه بررسی کنید ، من با توجه به کد فعلی گفتم نیازی نیست ، اگر کدها اضافه بشن و نیاز به اون کار باشه انجامش کار 10 ثانیه هست ...


آره ولی خوب اینام یجور قوانین نا نوشته هستن دیگه، دقیقا مثل همون T می مونه که اول انواع داده ای خودتون هم وجود داره، یه قرادادیه که هیچ جا ثبت نشده ولی من و شما جفتمون بهش عمل می کنیم، اگه یکی عمل نکنه آدم یه گیر الکی بهش میده...
:گیج: ایول ... !
به خاطر شما عوضشون میکنم ... !

Felony
پنج شنبه 05 خرداد 1390, 19:00 عصر
سلام ،

- قسمت نمایش تعداد بمب های اطراف اضافه شد .
- به خاطر SAASTN نام متدهای Property های کلاس TOption رو هم تغییر دادم !
- 2 تا باگ هم پیدا کردم که رفع شدند .

علی آقا منتظرم ...

vcldeveloper
جمعه 06 خرداد 1390, 00:50 صبح
خدائیش الکی بود ... الکی نبود، بلکه در هنگام کد نویسی باید سعی کنید که همیشه یک Coding Convention مشخص را رعایت کنید، بخصوص اگر بخواید در یک تیم کار کنید. اینطوری سایر برنامه نویسان، بخصوص اعضاء تیم تان می تونند راحتر کد شما رو بخونند و متوجه بشند. یکی از اولین کارهایی که باید در یک تیم انجام بدید، همین تعیین Coding Convention هست، تا مشخص بشه همه اعضاء تیم باید بر اساس چه اصولی کد بنویسند.



در کلاس های استفاده کننده ؟ یعنی الان که من دارم ازشون تو کلاس TForm استفاده میکنم اونها رو در قسمت Private کلاس TForm مربوط به همون فرمی که ازشون استفاده میکنه قرار بدم ؟بله، چون شی مربوطه فقط در فرم اصلی برنامه شما استفاده میشه، در داخل همون فرم ساخته میشه، و در داخل همون فرم هم آزاد میشه؛ پس اون شی متعلق به اون فرم هست و باید در همون فرم تعریف بشه. اصلا در برنامه نویسی شی گرا چیزی خارج از کلاس ها نداریم.


منظورتون این هست که یک تابع در بخش Public کلاس مربوط به فرم مورد نظر تعریف کنم ، مثلمنظورم چیزی مثل این هست:

TfrmAbout = class(TForm)
...
private
{ Private declarations }
public
class function Execute: Boolean;
end;

implementation

{$R *.dfm}

class function TfrmAbout.Execute: Boolean;
begin
with TfrmAbout.Create(nil) do
try
Result := (ShowModal = mrOK);
finally
Free;
end;
end;


حالا برای نمایش این فرم فقط کافیه بنویسید:

TfrmAbout.Execute;



این مورد رو جور دیگه ای نمیشه پیاده کرد ، ببینید اون کلاس TOption تو بازی فقط توسط متد NewGame کلاس TMinesweeper صدا زده میشه ، یعنی هر وقت شما تصمیم به ساخت یک بازی جدید گرفتید متد NewGame اول تنظیمات داخل فایل رو میخونه و بعد با توجه به اون تنظیمات بازی رو میسازه .

یک جا هم در بخش نمایش و ذخیره تنظیمات در فرم تنضیمات صدا زده میشه .

برای این به صورتی که شما میگید پیاده سازیش نکردم که کاربر ممکنه وسط بازی توسط فرم تنظیمات یا به صورت دستی تنظیمات داخل فایل رو تغییر بده و اون وقت دیگه تا زمانی که بازی یک بار بسته و باز نشه تنظیمات جدید اعمال نمیشه و هر چقدر NewGame رو بزنه تنظیمات قبلی که موقع نمونه ساخت شئ انجام شده بود بار میشه .خیر، برای رسیدن به این منظور، نیازی به همچین پیاده سازی ایی نبود. من اگر میخواستم اون کلاس رو طراحی و پیاده سازی کنم، اینطوری انجام می دادم:

type
TOptions = class(TPersistent)
private
FBackColor : TColor;
FCol : Integer;
FFileName : TFileName;
FRow : Integer;
procedure SetFileName(const Value: TFileName);
public
constructor Create(const OptionsFileName: TFileName);
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
procedure SaveToFile;
property BackColor : TColor read FBackColor write FBackColor;
property Col : Integer read FCol write FCol;
property FileName : TFileName read FFileName write SetFileName;
property Row : Integer read FRow write FRow;
end;

implementation

uses IniFiles;

{ TOtions }

procedure TOptions.Assign(Source: TPersistent);
begin
if Source is TOptions then
begin
FBackColor := TOptions(Source).BackColor;
FCol := TOptions(Source).Col;
FFileName := TOptions(Source).FileName;
FRow := TOptions(Source).Row;
end
else
inherited;
end;

constructor TOptions.Create(const OptionsFileName: TFileName);
begin
inherited Create;
Self.FileName := OptionsFileName;
end;

destructor TOptions.Destroy;
begin

inherited;
end;

procedure TOptions.SaveToFile;
var
IniFile : TIniFile;
begin
Assert(FFileName <> '', 'Filename property is empty');
ForceDirectories(ExtractFilePath(FFileName));
IniFile := TIniFile.Create(FFileName);
try
IniFile.WriteInteger('Options','BackColor',FBackCo lor);
IniFile.WriteInteger('Options','Col',FCol);
IniFile.WriteInteger('Options','Row',FRow);
finally
IniFile.Free;
end;
end;

procedure TOptions.SetFileName(const Value: TFileName);
var
IniFile : TIniFile;
begin
FFileName := Value;
if FileExists(FFileName) then
begin
IniFile := TIniFile.Create(FFileName);
try
FBackColor := IniFile.ReadInteger('Options','BackColor',0);
FCol := IniFile.ReadInteger('Options','Col',8);
FRow := IniFile.ReadInteger('Options','Row',8);
finally
IniFile.Free;
end;
end;
end;

اون وقت اگر بر فرض بخوام در جایی مثل TfrmOption ازش استفاده کنم، TfrmOption رو اینطوری می نوشتم:

type
TfrmOption = class(TForm)
...
private
{ Private declarations }
public
class function Execute(Options: TOptions): Boolean;
end;

implementation

{$R *.dfm}

class function TfrmOption.Execute(Options: TOptions): Boolean;
begin
Assert(Assigned(Options),'Options parameter is invalid!');

with TfrmOption.Create(nil) do
try
/// Initialize the form with given options.
spedCol.Value := Options.Col;
spedRow.Value := Options.Row;
btncBackgroundColor.SymbolColor := Options.BackColor;

Result := (ShowModal = mrOK);
/// If the form is confirmed, then put new values back into Options parameter.
if Result then
begin
Options.Col := spedCol.Value;
Options.Row := spedRow.Value;
Options.BackColor := btncBackgroundColor.SymbolColor;
end;
finally
Free;
end;
end;

و در فرم اصلی برنامه یک فیلد FOptions از نوع TOptions تعریف می کردم و شی مربوطه رو در زمان ساخت فرم می ساختم، و برای فرم هم یک متد با نام ShowOptions ایجاد می کردم و براش همچین کدی می نوشتم:

procedure TfrmMain.ShowOptions;
begin
Assert(Assigned(FOptions),'FOptions is not initialized!');

if TfrmOption.Execute(FOptions) then
FOptions.SaveToFile;
end;




کلاس TMinesweeper هم نیازی به استفاده از TOptions نداره، وقتی خودش خصوصیات Col و Row و غیره داره، باید به همون ها مقدار بدید، نه اینکه اونها رو محض دکور بذارید و در داخل کدهای این کلاس خودتون یک شی جدید از TOption ایجاد کنید و از اون مقادیر رو بخونید. یک نمونه از TOptions که در فرم اصلی وجود داشته باشه، کفایت میکنه. فرم اصلی میتونه بعد از ساختن یک نمونه از TMinesweeper، تنظیمات مورد نیاز شی مربوطه رو از شی FOptions خودش بخونه، و بر روی خصوصیات مربوطه در TMinesweeper اعمال کنه، یا اینکه TMinesweeper این داده ها را به عنوان پارامتر متد سازنده خودش دریافت کنه.

SAASTN
جمعه 06 خرداد 1390, 23:42 عصر
وقتی خودش خصوصیات Col و Row و غیره داره، باید به همون ها مقدار بدید،
اگه خودش به TMinesweeper اشاره می کنه که در پیاده سازی فعلی همچین چیزی وجود نداره.

کلاس TMinesweeper هم نیازی به استفاده از TOptions نداره، وقتی خودش خصوصیات Col و Row و غیره داره، باید به همون ها مقدار بدید، نه اینکه اونها رو محض دکور بذارید و در داخل کدهای این کلاس خودتون یک شی جدید از TOption ایجاد کنید و از اون مقادیر رو بخونید. یک نمونه از TOptions که در فرم اصلی وجود داشته باشه، کفایت میکنه. فرم اصلی میتونه بعد از ساختن یک نمونه از TMinesweeper، تنظیمات مورد نیاز شی مربوطه رو از شی FOptions خودش بخونه، و بر روی خصوصیات مربوطه در TMinesweeper اعمال کنه، یا اینکه TMinesweeper این داده ها را به عنوان پارامتر متد سازنده خودش دریافت کنه.
من یه نکته گنگی برام وجود داره، الان دقیقا تعریفتون از TOption چیه؟ چیزی که من از این توضیحات و نمونه کد پیشنهادیتون برداشت می کنم اینه که TOption وظیفش ذخیره و بازیابی یه سری از مشخصات TMinesweeper تو فایله. که اگه اینطور باشه TOption نام بی مسمی ایه. و باز هم اگه اینطور باشه بهتر نیست ذخیره و بازیابی مشخصات کلاس TMinesweeper رو به خود TMinesweeper محول کنیم؟ اساسا ایجاد یه کپی از یه سری مقادیر که در یه جایی نگهداری میشن چه لزومی داره؟
البته ببخشید من وارد اینجور چیزا میشما، چون تصورم اینه که تاپیک بیشتر روی مفاهیم شیئ گرایی تاکید داره نه جواب گرفتن از کد. من خودم بعضی وقتا تو تعریف کلاسام از اینجور مشکلا دارم: هر کلاس نقشش تو برنامه چیه؟ فلان کار رو بهتره به کدوم کلاس محول کنم؟ فلان مقدار رو بهتره تو کدوم کلاس نگه دارم؟ و از این قبیل شبهات ...

vcldeveloper
شنبه 07 خرداد 1390, 02:51 صبح
من یه نکته گنگی برام وجود داره، الان دقیقا تعریفتون از TOption چیه؟ چیزی که من از این توضیحات و نمونه کد پیشنهادیتون برداشت می کنم اینه که TOption وظیفش ذخیره و بازیابی یه سری از مشخصات TMinesweeper تو فایله. که اگه اینطور باشه TOption نام بی مسمی ایه. و باز هم اگه اینطور باشه بهتر نیست ذخیره و بازیابی مشخصات کلاس TMinesweeper رو به خود TMinesweeper محول کنیم؟ اساسا ایجاد یه کپی از یه سری مقادیر که در یه جایی نگهداری میشن چه لزومی داره؟نه، من درکی که از کارکرد TOption در اون برنامه دارم، اون نیست؛ اگر قرار باشه صرفا خصوصیات یک شی از TMineSweeper نگهداری بشه، این میشه Persistent کردن یک شی، که راهکارهای بهتری براش هست. درک من این هست که برنامه کلا برای نگهداری تنظیمات خودش از یک فایل INI استفاده میکنه، و باید مقادیر این فایل INI در داخل برنامه در دسترس باشه. به خودِ کلاس TMineSweeper ربطی نداره که برنامه تنظیماتش را چطور نگهداری میکنه، یا اصلا آیا یک شی از کلاس TMineSweeper با مقادیر پیش فرضش استفاده میکنه یا نمیکنه. کلاس TMineSweeper یک عملیات خاص انجام میده، و برای تنظیم چگونگی انجام اون عملیات توسط کاربر (یعنی اشیائی که از این کلاس استفاده می کنند) یک سری خصوصیات در اختیار کاربر قرار میده. البته اینها با این پیش فرض هست که برنامه هنوز تکمیل نشده، و احتمالا در مراحل بعدی، تنظیمات دیگه ایی هم وارد اون فایل INI خواهد شد که لزوما ارتباطی با کلاس TMineSweeper ندارند. این پیش فرض هم بیشتر از روی انتخاب نوع INI برای فایل، و انتخاب نام کلاس برای من حاصل شد، چون از فایل های INI معمولا برای ذخیره سازی پیکربندی یک برنامه استفاده میشه، و نام کلاس هم اینطور میرسونه که این کلاس قراره تنظیمات کل برنامه رو نگهداری کنه، نه فقط یک شی خاص رو.

اگر قرار بود که فقط خصوصیات TMineSweeper ذخیره بشه، اگر لازم بود همه جزئیات ذخیره بشه، من ترجیح می دادم که TMineSweeper به صورت یک کامپوننت تعریف بشه، و اون کامپوننت با استفاده از قابلیت های Streaming دلفی مستقیما به یک فایل Stream بشه، یا اینکه فرم اصلی برنامه Stream بشه به یک فایل. اگر هم قرار بود فقط همین چند خصوصیت محدود در فایل ذخیره بشند، من تقریبا همین تعریف و پیاده سازی فعلی خودم از کلاس TOptions رو به کلاس TMineSweeper منتقل می کردم، یعنی براش یک متد SaveToFile و LoadFromFile تعریف می کردم که خودش کار خواندن و نوشتن داده های خودش از/در فایل رو انجام بده. اون وقت فرم اصلی برنامه وظیفه فراخوانی متد LoadFromFile و SaveToFile را برای شی ایی که خودش از TMineSweeper میسازه، بر عهده می گرفت.

در واقع تمام کلاس های موجود در این برنامه رو می تونید به عنوان قطعاتی از یک پازل در نظر بگیرید که فرم اصلی به فراخور نیازش اون ها رو میسازه، و در محل های مد نظر خودش قرار میده، تا تصویر مورد نظر خودش را بوجود بیاره. کلاس TMineSweeper، کلاس نگهداری کننده تنظیمات برنامه، پنجره نمایش تنظیمات، پنجره معرفی برنامه، کنترل های موجود در فرم اصلی؛ همگی موجودیت های مستقلی هستند که توسط فرم اصلی کنار هم جمع شدند، و فرم اصلی ارتباط اونها رو با هم برقرار میکنه، تا به هدف مورد نظر خودش برسه.

SAASTN
شنبه 07 خرداد 1390, 11:02 صبح
درک من این هست که برنامه کلا برای نگهداری تنظیمات خودش از یک فایل INI استفاده میکنه، و باید مقادیر این فایل INI در داخل برنامه در دسترس باشه.
آهان، خیلی خوب، شما می گید ما یه مفهومی به اسم برنامه داریم که بالاتر از TMineSweeper قرار داره، حالا مفهوم تنظیمات اینه که یک کلاسی میاد یه سری از صفات قابل تنظیم زیرمجموعه های اون "برنامه" که شامل TMineSweeper و کلاسای دیگه هست رو در یکجا جمع میکنه. من درست متوجه شدم؟
خوب سوال اینجاست که برنامه دقیقا از چه بخشهایی تشکیل شده؟ من تصور می کنم این مفاهیمی که داریم راجع بهشون صحبت می کنیم تو تمام WinApp هایی که بیشتر از 10 ساعت روشون وقت گذاشته شده وجود دارن. و احتمالا هر برنامه نویسی به مرور توی پروژه های مختلفی که انجام میده به یک مدلی میرسه که توی بقیه برنامه ها هم همون مدل رو پیاده می کنه. و در واقع تصور می کنم اون پاراگراف آخر همین پست اخیرتون یه تصویری از مدلی هست که شما ازش استفاده می کنید. یعنی فرم اصلی در بالاترین سطح قرار داره و بقیه کلاس ها به نوعی پراپرتی ها و یا ابزارای مقطعی فرم اصلی هستند. یعنی شاید شما اون مفهوم "برنامه" رو در همین فرم اصلی پیاده سازی می کنید.
خوب حالا دوباره همون سوال قبلی: برنامه دقیقا از چه بخشهایی تشکیل شده؟ خوب من فکر می کنم توی یه تقسیم بندی خیلی کلی، هر برنامه SingleProjectی از دو بخش تشکیل شده. یک بخش یک مدل داده ای و عملیاتی هست که دقیقا کارکرد اون نرم افزاری که قصد توسعه اش رو داریم پیاده می کنه؛ و بخش دوم یک represent از همون مدل داده ای و عملیاتی برای برقراری ارتباط با کاربره که فرم ها و محتویاتشون این کار رو انجام میدن. دیگه هر چیزی که تو برنامه وجود داشته باشه در زیر یکی از این دو بخش قرار میگیره. به تصور من توی برنامه مورد بحث کلاس TMineSweeper معادل بخش اول و کلاس فرم اصلی معادل بخش دوم هست.
حالا می رسیم به تنظیمات که به شکل اعجاب انگیزی دقیقا از همون دو بخش تشکیل شده! یعنی یه سری از موارد مربوط به کارکرد برنامه است مثل Row و Col و یه سری دیگه مربوط به نحوه ارتباط با کاربر مثل BackColor. یعنی در واقع توی این مدل برنامه به این شکل درمیاد:
TSingleProjectApplication = class
Project: TProject;
MainForm: TMainForm;
Options: TOptions;
end;

این تئوریه مدلیه که من باهاش کار می کنم و در عمل هم چیز عمده ای به کلاس برنامه ام اضافه نمیشه، توی همون پست اول هم گفتم:
بازم اگه فکر می کنید اینجوری از نظر مالکیت درست نیست، باید یه کلاس بالاتری باشه که TOption و TMinesweeper هر دو پراپرتی های اون باشند.
حالا تازه میرسیم به موضوع بحث که اساسا مقادیری مثل Row و Col باید در خود TMinesweeper نگهداری شن یا توی TOption؟ فکر می کنم اینجا یک سری فرضیات توی این تصمیم گیری نقش مهمی داشته باشن، مثل:

به خودِ کلاس TMineSweeper ربطی نداره که برنامه تنظیماتش را چطور نگهداری میکنه، یا اصلا آیا یک شی از کلاس TMineSweeper از مقادیر پیش فرضش استفاده میکنه یا نمیکنه.
حالا واقعا می کنه یا نمی کنه؟ ببینید این چیزی که شما مطرح کردید دو نگاه کاملا متفاوت به TOptionه. توی یک نگاه TOption یه کلاس مقطعیه و در واقع تنها مقادیر پیش فرض رو در خودش نگه می داره. یعنی اینکه وقتی ما می خوایم یه بازی جدید رو شروع کنیم پیشفرض تعداد سطر و ستون چند باشه؟ ولی خوب لزوما این مقادیر در طول برنامه ثابت نمی مونن، یعنی ممکنه بازی با یه مشخصاتی شروع بشه ولی در طول بازی اون مشخصات تغییر کنه، و TOption فقط اون مقادیریه که بازی باهاش شروع شده نه مقادیری که بازی داره باهاش انجام میشه. توی مدل دوم اینطور نیست، میگه مقادیر موجود در TOption کاملا Live هستند، یعنی همون مقادیری هستند که بازی داره باهاشون انجام میشه، تنها ما هر وقت که این مقادیر تغییر می کنند یه کپی ازشون در یک فایل نگه می داریم تا وقتی خواستیم بازی جدیدی شروع کنیم بر اساس آخرین تغییرات TOption باشه.
ببینید من فکر می کنم که وضعیت موجود به حالت دوم نزدیکتره. مهمترین دلیل اینکه ما اساسا توی این برنامه امکان تغییر مشخصات بازی رو بصورت مستقل از مقادیر پیشفرض نداریم، دومی یه مسئله خاص تریه که حالا با توجه به موضوع این برنامه خیلی زود باهاش برخورد کردیم. اونم تنظیماتی هست که قبل از Restart کردن برنامه قابل اعمال نیستند. ببینید، فرض کنید ما مثلا داریم بازیه نقطه بازی رو می نویسیم. یه سری از مشخصات مربوط به کارکرد بازی (که در تنظیمات هم می بینیمشون) مثل تعداد سطر و ستونهاست که تا بازی از نو شروع نشه قابل تغییر نیستند، اما یه سری دیگه نیازی به Restart ندارند، مثل درجه سختی بازی، بازی رو با درجه سختی کمتری شروع می کنی وسط بازی میبنی خیلی احمقه سخت ترش می کنی یا بلعکس(البته از دوستان عزیز عاجزانه استدعا دارم مفهوم سختی توی یه بازی مثل نقطه بازی رو با یه بازی ای مثل CallOf مقایسه نکنند:لبخند:). تنظیمات مربوط به UI هم که اکثرا Live هستند مگر اینکه یه شق القمری کرده باشیم که نیاز به Restart داشته باشه.
اینجوری اگه بخوایم به قضیه نگاه کنیم نتیجه این میشه: یک سری مقادیر مثل تعداد سطر و ستون یا سختی بازی یا رنگ پس زمینه اساسا متعلق به TMineSweeper یا فرم اصلی نیستند بلکه این مقادیر دقیقا متعلق به همون مفهوم "برنامه" هستند که TMineSweeper و فرم اصلی کارکردشون رو بر اساس این مقادیر تنظیم می کنند.
البته اگه برنامه MultiProject بشه یکم اوضاع فرق میکنه و یه مفهوم دیگه ای به اسم ProjectOption هم بوجود میاد و ...
نمی دونم، من خیلی فن بیانم خوب نیست و معمولا تو انتقال مطلب مجبورم زیادی حرف بزنم، امیدوارم تونسته باشم منظورم رو برسونم.

Felony
شنبه 07 خرداد 1390, 13:14 عصر
خیلی ممنون علی آقا ؛


و در فرم اصلی برنامه یک فیلد FOptions از نوع TOptions تعریف می کردم و شی مربوطه رو در زمان ساخت فرم می ساختم، و برای فرم هم یک متد با نام ShowOptions ایجاد می کردم و براش همچین کدی می نوشتم:

procedure TfrmMain.ShowOptions;
begin
Assert(Assigned(FOptions),'FOptions is not initialized!');

if TfrmOption.Execute(FOptions) then
FOptions.SaveToFile;
end;
الان اون متد SaveToFile میاد مقدار FCol ، FRow و FBackColor رو در فایل ذخیره میکنه ، ولی در کد شما موقع ذخیره جایی این مقادیر رو به روز نکردید بنابراین همون مقادیر قبلی دوباره ذخیره میشه ، آیا من بد متوجه شدم ؟

برنامه رو بر اساس گفته های شما به تغییر دادم ، لطفا نواقص رو بگید .

vcldeveloper
یک شنبه 08 خرداد 1390, 04:24 صبح
آهان، خیلی خوب، شما می گید ما یه مفهومی به اسم برنامه داریم که بالاتر از TMineSweeper قرار داره، حالا مفهوم تنظیمات اینه که یک کلاسی میاد یه سری از صفات قابل تنظیم زیرمجموعه های اون "برنامه" که شامل TMineSweeper و کلاسای دیگه هست رو در یکجا جمع میکنه. من درست متوجه شدم؟بله.


و در واقع تصور می کنم اون پاراگراف آخر همین پست اخیرتون یه تصویری از مدلی هست که شما ازش استفاده می کنید. یعنی فرم اصلی در بالاترین سطح قرار داره و بقیه کلاس ها به نوعی پراپرتی ها و یا ابزارای مقطعی فرم اصلی هستند. یعنی شاید شما اون مفهوم "برنامه" رو در همین فرم اصلی پیاده سازی می کنید.مفهوک controller لزوما به فرم اصلی بر نمیگرده. اینکه باید در اکثر موارد کلاسی باشه که نقش کنترل کننده و در واقع به کار گیرنده اجزاء مختلف برنامه رو داشته باشه، درسته. اما اینکه فرم اصلی برنامه لزوما همچین کلاسی باشه، نه؛ این نظر من نیست. من فرم اصلی رو در این پروژه خاص، با توجه به سادگی پروژه و کدهای موجود در پروژه به عنوان همچین کلاسی معرفی کردم. دقت داشته باشید که اگر قرار می بود من همچین پروژه ایی بنویسم، شاید از ابتدا پروژه رو طور دیگه ایی طراحی می کردم؛ اما اینجا ما داریم درباره یک پروژه و کدهای موجود اون صحبت می کنیم. نمیشه که بگیم همه کدهای نوشته شده برند کنار، ما همه رو از نو بنویسیم، بلکه اونجاهایی که مشکل داره رو میشه اصلاح یا بهینه کرد.



خوب من فکر می کنم توی یه تقسیم بندی خیلی کلی، هر برنامه SingleProjectی از دو بخش تشکیل شده. یک بخش یک مدل داده ای و عملیاتی هست که دقیقا کارکرد اون نرم افزاری که قصد توسعه اش رو داریم پیاده می کنه؛ و بخش دوم یک represent از همون مدل داده ای و عملیاتی برای برقراری ارتباط با کاربره که فرم ها و محتویاتشون این کار رو انجام میدن. دیگه هر چیزی که تو برنامه وجود داشته باشه در زیر یکی از این دو بخش قرار میگیره. به تصور من توی برنامه مورد بحث کلاس TMineSweeper معادل بخش اول و کلاس فرم اصلی معادل بخش دوم هست. زیادی کلی به نظر میرسه. البته اینکه میگید Single Project رو من متوجه نمیشم. از نظر من پروژه فقط یکی هست، حالا ممکنه شما در طراحی خودتون برای اون پروژه ماجول های متعددی در نظر بگیرید که در قالب EXE ها، DLL ها، BPL ها، و غیره با هم تعامل داشته باشند؛ اینها همگی میشن اجزاء اون پروژه و البته خودشون هم میتونند اجزاء کوچکتری داشته باشند.

درباره مدلی که توضیح دادید، باید یک حلقه واسطی باشه که back-end پروژه شما؛ یعنی اون ساختارهای داده و کدهای عملیاتی که نوشتید رو به front-end پروژه شما؛ یعنی اون رابط کاربر که ممکنه یک رابط گرافیکی باشه، یا یک رابط برنامه نویسی و غیره، ارتباط بده؛ حلقه ایی که ورودی ها رو از رابط کاربر شما بگیره و به back-end برسونه، یا داده های مورد نیاز اون رو فراهم کنه. این حلقه واسط در واقع همون کنترلر شما ست.



حالا واقعا می کنه یا نمی کنه؟ربطی پیدا نمیکنه. اگر قرار باشه TOptions تنظیمات کل برنامه رو در خودش نگهداره، و نه فقط تنظیمات TMineSweeper رو، به TMineSweeper ربطی پیدا نمیکنه که برنامه این تنظیمات رو چطور و با چه فرمتی یا در چه فایلی نگهداری میکنه. اگر قرار باشه که فقط و فقط تنظیمات TMineSweeper نگهداری بشه، نه سایر چیزها؛ یا قرار باشه سایر تنظیمات جدای از تنظیمات TMineSweeper نگهداری بشند، اون وقت TMineSweeper میتونه خودش مسئولیت ذخیره سازی تنظیمات خودش را برعهده بگیره. ببینید، من اگر بخوام خونه بسازم، وقتی یک بنا رو میارم سر ساختمون، به بنا ربطی نداره که من گچ و سیمان و آجر رو از کجا میارم میدم بهش! من استخدامش کردم که کار مورد نظر من رو انجام بده، و پولش رو بگیره. از طرف دیگه، به من هم ربطی نداره که بنا ابزار مورد نیاز خودش رو از کجا میاره، من نوع کارم رو بهش میگم، اون هم ابزار مناسب خودش رو میاره و کارش رو انجام میده. حالا TMineSweeper اون بنا هست، TOptions هم اگر تنظیمات پروژه در نظر بگیریم، میشه اون گچ و سیمان و غیره، ولی اگر فقط خصوصیات TMineSweeper در نظر بگیریم، میشه اون ابزار کار بنا.



الان اون متد SaveToFile میاد مقدار FCol ، FRow و FBackColor رو در فایل ذخیره میکنه ، ولی در کد شما موقع ذخیره جایی این مقادیر رو به روز نکردید بنابراین همون مقادیر قبلی دوباره ذخیره میشه ، آیا من بد متوجه شدم ؟شما درست متوجه شدید. من برای اینکه منظورم رو برسونم، سریع اون کد رو نوشتم و اصلا اجراش نکردم. در متد TfrmOption.Execute باید مقادیر دریافت شده در شی Options نوشته میشدند، که من یادم رفت بنویسم. ممنون، کدش رو اصلاح میکنم.

Felony
یک شنبه 08 خرداد 1390, 20:38 عصر
شما درست متوجه شدید. من برای اینکه منظورم رو برسونم، سریع اون کد رو نوشتم و اصلا اجراش نکردم. در متد TfrmOption.Execute باید مقادیر دریافت شده در شی Options نوشته میشدند، که من یادم رفت بنویسم. ممنون، کدش رو اصلاح میکنم.
من تغییراتی که لازم بود رو اعمال کردم ، الان یه جا از بازی به مشکل بر خوردم ، اگر دیده باشید با کلیک بر روی یکی از خانه ها که خالی هست یک محوطه برای کاربر خالی میشه ( عکس ضمیمه ) همونطور که میبینید ترتیب خانه های بمب به گونه ای هست که بمب ها اون محوطه رو میسازن ، کسی راه حلی ( کد نمیخوام ) به ذهنش میرسه که تو این بازی همچین ترتیبی برای Set شدن بمب ها در نظر بگیریم ؟

javad p
یک شنبه 08 خرداد 1390, 23:44 عصر
7053770538
با سلام
من دوتا تصویر رو وارد کردم
توی تصویر اول نقاط کلیک من توی صفحه مشخص شده
همونطور که میبینید عمل باز کردن محیط برای خانه هایی اتفاق میافته که هیچ کدام از خانه های اطرافشون ( دقیقا خانه های چسبیده بهشون) هیچ کدوم بمب نیستند
خب طراحی و کد نویسی این که اصلا نیاز به توضیح نداره
تو تصویر بعد اینطور به نظر میاد که برای خانه هایی که قرار هست محیط براشون گسترش پیدا کنه، ابتدا در یکی از جهات مختصات دوبعدی شروع به حرکت میکنه
برای مثال از نقطه کلیک شده به سمت بالا و پایین شروع میکنه تا به نقاط بمب برخورد بکنه
بعد برای هر کدوم از خونه های ایجاد شده در جهت عمود مختصاتی شروع به حرکت میکنه تا به نقاط بمب برخورد بکنه ، یا به نقطه ای برسه که خانه ی بعدی در مسیر حرکت اون بمب نیست، ولی یکی از خانه های اطرافش در جهت عمود (قبلی) خانه ای است که یکی از خانه های اطرافش بمب هست
من برای چند مورد بررسی کردم، این راه حل صادق بود
ولی فکر کنم کامل نباشه
موفق باشید

SAASTN
دوشنبه 09 خرداد 1390, 00:52 صبح
اما اینجا ما داریم درباره یک پروژه و کدهای موجود اون صحبت می کنیم. نمیشه که بگیم همه کدهای نوشته شده برند کنار، ما همه رو از نو بنویسیم، بلکه اونجاهایی که مشکل داره رو میشه اصلاح یا بهینه کرد.
خوب من فکر می کنم داریم برنامه Minesweeper رو از لحاظ شیئ گرایی بررسی می کنیم. حالا این درسته که لزوما نباید نظرات همه در کدی که موازی با این تاپیک پیش میره اعمال بشه، اما خوب من دارم نظر خودم رو به لحاظ مشخصات اشیاء و ارتباط اونها بیان می کنم و منتظرم بقیه هم نظراتشون رو بیان کنم. هدفم هم به نقد گذاشتن دانسته های خودم در یک فضای عمومی و البته تخصصی و یاد گرفتن مطالب جدیده. حالا درسته اگه این وسط به یه نکته جدیدی برسم ممکنه نتونم پاشم بدو بدو برم تو پروژه های بازم اعمال کنم، ولی مسلما اگه پروژه جدیدی رو بخوام شروع کنم حتما اون نکته رو در نظر خواهم داشت.

زیادی کلی به نظر میرسه.
به نظرتون چه بخشهای عمده دیگه ای وجود دارن که توی اکثر پروژه ها دیده میشن و در این غالب قرار نمی گیرن. البته من تجربه زیادی در رابطه با برنامه های رایج مرتبط با پایگاه داده ندارم(راستش همین چند روز پیش بعد از 4-5 سال دوباره SQL Server رو نصب کردم) و توی برنامه های محاسباتی به این مدلی که مطرح کردم رسیدم، مثلما با یک نگاه تک بعدی نمی تونم یه مدل جامع داشته باشم.

البته اینکه میگید Single Project رو من متوجه نمیشم.
منظورم برنامه ای هستش که در آن واحد امکان انجام عملیات روی یک پروژه رو میده، مثل همین بازی یا یه ضد ویروس، یعنی تمام تنظیمات برنامه مربوط به پروژه جاری هست. اما تو برنامه های چند پروژه ای مثل همین دلفی خودمون در آن واحد چند پروژه می تونن فعال باشن و ممکنه بخشی از تنظیمات بین اونها مشترک و بخش دیگه ای متفاوت باشه.

درباره مدلی که توضیح دادید، باید یک حلقه واسطی باشه که back-end پروژه شما؛ یعنی اون ساختارهای داده و کدهای عملیاتی که نوشتید رو به front-end پروژه شما؛ یعنی اون رابط کاربر که ممکنه یک رابط گرافیکی باشه، یا یک رابط برنامه نویسی و غیره، ارتباط بده؛ حلقه ایی که ورودی ها رو از رابط کاربر شما بگیره و به back-end برسونه، یا داده های مورد نیاز اون رو فراهم کنه. این حلقه واسط در واقع همون کنترلر شما ست.
درست متوجه این بخش نمیشم، اون حلقه قراره برای ما چکار کنه؟ خوب من اون رابط کاربر رو یه پوسته روی هسته می بینم که قراره داده ها و مشخصات هسته رو به کاربر نمایش بده و در خواست های کاربر رو به هسته مخابره کنه، ارتباطی هم که این وسط لازمه از طریق دسترسی هایی که همون کلاس مثلا TSingleProjectApplication یا بقول شما Controller در اختیار میذاره فراهم میشه.

حالا TMineSweeper اون بنا هست، TOptions هم اگر تنظیمات پروژه در نظر بگیریم، میشه اون گچ و سیمان و غیره، ولی اگر فقط خصوصیات TMineSweeper در نظر بگیریم، میشه اون ابزار کار بنا.
من اگه بخوام منظورم رو در قالب مثال شما بیان کنم باید اینجوری بگم: اولا کار فرما در این پروژه کاربره، TMineSweeper (یا خود بنده به عنوان ایجاد کنندش) همون بنا و TOptions مشخصات ساختمانیه که باید ساخته بشه. ببینید بنا بناست، نه آهنگره نه نونواست و نه چیز دیگه، من برای ساختن یه ساختمون میرم پیشش و بهش اعتماد دارم که از ابزار و مصالح درستی استفاده میکنه، اما باید براش تعرف کنم که چجور ساختمانی می خوام. من به عنوان کاربر برنامه Minesweeper رو اجرا می کنم چون می خوام بازی کنم، اما می خوام بازیم چجوری باشه؟ خونه ها 20*20 باشه، تعداد بمب ها فلان قدر باشه و محیط برنامم هم این رنگی باشه. به نظر من تنظیمات مسالح یا ابزار بازی نیست بلکه ابعاد اونه.

الان یه جا از بازی به مشکل بر خوردم ، اگر دیده باشید با کلیک بر روی یکی از خانه ها که خالی هست یک محوطه برای کاربر خالی میشه ( عکس ضمیمه ) همونطور که میبینید ترتیب خانه های بمب به گونه ای هست که بمب ها اون محوطه رو میسازن ، کسی راه حلی ( کد نمیخوام ) به ذهنش میرسه که تو این بازی همچین ترتیبی برای Set شدن بمب ها در نظر بگیریم ؟
من تصور می کنم کار به این شکله که اگر خونه کلیک شده مقداری بیشتر از صفر داشته باشه که تنها مقدار خودش رو نمایش میده، اما در صورتی که صفر باشه، بصورت بازگشتی تمام خونه های صفر مجاور خودش رو هم کلیک میکنه، بعد که تعدادی از خونه های مجاور با مقدار صفر خالی شدن تمام خونه های غیر صفر و غیر بمب مجاور با اونها (یعنی همه اونایی که صفر بودن) مقادیرشون رو نمایش میدن. البته هنوز به نتیجه دقیقی نرسیدم که اون تابع بازگشتی تنها روی خونه های مجاور صلیبی اجرا میشه یا روی خونه های مجاور ضربدری هم باید اجرا بشه.

vcldeveloper
دوشنبه 09 خرداد 1390, 02:28 صبح
درست متوجه این بخش نمیشم، اون حلقه قراره برای ما چکار کنه؟ خوب من اون رابط کاربر رو یه پوسته روی هسته می بینم که قراره داده ها و مشخصات هسته رو به کاربر نمایش بده و در خواست های کاربر رو به هسته مخابره کنه، ارتباطی هم که این وسط لازمه از طریق دسترسی هایی که همون کلاس مثلا TSingleProjectApplication یا بقول شما Controller در اختیار میذاره فراهم میشه.
اون حلقه قراره یک لایه Abstraction به برنامه شما اضافه کنه، تا ساختار های back-end و front-end برنامه تون بیش از حد به هم وابسته نشند، و بشه کنترل و مدیریت بهتری روی نقل و انتقال داده ها بین اونها داشت، بخصوص اعتبار سنجی داده ها رد و بدل شده. اینطوری تغییرات در یک لایه تاثیرات جانبی کمتری روی سایر لایه ها میذاره. چند مثال دم دستی از به کار گیری Abstraction در نرم افزارهای مختلف شاید موضوع رو بیشتر روشن کنه:
1- در معماری بانک اطلاعاتی دلفی، تعداد کلاس نگهداری کننده داده وجود دارند که از TDataset مشتق میشند. تعدادی هم کنترل بصری وجود دارند که می تونند به طور خودکار به داده های بانک دسترسی پیدا کنند و محتوای یک فیلد یا مجموعه ایی از رکوردها رو نمایش بدند، مثل TDBGrid, TDBEdit, TDBMemo و غیره که کلا بهشون Data-aware controls گفته میشه. این کنترل های بصری مستقیما به کلاس های مشتق شده از TDataset متصل نمیشند، بلکه به یک کامپوننت واسط با نام TDataSource وصل میشند، و این DataSource هست که پیوند دهنده Dataset ها با کنترل های Data-aware هست.

2- در ویندوز، سه لایه Abstraction برای یک برنامه که روی این سیستم عامل اجرا میشه، وجود دارند؛ اولا برنامه برای انجام کاری وابسته به یک سری توابع موسوم به توابع API ویندوز هست. این توابع درخواست برنامه مربوطه رو به یک سطح پایین، یعنی کرنل ویندوز می برند. توابع کرنل ویندوز این درخواست رو به یک لایه با نام HAL میبرند. HAL این درخواست رو به درخواستی قابل فهم برای ماشین مربوطه میکنه، تا ماشین این درخواست رو انجام بده. در نگاه اول این لایه HAL یک مرحله اضافه هست، اما چرا در ویندوز این لایه اضافه ایجاد شده؟ توضیحش در همون عنوان این لایه هست؛ HAL = Hardware Abstraction Layer؛ به خاطر اینکه طراحان ویندوز نمی خواستند بخش های مختلف ویندوز با پیاده سازی های سخت افزاری مختلف درگیر بشند؛ این لایه مابقی بخش های ویندوز رو از تغییرات در پیاده سازی سخت افزاری در ماشین های مختلف مصون نگه میداره. بخش های مختلف سیستم عامل لایه HAL رو به عنوان سخت افزار هدف خودشون می شناسند، تا زمانی که این لایه تغییری نکرده، برای اون بخش ها مهم نیست که سخت افزار پشت این لایه چی هست و چطور کار میکنه.

3- مشابه مسئله فوق رو در طراحی سکوهای نرم افزاری مثل دات نت و جاوا هم می بینید؛ در اونجا یک ماشین مجازی یک لایه Abstraction بین برنامه در حال اجرا و سیستم عامل مقصد ایجاد میکنه.


اگر قرار باشه اجزاء رابط گرافیکی کاربر، یا با یک عنوان کلی تر، front-end برنامه شما مستقیما با لایه های زیرین برنامه شما، یا با یک عنوان کلی تر، back-end برنامه شما، تعامل داشته باشند؛ هر گونه تغییرات در back-end موجب تغییرات گسترده در front-end میشه. همچین بسیاری از فرآیند ها بین این دو سر برنامه شما پخش و پلا میشند. نوشتن تست های مناسب برای همچین سیستمی سخت میشه، و به طور کلی، نگهداری از این سیستم تبدیل به یک کار طاقت فرسا میشه.
در حالی که وجود یک لایه میانی ما بین این دو سر برنامه شما باعث میشه که ساختارهای موجود در این دو سر به هم وابستگی نداشته باشند، یا حداقل وابستگی شان کم باشه. front-end شما فقط این لایه میانی رو میشناسه، براش فرق نمیکنه که پشت این لایه چه ساختارهایی پنهان هستند، یا اصلا این لایه درخواست های front-end رو به کجا میفرسته و از کجا جواب میگیره. اینطوری تغییرات در یک لایه روی لایه دیگه اثرات کمتری میذاره. اگر هم بخواید بر نقل و انتقال داده ها بین این لایه ها نظارت داشته باشید، میدونید که باید کجا دست بذارید (اونجایی که ترافیک لایه های مختلف ازش عبور میکنه). اگر هم بخواید برای این لایه ها و ساختارهای موجودشان تست بنویسید، می تونید با اعمال کمترین تغییرات در این لایه میانی، مسیر نقل و انتقال داده اون لایه جزء از برنامه رو طوری تغییر بدید که بشه به طور مستقل تستش کرد؛ مثلا اگر یک جزء از برنامه باید برای انجام کارش داده ایی را از شبکه دریافت کنه و سپس اون رو پردازش کنه، برای تست صحت پردازش این جزء، لازم نیست که من داده های حقیقی که احتمالا مرتبا هم در حال تغییر هستند را از شبکه دریافت کنم، بلکه کافیه به این لایه واسطی که وجود داره، بگم که به جای ارسال درخواست های اون جزء خاص به شبکه و دریافت جواب از شبکه، داده ها رو از یک فایل ثابت که من از قبل آماده کردم بخونه، و به این جزء بده. این طوری با توجه به اینکه من از قبل میدونم چه چیزی در این فایل هست، می تونم خروجی صحیح پردازش مربوطه رو هم محاسبه کنم، و نتیجه انجام پردازش توسط اون جزء رو با نتیجه مد نظر خودم مقایسه کنم. حالا ممکنه در یک برنامه هزاران جزء مثل این وجود داشته باشند. برای تست همه اونها لازم نیست که من تک تک اونها رو تغییر بدم، بلکه کافیه تغییرات برای حالت انجام تست و حالت عملیاتی در اون لایه میانی مربوطه اعمال بشند.


من اگه بخوام منظورم رو در قالب مثال شما بیان کنم باید اینجوری بگم: اولا کار فرما در این پروژه کاربره، TMineSweeper (یا خود بنده به عنوان ایجاد کنندش) همون بنا و TOptions مشخصات ساختمانیه که باید ساخته بشه. ببینید بنا بناست، نه آهنگره نه نونواست و نه چیز دیگه، من برای ساختن یه ساختمون میرم پیشش و بهش اعتماد دارم که از ابزار و مصالح درستی استفاده میکنه، اما باید براش تعرف کنم که چجور ساختمانی می خوام. من به عنوان کاربر برنامه Minesweeper رو اجرا می کنم چون می خوام بازی کنم، اما می خوام بازیم چجوری باشه؟ خونه ها 20*20 باشه، تعداد بمب ها فلان قدر باشه و محیط برنامم هم این رنگی باشه. به نظر من تنظیمات مسالح یا ابزار بازی نیست بلکه ابعاد اونه.
متوجه نشدم که از این مطلب میخواستید به چی برسید؟ :متفکر:

شما میگید تنظیمات ابزار بازی نیست، ابعاد بازی هست؛ خب در مثال مناقشه نیست؛ ولی این چه تغییری در بحث ایجاد میکنه؟ شما به طور خلاصه این رو پرسیدید که چرا داده های مربوط به طول و عرض و غیره که در کلاس TOption تعریف شده بودند، در مثال من از TOptions، مستقیما توسط کلاس TMineSweeper از فایل خوانده نمیشند؟ در جواب اون پاسخ من دو بخش داشت، اول اینکه، من بوجود آورنده اون کلاس نبودم، نمونه کد من به این خاطر نوشته شد که من به کد آقای تاجیک درباره خواندن و نوشتن های مکرر در فایل توسط TOption ایراد گرفتم، و ایشون گفتند که غیر از این نمیشه کاری کرد. اون کد نوشته شد که بگه میشه اون کار رو به شکل خیلی بهینه تری بدون نیاز به ارجاعات متعدد به فایل انجام داد. دوم اینکه من اون کلاس TOption رو به عنوان ساختاری برای نگهداری کل تنظیمات برنامه در نظر گرفتم که قراره به تدریج سایر تنظیمات برنامه رو در خودش نگهداری کنه، نه اینکه صرفا تنظیمات یک بخش خاص از برنامه (یعنی TMineSweeper) رو نگهداری کنه. در همون توضیحات هم گفتم که به TMineSweeper ربطی نداره که برنامه تنظیمات خودش رو کجا و چطوری نگهداری میکنه! درباره این هم مجددا توضیح دادم.
حالا این مثالی که زدید، چه چیزی رو در بحث تغییر میده؟! مثلا بگیم تنظیمات ابعاد برنامه هست، خب این خللی در بحث ایجاد نمیکنه؛ همچنان به کلاس TMineSweeper ربطی نداره که برنامه ابعاد خودش یا هر اسم دیگه ایی که میخواید براش بذارید رو از کجا میاره و چطور میاره. آیا برنامه اینها رو از کاربر میگیره؟ آیا اینها رو از یک سرور راه دور میگیره؟ آیا برنامه این مقادیر رو به صورت ثابت در خودش تعریف کرده؟ اگر در فایل ذخیره میشند، آیا به صورت XML ذخیره شدند؟ به صورت INI ذخیره شدند؟ آیا باینری نیستند؟! اینها هیچ ربطی به TMineSweeper نداره. TMineSweeper میخواد یک رابط کاربر برای یک بازی بسازه. برای ساخت این رابط کاربر نیاز داره که بدونه ابعاد بازی باید چقدر باشه، خب این رو برنامه بهش میده. اما اینکه برنامه اینا رو از کجا و چطوری میاره و به TMineSweeper میده، ربطی به این کلاس نداره. تا زمانی که برنامه این داده ها رو درست و به موقع به TMineSweeper میرسونه، TMineSweeper کارش رو انجام میده. اگر هم این داده ها بهش نرسه، ولی برنامه ازش انتظار کار داشته باشه، به برنامه اطلاع میده که تا زمانی که داده مورد نیازش فراهم نشه، قادر به انجام کار محوله نیست.

حالا با این توضیحات، من نمیدونم مثال شما کجای این بحث قرار میگیره؟ یا بهتره بگیم من جایی براش در بحث پیدا نمی کنم.

SAASTN
دوشنبه 09 خرداد 1390, 16:30 عصر
اینطوری تغییرات در یک لایه تاثیرات جانبی کمتری روی سایر لایه ها میذاره. چند مثال دم دستی از به کار گیری Abstraction در نرم افزارهای مختلف شاید موضوع رو بیشتر روشن کنه:
راستش هر کدوم از مثالا رو تا حدودی درک می کنم اما نمی تونم بهشون جامعیت بدم و بیارمشون توی همه برنامه ها، باید بیشتر روش فکر کنم و البته یکم بیشتر بخونم. اما مثلا فرض کنید برای کوچکتر کردن موضوع، بخوایم همین مطلب رو توی Minesweeper پیاده کنیم. مثلا من در کلاس TMinesweeper یه TList دارم که حاوی اشیاء مربوط به خانه های بازی هست، از طرفی هر کدوم از این خانه ها یه معادل گرافیکی در UI دارن که مشخصات اون خونه رو نمایش میده و درخواست های کاربر مربوط به هر خونه رو دریافت می کنه. خوب حالا اگه بخوایم برای این ارتباطها یه لایه Abstraction ایجاد کنیم به چه صورت میشه؟

شما به طور خلاصه این رو پرسیدید که چرا داده های مربوط به طول و عرض و غیره که در کلاس TOption تعریف شده بودند، در مثال من از TOptions، مستقیما توسط کلاس TMineSweeper از فایل خوانده نمیشند؟
نه، من داشتم راجع به نقش TOptions میپرسیدم، که تا اون لحظه تنها کاری که میکرد این بود که یه سری از مشخصات مربوط به TMinesweeper رو تو فایل ذخیره کنه. من نگفتم به نظر من TMinesweeper باید خودش این کار رو بکنه، بلکه گفتم همچین چیزی تعریف تنظیمات یه برنامه نیست، یه همچین کلاسی اولا اسمش TOption نمی تونه باشه و در ثانی اینقدر کوچیکه که می تونه با خود TMinesweeper تلفیق بشه. اما این اصل مطلب من نبود، اصل مطلب جایگزینی بود که برای این شرایط توضیح دادم. من سعی دارم توضیح بدم که پراپرتی هایی مثل Col و Row اساسا متعلق به TMinesweeper نیستند و تنها باید در TOption پیاده بشن. هر کسی هم به این مقادیر نیاز داشته باشه باید از طریق نمونه ایجاد شده از این کلاس بهشون دست پیدا کنه. حالا چرا؟ به همون دلائلی که تو پستای قبلی گفتم و نمونش هم همون مثال. من اصلا به این که این مقادیر از کجا و چجوری میان کاری ندارم، تنها دارم در مورد طراحی پراپرتی های کلاسا صحبت می کنم. فکر می کنم شما به بخشهایی که من در مورد ذخیره و بازیابی صحبت کردم یه مقدار وزن دادید درصورتی که من تاکید زیادی روی اون نداشتم.

vcldeveloper
سه شنبه 10 خرداد 1390, 07:20 صبح
راستش هر کدوم از مثالا رو تا حدودی درک می کنم اما نمی تونم بهشون جامعیت بدم و بیارمشون توی همه برنامه ها
لازم نیست توی همه برنامه ها باشه؛ همه برنامه ها که شرایطشون یکسان نیست. نمیشه یک ساختار جامعه در آورد و گفت همه برنامه ها باید این مدلی باشند. هر برنامه ایی شرایط خاص خودش رو داره؛ گاهی اوقات ایجاد لایه های اضافی بیشتر موجب هدر رفتن وقت و هزینه میشه، تا اینکه مزیتی بوجود بیاره.



اما مثلا فرض کنید برای کوچکتر کردن موضوع، بخوایم همین مطلب رو توی Minesweeper پیاده کنیم. مثلا من در کلاس TMinesweeper یه TList دارم که حاوی اشیاء مربوط به خانه های بازی هست، از طرفی هر کدوم از این خانه ها یه معادل گرافیکی در UI دارن که مشخصات اون خونه رو نمایش میده و درخواست های کاربر مربوط به هر خونه رو دریافت می کنه. خوب حالا اگه بخوایم برای این ارتباطها یه لایه Abstraction ایجاد کنیم به چه صورت میشه؟
این دیگه ارزش لایه اضافه کردن نداره، با اندازه کافی خودش کوچیک هست، لایه اضافی بیشتر موجب افزایش پیچیدگی کد میشه تا اینکه بخواد مزیتی بهش اضافه کنه. از طرف دیگه، اینجا این اشیاء بسیار ساده هستند و ارتباط شون هم نظیر به نظیر هست، یک دکمه در ازاء یک آیتم از لیست. البته اینقدر ساده هستند که اصلا نیاز به دو لیست نیست، چون یک لیست از کنترل ها به طور آماده برامون توسط کلاس TControl فراهم شده، و هر کدوم از کنترل ها و خصوصیاتی مثل Caption و Tag دارند که می تونند کل داده مورد نیاز ما رو نگهداری کنند، بدون اینکه نیاز به یک لیست اضافی باشه. حتی اگر حجم داده هاشون بیشتر از این هم بود، میشد داده اضافی رو از طریق خصوصیت Tag به عنوان آدرس یک خانه حافظه، به هر کدوم از اون کنترل ها الصاق کرد. البته در پست های قبلی هم گفتم، TMinesweeper باید تبدیل به یک کامپوننت بشه، که کل فرآیند رسم دکمه ها، پاسخگویی به دکمه های کلیک شده، و پخش کردن مین ها بین دکمه را برعهده بگیره.


من سعی دارم توضیح بدم که پراپرتی هایی مثل Col و Row اساسا متعلق به TMinesweeper نیستند و تنها باید در TOption پیاده بشن.
TMineSweeper برای رسم دکمه ها، نیاز به دونستن تعداد سطر و ستون ها داره یا نه؟ میخواد برای نگهداری این داده ها از چی استفاده کنه؟ برای تغییر این داده ها میخواد چه مکانیزمی ارائه کنه؟ TMineSweeper به خصوصیات Col و Row نیاز داره تا بتونه با اشیاء دیگه درباره ابعاد بازی تعامل داشته باشه. ممکنه بگید، خب هر وقت که میخواد یک بازی جدید رسم کنه، اینها رو به عنوان پارامتر بگیره؛ حتی در اون صورت هم نیاز داره برای انجام رسم اینها رو جایی نگهداره، و البته باید مکانیزمی رو برای سایر اشیاء فراهم کنه که هر وقت ازش پرسیدند بازی فعلی چند سطر و ستون داره؟ بتونه بهشون جواب بده. دونستن تعداد سطر و ستون ها حداقل نیازهای TMineSweeper هست، برای انجام صحیح کارش، و اساسا هم فقط برای TMineSweeper این داده مفهوم داره، و برای فرم اصلی، TOption، یا حتی فرم تنظیمات این داده ها مفهومی ندارند. اینها فقط میدونند که TMinesweeper برای کارش به دو عدد نیاز داره. پس این دو داده ارتباط مستقیمی با TMineSweeper دارند.

SAASTN
سه شنبه 10 خرداد 1390, 23:35 عصر
لازم نیست توی همه برنامه ها باشه؛ همه برنامه ها که شرایطشون یکسان نیست...

این دیگه ارزش لایه اضافه کردن نداره، با اندازه کافی خودش کوچیک هست، لایه اضافی بیشتر موجب افزایش پیچیدگی کد میشه تا اینکه بخواد مزیتی بهش اضافه کنه...
خوب پس اون ساختاری که گفتم خیلی هم کلی نیست و تا یه جاهایی جواب میده. اما با این حال می تونید یه مثال ساده از ایجاد یه لایه Abstraction بزنید. در واقع سوالم اینه که آیا یه شرایط تعریف شده برای الزام به بوجود اومدن این مفهوم وجود داره؟

TMineSweeper برای رسم دکمه ها، نیاز به دونستن تعداد سطر و ستون ها داره یا نه؟
ای بابا! بعد این همه پست دوباره رسیدیم به خونه اول. خوب معلومه که داره، من که همون اول به آقای تاجیک گفتم برای یه پیمایش ساده روی خونه ها به اینا احتیاج پیدا میشه. برای محل نگهداریشون هم که خوب تابلوئه، اولین جایی که به ذهن میرسه TMineSweeper هست. ولی خوب من سعی کردم یه مدل دیگه رو ارائه کنم و توی پستای قبلی هم راجع به این مدل و نقش بخشهای مختلفش توضیح دادم.

میخواد برای نگهداری این داده ها از چی استفاده کنه؟
TOptions!

برای تغییر این داده ها میخواد چه مکانیزمی ارائه کنه؟
Controller به راحتی میتونه این امکان رو فراهم کنه:
TSingleProjectApplication = class
Project: TProject;
MainForm: TMainForm;
Options: TOptions;
end;


برای انجام صحیح کارش، و اساسا هم فقط برای TMineSweeper این داده مفهوم داره، و برای فرم اصلی، TOption، یا حتی فرم تنظیمات این داده ها مفهومی ندارند. اینها فقط میدونند که TMinesweeper برای کارش به دو عدد نیاز داره. پس این دو داده ارتباط مستقیمی با TMineSweeper دارند.
خوب این یک نگاه هست و البته نگاه غیر منطقی ای هم نیست، اما من بازهم می پرسم که توی این شرایط کلاس TOption دقیقا چکارست؟ یه کپی از روی مشخصات TMinesweeper نگه میداره که چی بشه؟ مگه TMinesweeper خودش نمی تونه مشخصاتش رو در اختیار فرم تنظیمات بذاره؟ یا مگه ما امکان تغییر مشخصات TMinesweeper بطور مستقل از تنظیمات پیشفرض رو داریم؟ یا می خوایم داشته باشیم؟
من اگه بخوام به این سوالا جواب بدم میگم باید تعریفمون رو از نقش بخشهای مختلف تغییر بدیم: TMinesweeper پیش برنده بازی هست، فرم اصلی مدیریت کننده ارتباط کاربر با هسته برنامه و TOption نگه دارنده مشخصات بازی، چه از نظر کارکرد و چه از نظر مشخصات UI، و اون مفهوم برنامه یا Controller هم نقش برقراری ارتباط بین این اجزا رو داره. حالا اگه اون مثال پست 19 رو بخونید شاید یه جایی برا خودش تو بحث باز کنه!:لبخند:

vcldeveloper
چهارشنبه 11 خرداد 1390, 20:30 عصر
خوب پس اون ساختاری که گفتم خیلی هم کلی نیست و تا یه جاهایی جواب میده. اما با این حال می تونید یه مثال ساده از ایجاد یه لایه Abstraction بزنید. هیچ ساختار کلی وجود نداره که نقش شاه کلید رو داشته باشه، و شما به هر دری بزنیدش، کار کنه؛ حتی شی گرایی که اینقدر توی بوق می کنندش، در مواردی میتونه خودش عامل افزایش پیچیدگی پروژه بشه، بدون اینکه مزیتی بوجود بیاره.


در واقع سوالم اینه که آیا یه شرایط تعریف شده برای الزام به بوجود اومدن این مفهوم وجود داره؟اونجایی که نیاز دارید اشیاء یا لایه های مختلف برنامه رو تا حد امکان از تاثیر پذیری در برابر تغییرات سایر اشیاء و لایه ها مصون نگه دارید؛ یا زمانی که ارتباط برخی از اشیاء با هم باید در زمان اجرا تعیین بشه؛ مثلا در شرایطی که نیاز به load balancing در سمت سرور باشه، و کلاینت ها باید متناسب با اینکه چه سرور در حال حاضر بار کمتری داره، به اون سرور متصل بشند، در همچین حالتی یک لایه میانی میتونه بین کلاینت ها و سرور قرار بگیره، و مشخص کنه هر کلاینت به چه سروری باید وصل بشه تا توازن بار روی سرورها حفظ بشه، و مواردی از این دست.





میخواد برای نگهداری این داده ها از چی استفاده کنه؟ TOptions!
برای تغییر این داده ها میخواد چه مکانیزمی ارائه کنه؟

Controller به راحتی میتونه این امکان رو فراهم کنه:
یک شی باید سعی کنه تا حد امکان، برای کارهای خودش مستقل عمل کنه. اینکه یک شی ساده ترین و پایه ایی ترین کارهای خودش رو به اشیاء دیگه واگذار کنه، مثل این هست که از شما بپرسیم، آیا یک انسان به پا نیاز داره؟ شما بگید، نه، با ویلچر میتونه راه بره! بپرسیم آیا به دهان نیاز داره؟ جواب بدید، نه، با سروم میشه بهش غذا رسوند، برای صحبت کردن هم میتونه از کاغذ و قلم استفاده کنه، و الی آخر!



خوب این یک نگاه هست و البته نگاه غیر منطقی ای هم نیست، اما من بازهم می پرسم که توی این شرایط کلاس TOption دقیقا چکارست؟ یه کپی از روی مشخصات TMinesweeper نگه میداره که چی بشه؟در پست 13 همین تاپیک مفصلا توضیح دادم:
http://barnamenevis.org/showthread.php?288863-%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%A8%D8%A7%D8%B2%DB%8C-Minesweeper-%D8%A7%D8%B2-%D9%84%D8%AD%D8%A7%D8%B8-%D8%B4%D8%A6-%DA%AF%D8%B1%D8%A7%D8%A6%DB%8C&p=1271698&viewfull=1#post1271698


...اما من بازهم می پرسم که توی این شرایط کلاس TOption دقیقا چکارست؟
لود کرد و ذخیره کردن تنظیمات برنامه در یک محل ذخیره سازی دائمی، به طوری که برنامه بتونه در هر زمانی به آنها دسترسی داشته باشه.

SAASTN
چهارشنبه 11 خرداد 1390, 23:25 عصر
یک شی باید سعی کنه تا حد امکان، برای کارهای خودش مستقل عمل کنه. اینکه یک شی ساده ترین و پایه ایی ترین کارهای خودش رو به اشیاء دیگه واگذار کنه
اولا فکر نمی کنم اینطور باشه، توی تمام کلاسا می بینیم که وقتی یک بخشی از مشخصات و روتین ها به هم نزدیک میشن و یک مفهوم و وزنی پیدا می کنن سریع خودشون تبدیل به کلاس جدید میشن. فکر می کنم از نزدیک ترین مثالها به این موضوع کلاس TAction باشه، یه باتن میاد تنظیم بخش زیادی از اعضای خودش رو در اختیار یه شیئ دیگه قرار میده. در ثانی کل این قضیه به اینجا برمی گرده که شما فرض رو بر این قرار دادید که مقادیری مثل Row و Col اساسا مشخصات TMinesweeper هستند و لاغیر، حالا ما اگه ببریمشون جای دیگه استقلال Tinesweeper رو از بین بردیم. در مقابل من دارم تعاریف اینها رو عوض می کنم، و توی تعریف جدید می گم Col و Row اساسا مشخصات کل برنامه هستند و در کلاس سطح بالاتری پیاده سازی میشن و TMinesweeper تحت عنوان موتور و پیشبرنده بازی تنها مصرف کننده این مقادیره و کارکرد خوش رو بر اساس مشخصات اون کلاس سطح بالاتر تنظیم می کنه.
ببینید من TMinesweeperی که مد نظر شماست رو درک می کنم و قبول دارم تو فضایی که شما تصویر می کنید جای Col و Row دقیقا تو TMinesweeperه. اما تو چیزی که من دارم توضیح میدم معنا و نقش خود TMinesweeper تغییر کرده.

در پست 13 همین تاپیک مفصلا توضیح دادم:
http://barnamenevis.org/showthread.p...=1#post1271698

...اما من بازهم می پرسم که توی این شرایط کلاس TOption دقیقا چکارست؟
لود کرد و ذخیره کردن تنظیمات برنامه در یک محل ذخیره سازی دائمی، به طوری که برنامه بتونه در هر زمانی به آنها دسترسی داشته باشه.
فکر می کنم توی دور بدی افتادیم :گیج:

vcldeveloper
شنبه 14 خرداد 1390, 20:49 عصر
اولا فکر نمی کنم اینطور باشه، توی تمام کلاسا می بینیم که وقتی یک بخشی از مشخصات و روتین ها به هم نزدیک میشن و یک مفهوم و وزنی پیدا می کنن سریع خودشون تبدیل به کلاس جدید میشن. فکر می کنم از نزدیک ترین مثالها به این موضوع کلاس TAction باشه، یه باتن میاد تنظیم بخش زیادی از اعضای خودش رو در اختیار یه شیئ دیگه قرار میده.
مثالی که زدید، ارتباطی به مطلبی که گفته شد نداره؛ کنترل هایی مثل TButton و غیره خصوصیات خودشان را دارند، اما بک یک کلاس Action اجازه میدند که یک سری خصوصیات را خودش برای خودش نگهداری کنه، و اگر تغییری در اون خصوصیات رخ داد، این کنترل ها رو با خبر کنه، تا اینها هم خصوصیات خودشون رو تغییر بدند. پس کنترل ها خصوصیات خودشون مثل Caption, Visible, Enabled و غیره را دارند، Action هم خصوصیات Enabled, Visible, Caption و غیره خودش را داره. اینها با هم مشترک نیستند. اما وقتی خصوصیتی از Action تغییر میکنه، کنترل هایی که به اون Action متصل هستند هم از این تغییر مطلع میشند، و خصوصیات مربوطه رو تغییر میدند.

کلاس هایی هم در طراحی های مختلف خصوصیاتی رو به کلاس های دیگه منتقل می کنند، به دو صورت عمل می کنند؛ اول کلاس هایی که خودشون حدی از پیچیدگی رو دارند، و فرآیند های مختلف در آنها به بخش های کوچک تر تقسیم میشه، و این بخش های کوچکتر اون کلاس بزرگتر رو میسازند؛ مثلا یک کنترل باید بتونه خودش رو رسم کنه، خودش رو استریم کنه، و غیره. هر کدوم از این ها کارهای مستقلی هستند. برای پرهیز از تکرار و استفاده مجدد از کد، و برای ماجولار کردن طراحی اون کلاس، میان این کارها را به بخش های مختلفی تقسیم می کنند، و هر کار رو در یک کلاس پیاده سازی می کنند، اون وقت، کنترل مربوطه شی یا اشیائی از این کلاس ها میسازه، و خودش کنترل اون شی رو برعهده میگیره؛ مثلا کلاس TCanvas یا TFont که یک نمونه شی از آنها توسط هر یک از کنترل ها ساخته میشه. به این کار در طراحی میگن Composition؛ یعنی کنترل مربوطه صاحب این اشیاء هست، و این اشیاء در کنار هم اون شی بزرگتر رو میسازند. مثل اتوموبیل که هر یک از اجزای اون مثل موتور، صندلی، چرخ و غیره همگی روی شاسی سوار میشند که اتوموبیل رو بوجود بیارند. در اون صورت، اینها همگی اجزاء یک اتومبیل هستند.
دوم، کلاس هایی که جزئی از هم نیستند، بلکه با هم در جهت رسیدن به یک نتیجه مشخص همکاری می کنند. مثل همون کلاس TButton و TAction؛ یا TDataSource و TDataset، و غیره؛ به این کار در طراحی میگن Aggregation. در Aggregation کلاس های مربوطه به هم وابسته هستند، اما جزئی از هم نیستند؛ بلکه در حقیقت با هم تعامل می کنند.

نکته دیگه اینه که هر کلاس باید داده های پایه و حیاتی خودش رو نگهداری کنه، این یک اصل شی گرایی هست که بهش میگن Encapsulation. حالا با این تفاسیر؛ چیزی که شما مطرح کردید؛ یعنی نگهداری داده های TMineSweeper در کلاس TOption، با توجه به این که TOption هم جزئی از TMineSweeper نیست؛ نه با اصول اولیه طراحی شی گرا مطابقت داره، نه اصلا با اصول اولیه مفهوم شی گرایی.


ببینید من TMinesweeperی که مد نظر شماست رو درک می کنم و قبول دارم تو فضایی که شما تصویر می کنید جای Col و Row دقیقا تو TMinesweeperه. اما تو چیزی که من دارم توضیح میدم معنا و نقش خود TMinesweeper تغییر کرده.
اون چیزی که شما مطرح می کنید، داره یک نوع وابستگی اضافی بین TMineSweeper و یک کلاس دیگه ایجاد میکنه، که نیازی به اون نیست؛ و ایجاد وابستگی های غیر ضروری یکی از اصلی ترین چیزهایی هست که طراح نرم افزار و برنامه نویس باید ازش اجتناب کنه، چون باعث میشه که هم تست بخش های مختلف کد سخت تر بشه، و هم اینکه استفاده مجدد از کد دچار مشکل بشه، چون برای استفاده از این TMineSweeper، برنامه نویس باید کلاس های دیگه ایی رو هم به کدهای خودش در پروژه های دیگه اضافه کنه.


فکر می کنم توی دور بدی افتادیم
بله، انگیزه ارسال پست رو از بین میبره.

loo30fer
دوشنبه 16 خرداد 1390, 12:29 عصر
ببخشید دوستان اگه امکان داره اینجا یک تعریف کاملم از شی گرائی بکنید خیلی خوب باشه چون یکی از دوستام گفت وقتی مثلا یک دی ال ال رو وارد برنامت میکنی میشه برنامه نویسی شی گرائی و برام خیلی سوال پیش اومد ؟؟؟؟؟ :متفکر:

vcldeveloper
چهارشنبه 18 خرداد 1390, 20:33 عصر
یکی از دوستام گفت وقتی مثلا یک دی ال ال رو وارد برنامت میکنی میشه برنامه نویسی شی گرائیعجیبا غریبا! DLL های در قالب استاندارد ویندوز بویی از شی گرایی نبردند، چه برسه به اینکه اضافه کردنشون به پروژه، اون کار بشه برنامه نویسی شی گرا!!


اگه امکان داره اینجا یک تعریف کاملم از شی گرائی بکنید:
http://en.wikipedia.org/wiki/Object-oriented_programming

لینک به صفحه فارسی اش هم در همون صفحه هست؛ البته محتوای فارسی درج شده عینا همون محتوای انگلیسی نیست، و محتوای انگلیسی موجود غنی تر هست.

javad.ahmadi
پنج شنبه 16 تیر 1390, 13:29 عصر
سلام به دوستان!!
من کاربر جدید هستم
همتون ایول دارین!! مخصوصا آقا مجتبی!!
منم دارم همین بازی رو مینویسم!! و خیلی از این مورد مشکلا رو دارم و دارم از این تالار واقعا استفاده میکنم