دیزاین پترن State در عمل

wikimediaدر قسمتی از پروژه ای که در حال نوشتن بودم و میخواستیم Fail Over رو بر روی قسمتی از سیستم پیاده سازی کنیم تا سیستم بصورت اتومات بتونه بعد از به خطا خوردن و یا مشکل دیگه ای، در سریعترین زمان ممکن یه نسخه دیگه کارش رو انجام بده.البته خود این fail over یه بحث جدا هست و توضیحات بیشتری میخواد که در آینده دربارش مینویسم، بصورت خلاصه این قسمت از برنامه در حال دریافت پیام های بورس هست و اون پیام ها رو برای بقیه سیستم در اختیار میزاره، اگه این قسمت قطع بشه میشه گفت کل سیستم میخوابه، پس اون fail over تو اینجا بدرد میخورهحالا برای این کار تو قسمتی از کد نیاز بود که بدونیم اگه سیستم اصلی خراب شد، سیستم جانشین از چه پیامی باید ادامه بده و با اون پیام ها چه کاری کنه.فرض کنید ما دوتا نسخه داریم با اسم های primary و secondary که اولی پیام ها رو به سیستم ارسال میکنه ولی دومی فقط پیام ها رو در دیتابیس ذخیره میکنه و هرموقع سیستم اول به مشکل خورد، بجاش ادامه میدهبرای اینکه secondary بتونه بجای سیستم اول ادامه بده، سه تا حالت مختلف ممکنه پیش میاد:درست تا جایی که سیستم اول پیام ها رو پردازش کرده بود، دریافت کرده باشهاز سیستم اول عقب تر باشیماز سیستم دوم جلوتر باشیمحالا تو هر کدوم از این حالت ها باید تصمیم متفاوتی بگیریماگه عقب تر باشیم باید پیام ها رو دریافت کنیم و داخل دیتابیس ذخیره کنیم و زمانی که درست به حالت سیستم اول رسیدیم، پیام ها رو ارسال هم کنیماگه جلوتر باشیم باید از دیتابیس بخونیم و وقتی به حالت سیستم اول رسیدیم، از بورس پیام ها رو دریافت کنیم.حالا برای این کار لازم بود که بدونیم دقیقا در چه حالتی هستیم که تو سناریوهای مختلف که بررسی کردیم، سه متغیر زیر به برنامه اضافه شدنpublic static bool IsPrimary { get; private set; } public static bool IsBefotrPrimary { get; private set; } public static bool IsFirstTime { get; private set; }تو قسمت نتیجه گیری هم با توجه به این متغیرها بصورت کد زیر تصمیم میگرفتیم که چیکار کنیم:private static int GetLastDispatchedMessageIdTypeCode() { int result; switch (FailOverService.IsBeforePrimary) { case true when FailOverService.IsPrimary: result = PrimaryTypeCode; break; case true when !FailOverService.IsPrimary: result = SecondaryTypeCode; break; case false when !FailOverService.IsPrimary: result = SecondaryTypeCode; break; case false when FailOverService.IsPrimary && FailOverService.IsFirstTime: result = PrimaryTypeCode; break; case false when FailOverService.IsPrimary && !FailOverService.IsFirstTime: result = SecondaryTypeCode; break; default: throw new Exception(&quotnot valid input&quot); } return result; }همونطور که میبینید کد بالا خیلی خوب نیست و خوانایی کمی هم داره.روشی که برای درست کردن کد بالا انجام دادم، استفاده از دیزاین پترن State بودhttps://sourcemaking.com/design_patterns/stateخلاصه کار به این صورت بود:یه کلاس با اسم Context به این صورت:public class Context { private State _state; public Context() { _state = new InitialState(); } public void TransitionTo(State state) { Logger.WriteInformationLog($&quotContext: Transition to {state.GetType().Name}&quot); _state = state; _state.SetContext(this); } public FailoverReadingTypeEnum GetState() { return _state.Handle(); } }کلاس State به صورت زیر:public abstract class State { protected Context Context; public void SetContext(Context context) { Context = context; } public abstract FailoverReadingTypeEnum Handle(); }یه Enum با مقادیر زیرpublic enum FailoverReadingTypeEnum { None = 0, Primary = 1, Secondary = 2 }روش کار به این صورت هست که ما برای هر کدوم از حالت هایی که نیاز داریم یه کلاس جدید درست میکنیم که از State ارث بری کرده باشهبه این صورت:public class PrimaryState : State { public override FailoverReadingTypeEnum Handle() { Logger.WriteInformationLog($&quotFailoverReadingTypeEnum change to : {FailoverReadingTypeEnum.Primary.ToString()}&quot); return FailoverReadingTypeEnum.Primary; } }برای استفاده هم کافی هست کلاس Context رو تو قسمتی که میخوایم New کنیم و در زمان هایی که لازم داریم State اون رو تغییر بدیم یا State فعلی رو بگیریمpublic FailOverService(ProcessorType processorType) { _context = new Context(); }به این صورت میتونیم حالت رو تغییر بدیم:_context.TransitionTo(new PrimaryState());که ورودی همون کلاس هایی هستن که از State ارث بری کردنبرای دریافت حالت هم کافیه بصورت زیر عمل کنیمpublic static FailoverReadingTypeEnum GetReadingType() { return _context.GetState(); }با این کار در درجه اول یکی از اون boolean ها برای دونستن حالت حذف شد و فقط دو مقدار IsPrimary و IsFirstTime باقی موندناون متد که توش از switch , case استفاده شده بود هم بصورت زیر در اومدprivate static int GetLastDispatchedMessageIdTypeCode() { return (int) FailOverService.GetReadingType(); }لینک مطلب در وبلاگ اصلی :دیزاین پترن State در عمل - روزمرگی های یک برنامه نویس (mhkarami97.ir)