Singleton design pattern is one of the simplest design patterns: it involves only one class throughout the application which is responsible to instantiate itself, to make sure it creates not more than one instance; in the same time it provides a global point of access to that instance. In this case the same instance can be used from everywhere, being impossible to invoke directly the constructor each time.
کدهای متورم (Bloaters)
- استفاده از متغیرهای اولیه بجای ساختارهای کوچک برای کارهای اولیه مانند Currency, DateTime, PhoneNumber
- استفاده از constantها برای کد کردن اطلاعات مانند USER_ADMIN_ROLE = 1
- استفاده از constantهای رشتهای به عنوان نام فیلدها در آرایههای داده
بد استفاده کنندگان از شیء گرایی (Object orientation abusers)
جلوگیری کنندگان از تغییر(Change preventers)
کدهای غیر ضروری (Dispensables)
کدهایی بیش از اندازه وابسته به هم (Couplers)
در این الگو ابتدا شیء از استخر درخواست میشود. اگر قبلا ایجاد شده باشد، مجددا مورد استفاده قرار میگیرد. در غیر این صورت نمونه جدیدی ایجاد میشود و وقتی که کار آن پایان یافت به استخر بازگشت داده شده و نگهداری میشود، تا زمانی که مجددا درخواست استفاده از آن برسد.
یکی از نکات مهم و کلیدی این است که وقتی کار شیء مربوطه تمام شد، باید اطلاعات شیء قبلی و دادههای آن نیز پاکسازی شوند؛ در غیر این صورت احتمال آسیب و خطا در برنامه بالا میرود و این الگو را به یک ضد الگو تبدیل خواهد کرد.
نمونهای از پیاده سازی این الگو را در دات نت، میتوانید در data providerهای مخصوص SQL Server نیز ببینید. از آنجا که ایجاد اتصال به دیتابیس، هزینه بر بوده و دستورات در کوتاهترین زمان ممکن اجرا میشوند، این کانکشنها یا اتصالات بعد از بسته شدن از بین نمیروند، بلکه در استخر مانده تا زمانیکه مجددا نمونه مشابهی از آنها درخواست شود تا دوباره مورد استفاده قرار بگیرند تا از هزینه اولیه برای ایجاد آنها پرهیز شود. استفاده از این الگو در برنامه نویسی با سوکت ها، تردها و کار با اشیاء گرافیکی بزرگ مثل فونتها و تصاویر Bitmap کمک شایانی میکند. ولی در عوض برای اشیای ساده میتواند کارآیی را به شدت کاهش دهد.
الگوی بالا را در سی شارپ بررسی میکنیم:
ابتدا کلاس PooledObject را به عنوان یک شیء بزرگ و پر استفاده ایجاد میکنیم:
public class PooledObject { DateTime _createdAt = DateTime.Now; public DateTime CreatedAt { get { return _createdAt; } } public string TempData { get; set; } public void DoSomething(string name) { Console.WriteLine($"{name} : {TempData} is written on {CreatedAt}"); } }
بعد از آن کلاس پول را پیاده سازی میکنیم:
public static class Pool { private static List<PooledObject> _available = new List<PooledObject>(); private static List<PooledObject> _inUse = new List<PooledObject>(); public static PooledObject GetObject() { lock(_available) { if (_available.Count != 0) { PooledObject po = _available[0]; _inUse.Add(po); _available.RemoveAt(0); return po; } else { PooledObject po = new PooledObject(); _inUse.Add(po); return po; } } } public static void ReleaseObject(PooledObject po) { CleanUp(po); lock (_available) { _available.Add(po); _inUse.Remove(po); } } private static void CleanUp(PooledObject po) { po.TempData = null; } }
متد Release Object وظیفهی بازگرداندن شیء را به استخر، دارد که از لیست در حال استفادهها آن را حذف کرده و به لیست موجودی اضافه میکند. متد Cleanup در این بین وظیفه ریست کردن شیء را دارد تا مشکلی که در بالا بیان کردیم رخ ندهد.
کد زیر را اجرا میکنیم:
var obj1 = Pool.GetObject(); obj1.DoSomething("obj1"); Thread.Sleep(2000); var obj2 = Pool.GetObject(); obj2.DoSomething("obj2"); Pool.ReleaseObject(obj1); Thread.Sleep(2000); var obj3 = Pool.GetObject(); obj3.DoSomething("obj3");
obj1 : is written on 4/21/2016 11:25:26 AM obj2 : is written on 4/21/2016 11:25:28 AM obj3 : is written on 4/21/2016 11:25:26 AM
class Car { } class CarProducer { public void DeliverTo(int carsCount, string town) { Car[] cars = new Car[carsCount]; ... } }
class Transporter { public string Name { get; private set; } public Transporter(string name) { this.Name = name; } public void Deliver(Car[] cars, string town) { Console.WriteLine("Delivering {0} car(s) to {1} by {2}", cars.Length, town, this.Name); } }
static class TransporterLocator { static IList<Transporter> transporters = new List<Transporter>(); public static void Register(Transporter transporter) { transporters.Add(transporter); } public static Transporter Locate(string name) { return transporters .Where(transporter => transporter.Name == name) .Single(); } }
class CarProducer { public void DeliverTo(int carsCount, string town) { Car[] cars = new Car[carsCount]; Transporter transporter = null; if (carsCount <= 12) transporter = TransporterLocator.Locate("truck"); else transporter = TransporterLocator.Locate("train"); transporter.Deliver(cars, town); } }
TransporterLocator.Register(new Transporter("truck")); TransporterLocator.Register(new Transporter("train")); CarProducer producer = new CarProducer(); producer.DeliverTo(7, "Tehran"); producer.DeliverTo(74, "Tehran");
TransporterLocator.Register(new Transporter("truck")); CarProducer producer = new CarProducer(); producer.DeliverTo(7, "Tehran"); producer.DeliverTo(74, "Tehran");
class CarProducer { private Transporter truck; private Transporter train; public CarProducer(Transporter truck, Transporter train) { if (truck == null) throw new ArgumentNullException("truck"); if (train == null) throw new ArgumentNullException("train"); this.truck = truck; this.train = train; } public void DeliverTo(int carsCount, string town) { Car[] cars = new Car[carsCount]; Transporter transporter = this.truck; if (carsCount > 12) transporter = this.train; transporter.Deliver(cars, town); } }
آیا وضعیتی وجود دارد که در آن Service Locator یک راه حل قابل قبول باشد؟
در برخی موارد بجای اینکه وابستگیها را به صورت صریح قید کنیم، بهتر است از این الگو استفاده کنیم.
Strategy pattern is one of the most useful design patterns in OOP. It lets you select an algoritm’s implementation at runtime. However most of the examples you will find online won’t make sense if you are using dependency injection
Nowadays, I am trying to learn different design patterns in object oriented paradigm that are pretty useful to implement generic solutions for different scenarios. Few weeks ago for a job hunt, I got an assignment to do which was a web application that would interact with database, so I took it up as a challenge and decided to make it loosely coupled using design patterns which were applicable in that scenario.
سایت UI Movement
قصد دارم مجموعه ای کامل از اصول طراحی شیء گرا، الگوهای طراحی و 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 میتوان رفتار را در زمان اجرا تغییر داد.
در مقالهی بعدی به ادامهی اصول خواهم پرداخت. از شنیدن نظرات و سوالات شما خرسند خواهم شد.
- می خواهیم کد هایی با قابلیت استفادهی مجدد بنویسیم ، استفاده از عملکردهای مشابه در سطح صفحات یک Web application یا چند Web Application.
- می خواهیم کد هایی با قابلیت نگهداری بنویسیم ، هر چه قدر در فاز توسعه کدهای با کیفیت بنویسیم در فاز نگهداری از آن بهره میبریم. باید کد هایی بنویسیم که قابل Debug و خواندن توسط دیگر افراد تیم باشند.
- کدهای ما نباید با توابع و متغیرهای دیگر پلاگینها تداخل نامگزاری داشته باشند. در برنامههای امروزی بسیار مرسوم است که از پلاگینهای Third party استفاده شود. میخواهیم با رعایت Encapsulation and modularization در کدهایمان از این تداخل جلوگیری کنیم.
function getDate() { var now = new Date(); var utc = now.getTime() + (now.getTimezoneOffset() * 60000); var est; est = new Date(utc + (3600000 * -4)); return dateFormat(est, "dddd, mmmm dS, yyyy, h:MM:ss TT") + " EST"; } function initiate_geolocationToTextbox() { navigator.geolocation.getCurrentPosition(handle_geolocation_queryToTextBox); } function handle_geolocation_queryToTextBox(position) { var longitude = position.coords.longitude; var latitude = position.coords.latitude; $("#IncidentLocation").val(latitude + " " + longitude); }
- توابع و متغیرها به Global scope برنامه افزوده میشوند.
- کد Modular نیست.
- احتمال رخ دادن Conflict در اسامی متغیرها و توابع بالا میرود.
- نگهداری کد به مرور زمان سخت میشود.
// file1.js function saveState(obj) { // write code here to saveState of some object alert('file1 saveState'); } // file2.js (remote team or some third party scripts) function saveState(obj, obj2) { // further code... alert('file2 saveState"); }
<script src="file1.js" type="text/javascript"></script> <script src="file2.js" type="text/javascript"></script>