قصد دارم مجموعه ای کامل از اصول طراحی شیء گرا، الگوهای
طراحی و best practice های مربوطه را ارائه دهم. از این رو
ابتدا با اصول طراحی شروع میکنم. سپس در مقالات آتی به الگوهای مختلف خواهم
پرداخت و تا جایی که معلومات اجازه دهد مشخص خواهم کرد که هر الگو متمرکز بر رعایت
کدام یک از اصول است و اینکه آیا مناسب است از الگوی مورد نظر استفاده کنیم.
این مطالب نیز چکیده ای از آموزشهای Lynda, Pluralsight , SkillFeed و کتاب های Gang of four, Headfirst Design
patterns و ...
میباشد .
اصل اول: Encapsulate
what varies
"آنچه را که تغییر میکند مشخص و جدا کن
یا به عبارتی آنرا کپسوله کن"
برای آنکه بتوانیم کدی منعطف،
قابل استفاده مجدد و خوانا داشته باشیم، ابتدا باید بخشهای ثابت و متغیر کد را تشخیص
دهیم و کاری کنیم تا بخش ثابت، بدون تکرار در جای جای برنامه استفاده شود و سپس برای بخش
متغیر برنامه ریزی کنیم.
اصل دوم: Program to an interface not implementation
" برنامه نویسی را
متمرکز بر واسط کن، نه نحوهی پیاده سازی "
برای این اصل تعابیر مختلفی ارائه
شده است:
- تمرکز بر واسطها به معنای تمرکز بر رفتار است که باعث میشود انعطاف برنامه بیشتر شده و با تغییر نحوهی پیاده سازی بتوان همچنان سیستمی پایدار داشت.
- تمرکز بر "چه کاری انجام میشود" باعث میشود بدون نگرانی از "چگونگی انجام آن" بتوانیم معماری سیستم را طراحی کنیم.
- واسطها نقش پروتکل را دارند و باعث پنهان شدن نحوهی پیاده سازی از چشم کلاینت (استفاده کنندهی خدمت) میشوند و آنها را ملزم میکنند تا به ورودی و خروجی تمرکز کنند.
برای رعایت این اصل باید:
- سعی بر تعریف واسط برای اکثر کلاسها کنیم
- اشیا را از نوع واسط تعریف
کنیم، نه کلاسهای پیاده ساز آن
در کد زیر نحوهی تعریف واسط
را برای کلاس و تعریف اشیاء از نوع واسط را میبینیم:
public interface IMyInterface
{
void DoWork();
}
public class MyImplementation1 : IMyInterface
{
public void DoWork()
{
var implementationName = this.GetType().ToString();
Console.WriteLine("DoWork from " + implementationName);
}
}
public class MyImplementation2 : IMyInterface
{
public void DoWork()
{
var implementationName = this.GetType().ToString();
Console.WriteLine("DoWork from " + implementationName);
}
}
public class Context
{
IMyInterface myInterface;
public void Print()
{
myInterface = new MyImplementation1();
myInterface.DoWork();
myInterface = new MyImplementation2();
myInterface.DoWork();
}
}
اصل سوم: Favor composition over inheritance
"ترکیب را بر توارث ترجیح
بده"
رابطه "دارد" (has a ) انعطاف بیشتری نسبت به ارث بری یا "از نوع ... هست" ( is a ) دارد. برای مثال فرض کنید کلاس Enemy رفتار Movable را دارد و این رفتار در طول بازی
بر حسب موقعیتی تغییر میکند. اگر این رفتار را با توارث مدل کنیم، یعنی Enemy از نوع Movable باشد، آنگاه برای هر نوع رفتار Movable (هر نوع پیاده سازی) باید یک نوع Enemy
داشته باشیم و تصور کنید بعضی از این پیاده سازیها در کلاس Player یکسان باشد. آنگاه کد باید دوباره تکرار شود. حال تصور کنید این
موقعیت را با ترکیب مدل کنیم. آنگاه کلاس Enemy یک شیء
از جنس Movable خواهد داشت و در زمان نیاز
میتواند نوع این رفتار را با نمونه گیری از کلاسهای پیاده ساز آن، تغییر دهد. در واقع با اینکار اصل اول را رعایت کرده ایم و بخش ثابت را از بخش متغیر جدا نموده
ایم.
در زیر مدلسازی با توارث را میبینیم:
public interface Movable
{
void Move();
}
public class EnemyBase : Movable
{
// Enemy properties goes here
protected int x, y;
public virtual void Move()
{
x += 2;
y += 2;
}
}
public class EnemyWithMoveType2 : EnemyBase
{
// override the move method
public override void Move()
{
x += 4;
y += 4;
}
}
public class EnemyWithMoveType3 : EnemyBase
{
// override the move method
public override void Move()
{
x += 6;
y += 6;
}
}
public class PlayerBase : Movable
{
// Player properties goes here
protected int x, y;
public virtual void Move()
{
// same code as EnemyBase move method
x += 2;
y += 2;
}
}
public class PlayerWithMoveType2 : PlayerBase
{
// override the move method
public override void Move()
{
// same code as EnemyWithMoveType2 move method
x += 4;
y += 4;
}
}
public class PlayerWithMoveType3 : PlayerBase
{
// override the move method
public override void Move()
{
// same code as EnemyWithMoveType3 move method
x += 6;
y += 6;
}
}
در ادامه نیز مدلسازی با ترکیب
را میبینیم:
public interface IMovable
{
void Move(ref int x, ref int y);
}
public class EnemyBase
{
// Enemy properties goes here
protected int x, y;
IMovable moveBehavior;
public void SetMoveBehavior(IMovable _moveBehavior) { moveBehavior = _moveBehavior; }
public void Move()
{
moveBehavior.Move(ref x,ref y);
}
}
public class PlayerBase
{
// Player properties goes here
protected int x, y;
IMovable moveBehavior;
public void SetMoveBehavior(IMovable _moveBehavior) { moveBehavior = _moveBehavior; }
public void Move()
{
moveBehavior.Move(ref x, ref y);
}
}
public class MoveBehavior1
{
public void Move(ref int x, ref int y)
{
x += 2;
y += 2;
}
}
public class MoveBehavior2 : IMovable
{
public void Move(ref int x, ref int y)
{
x += 4;
y += 4;
}
}
public class MoveBehavior3 : IMovable
{
public void Move(ref int x, ref int y)
{
x += 6;
y += 6;
}
}
همانطور که میبینید، با
فراخوانی تابع SetMoveBehavior میتوان رفتار را در زمان اجرا
تغییر داد.
در مقالهی بعدی به ادامهی اصول خواهم پرداخت. از شنیدن نظرات و سوالات شما خرسند
خواهم شد.