// The getDate() function returns a nested function which refers the 'date' variable defined // by outer function getDate() function getDate() { var date = new Date(); // This variable stays around even after function returns // nested function return function () { return date.getMilliseconds(); } }
// Once getDate() is executed the variable date should be out of scope and it is, but since // the inner function // referenes date, this value is available to the inner function. var dt = getDate(); alert(dt()); alert(dt());
function myNonClosure() { var date = new Date(); return date.getMilliseconds(); }
var MyDate = function () { var date = new Date(); var getMilliSeconds = function () { return date.getMilliseconds(); } } var dt = new MyDate(); alert(dt.getMilliSeconds()); // This will throw error as getMilliSeconds is not accessible.
// This is closure var MyDate = function () { var date = new Date(); // variable stays around even after function returns var getMilliSeconds = function () { return date.getMilliseconds(); }; return { getMs : getMilliSeconds } }
var dt = new MyDate(); alert(dt.getMs()); // This should work.
(function($) { // $() is available here })(jQuery);
// 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>
function App() { var save = function (o) { // write code to save state here.. // you have acces to 'o' here... alert(o); }; return { saveState: save }; }
var app = new App(); app.saveState({ name: "rajesh"});
تعریف مقدماتی fluent interface در ویکی پدیا به شرح زیر است: (+)
In software engineering, a fluent interface (as first coined by Eric Evans and Martin Fowler) is a way of implementing an object oriented API in a way that aims to provide for more readable code.
به صورت خلاصه هدف آن فراهم آوردن روشی است که بتوان متدها را زنجیر وار فراخوانی کرد و به این ترتیب خوانایی کد نوشته شده را بالا برد. پیاده سازی آن هم شامل دو نکته است:
الف) نوع متد تعریف شده باید مساوی با نام کلاس جاری باشد.
ب) در این حالت خروجی متدهای ما کلمه کلیدی this خواهند بود.
برای مثال:
using System;
namespace FluentInt
{
public class FluentApiTest
{
private int _val;
public FluentApiTest Number(int val)
{
_val = val;
return this;
}
public FluentApiTest Abs()
{
_val = Math.Abs(_val);
return this;
}
public bool IsEqualTo(int val)
{
return val == _val;
}
}
}
if (new FluentApiTest().Number(-10).Abs().IsEqualTo(10))
{
Console.WriteLine("Abs(-10)==10");
}
خوب! این مطلبی است که همه جا پیدا میکنید و مطلب جدیدی هم نیست. اما موردی را که سخت میشود یافت این است که طراحی کلاس فوق ایراد دارد. برای مثال شما میتوانید ترکیبهای زیر را هم تشکیل دهید و کار میکند؛ یا به عبارتی برنامه کامپایل میشود و این خوب نیست:
if(new FluentApiTest().Abs().Number(-10).IsEqualTo(10)) ...
if (new FluentApiTest().Abs().IsEqualTo(10)) ...
ولی ... این روش هم صحیح نیست. از ابتدای کار نباید بتوان متد بیربطی را در طول این زنجیره مشاهده کرد. اگر قرار نیست استفاده گردد، نباید هم در intellisense ظاهر شود و پس از آن هم نباید قابل کامپایل باشد.
بنابراین صورت مساله به این ترتیب اصلاح میشود:
میخواهیم پس از نوشتن FluentApiTest و قرار دادن یک نقطه، در intellisense فقط Number ظاهر شود و نه هیچ متد دیگری. پس از ذکر متد Number فقط متد Abs یا مواردی شبیه به آن مانند Sqrt ظاهر شوند. پس از انتخاب مثلا Abs آنگاه متد IsEqualTo توسط Intellisense قابل دسترسی باشد. در روش اول فوق، به صورت دوستانه همه چیز در دسترس است و هر ترکیب قابل کامپایلی را میشود با متدها ساخت که این مورد نظر ما نیست.
اینبار پیاده سازی اولیه به شرح زیر تغییر خواهد کرد:
using System;
namespace FluentInt
{
public class FluentApiTest
{
public MathMethods<FluentApiTest> Number(int val)
{
return new MathMethods<FluentApiTest>(this, val);
}
}
public class MathMethods<TParent>
{
private int _val;
private readonly TParent _parent;
public MathMethods(TParent parent, int val)
{
_val = val;
_parent = parent;
}
public Restrictions<MathMethods<TParent>> Abs()
{
_val = Math.Abs(_val);
return new Restrictions<MathMethods<TParent>>(this, _val);
}
}
public class Restrictions<TParent>
{
private readonly int _val;
private readonly TParent _parent;
public Restrictions(TParent parent, int val)
{
_val = val;
_parent = parent;
}
public bool IsEqualTo(int val)
{
return _val == val;
}
}
}
if (new FluentApiTest().Number(-10).Abs().IsEqualTo(10))
{
Console.WriteLine("Abs(-10)==10");
}
در پیاده سازی کلاس MathMethods از Generics استفاده شده به این جهت که بتوان نوع متد Number را بر همین اساس تعیین کرد تا متدهای کلاس MathMethods در Intellisense (یا به قولی در طول زنجیره مورد نظر) ظاهر شوند. کلاس Restrictions نیز به همین ترتیب معرفی شده است و از آن جهت تعریف نوع متد Abs استفاده کردیم. هر کلاس جدید در طول زنجیره، توسط سازنده خود به وهلهای از کلاس قبلی به همراه مقادیر پاس شده دسترسی خواهد داشت. به این ترتیب زنجیرهای را تشکیل دادهایم که سازمان یافته است و نمیتوان در آن متدی را بیجهت پیش یا پس از دیگری صدا زد و همچنین دیگر نیازی به بررسی نحوهی فراخوانیهای یک مصرف کننده نیز نخواهد بود زیرا برنامه کامپایل نمیشود.
مزایای Interface ها چیست ؟
در حالت عادی ارث بری از چند کلاس به طور هم زمان امکان پذیر نیست ولی Interfaceها این مزیت را دارند که به هر تعداد که لازم است، کلاسهای مشتق شده از آنها ارث بری کنند. این موضوع یکی از مهمترین مزایای Interface میباشد. هم چنین با استفاده از Interfaceها کدها قابلیت بهتری در نگهداری، انعطاف پذیری و استفاده مجدد پیدا میکنند.
Abstract Class چیست ؟
کلاس Abstract، یکی از ابزارهای مهم OOP میباشد که نمیتوان از آنها نمونهای ساخت. به عبارتی دیگر نمیتوانیم متغیری از کلاس Abstract تعریف کنیم. یک کلاس Abstract شبیه Interface میباشد ولی با دیدی وسیعتر. این کلاسها میتواند دارای متدهای Abstract باشند که شبیه Interface فقط اعلام میشوند و باید در کلاسهای مشتق شده بازنویسی شوند. البته میتوان در این کلاسها متدهایی داشت که Abstract نیستند و احتیاجی به پیاده سازی آنها در کلاسهای مشتق شده ندارند.
باید توجه داشت که تنها متدهایی از کلاس abstract الزام به پیاده سازی دارند که صریحا کلمهی abstract در تعریف آن متد ذکر شده باشد.
در واقع همین متدها هم الزامی به پیاده سازی ندارند. یعنی میشود در subclass هم به صورت abstract ذکر شوند. البته به شرطی که subclass هم به صورت abstract تعریف شده باشد.
در ضمن کلاس abstract میتواند متدهای ساده یا غیر abstract هم داشته باشد. همانطور که میدانید متدهای غیر abstract باید بدنه داشته باشند و نیازی به پیاده سازی ندارند.
پس کلاس abstract هم میتواند متدهایی داشته باشد که باید پیاده سازی شوند و هم متدهایی داشته باشد که لازم نباشد پیاده سازی شوند.
با توجه به تعاریف ذکر شده کلاس Abstract حالتی بین کلاسهای معمولی و Interfaceها میباشد و کلاسی میباشد که غیر قطعی و ناتمام است که باید در سطح فرزندانش تکمیل شود .
مزایای کلاسهای Abstract چیست ؟
یکی از مزیتهای کلاس Abstract فراهم نمودن کلاسی پایه برای دیگر کلاسهای مشتق شده است؛ با این توضیح که متدهای آن میتوانند کد نویسی شده باشند یا خیر. از طرفی پیاده سازی تمام متدهای Abstract در کلاس مشتق شده اجباری نیست (برخلاف Interface).
تعریف سطوح دسترسی برای متدها و خصوصیتها مانند کلاسهای معمولی نیز یکی دیگر از مزیتهای این کلاسها است.
تفاوت بین کلاسهای Abstract و Interface
1- یک کلاس معمولی تنها میتواند از یک کلاس Abstract ارث بری کند ولی همان کلاس میتواند از چندین Interface ارث ببرد.
2- یک Interface فقط میتواند اعلان متدها و خصوصیتها را داشته باشد؛ اما یک کلاس Abstract علاوه بر آنها میتوانید متدها و خصوصیتهایی با کدهای کامل داشته باشد.
3- عناصر موجود در کلاس Abstract میتوانند مانند یک کلاس معمولی دارای سطح دسترسی باشند؛ ولی Interfaceها فاقد این امکان هستند.
4- وقتی شما متدی را به کلاس Abstract اضافه میکنید، به طور خودکار به همه زیر کلاسها اعمال میشود؛ اما در Interface اگر متدی اضافه کنید باید در تمام زیر کلاسها آن را اعمال کنید .
5- کلاسهای Abstract مانند کلاسهای معمولی میتوانند دارای فیلد و عناصر دیگری (مثل ثابتها) باشند؛ در حالیکه یک Interface فاقد این امکان میباشد. همچنین کلاس abstract میتواند شامل سازنده باشد، اما اینترفیس نمیتواند.
6- Abstract یکی از انواع کلاس است؛ ولی Interface کلاس نیست .
7- اینترفیس تنها میتواند از اینترفیس ارث بری کند اما کلاس abstract میتواند از اینترفیس، کلاس Abstract و یا سایر کلاسها ارث بری کند.
چه زمانی از Interface ها و یا کلاسهای Abstract استفاده کنیم؟
- با توجه به توضیحات ذکر شده مواقعی که نیاز به وراثت چند گانه داریم، باید از Interface استفاده کنیم؛ به دلیل اینکه این امکان در کلاسهای Abstract وجود ندارد.
- زمانی که بخواهیم تمام متدهای معرفی شده در کلاس پایه به طور کامل در کلاس مشتق شده پیاده شوند باید از Interface استفاده کنیم.
- وقتی در پروژههای بزرگ با تغییرات زیادی مواجه هستیم، استفاده از کلاس Abstract توصیه میشود؛ چون با تغییر آن به طور خودکار تغییرات در کلاسهای مشتق شده اعمال میشوند.
- با توجه به اینکه به غیر از اعلان متدها و خصوصیتها امکان تعریف عناصر دیگری در Interfaceها وجود ندارد، در صورتیکه ملزم به استفاده از این عناصر باشیم، استفاده از کلاسهای Abstract ضروری میباشد.
- در صورتی که نخواهیم کلیه متدها در کلاسهای مشتق شده پیاده سازی شوند و تعدادی از آنها را در کلاس پدر کدنویسی کنیم، باید از کلاس Abstract استفاده کنیم.- به طور کلی یک Interface چارچوب و قابلیتهای یک کلاس را مشخص میکند و یک قرارداد است؛ ولی کلاس Abstract نوع کلاس را معین میکند. این تفاوت کمک بسیاری برای تشخیص زمان استفاده از این دو را به برنامه نویسان میدهد.
12 دوره طراحی وب واکنشگرا
Dependency Injection
در ادامه مباحث بهتر کد بنویسیم و الگوهایی که در این رابطه معرفی شدند، اخیرا کتابی از انتشارات manning منتشر شده تحت عنوان Dependency Injection . هر چند به ظاهر این کتاب برای جاوا کارها تهیه شده اما قسمت عمدهای از آن برای سایر زبانهای برنامه نویسی دیگر نیز قابل استفاده است.
In object-oriented programming, a central program normally controls other objects in a module, library, or framework. With dependency injection, this pattern is inverted—a reference to a service is placed directly into the object which eases testing and modularity. Spring or Google Guice use dependency injection so you can focus on your core application and let the framework handle infrastructural concerns.
Dependency Injection explores the DI idiom in fine detail, with numerous practical examples that show you the payoffs. You'll apply key techniques in Spring and Guice and learn important pitfalls, corner-cases, and design patterns. Readers need a working knowledge of Java but no prior experience with DI is assumed.
WHAT'S INSIDE:
◊ How to apply it (Understand it first!)
◊ Design patterns and nuances
◊ Spring, Google Guice, PicoContainer, and more
◊ How to integrate DI with Java frameworks
راستی، این کتاب تر و تازه رو میتونید از همین کتاب فروشیهای دور و اطراف نیز تهیه کنید! در سایت booktraining دات ارگ در قسمت graphics-and-design به تاریخ 4 آگوست.
نیاز به یک Dispatcher برای تعامل با بیش از یک مخزن حالت
در اینجا برای نمونه دو مخزن حالت تعریف شدهاند؛ اما روش تعامل با این مخازن حالت، دیگر مانند قبل نیست. برای نمونه در اثر تعامل یک کاربر با View ای خاص، رخدادی صادر شده و اینبار مدیریت این رخداد توسط یک Action (که عموما یک پیام رشتهای است)، به Dispatcher مرکزی ارسال میشود (و نه مستقیما به مخزن حالت خاصی). اکنون این Dispatcher، اکشن رسیده را به مخازن کد مشترک به آن ارسال میکند تا عمل متناسب با آن اکشن درخواستی را انجام دهند. مابقی آن همانند قبل است که پس از تغییر حالت در هر کدام از مخازن حالت، کار به روز رسانی UI، در کامپوننتهای مشترک صورت خواهد گرفت. بدیهی است در اینجا مخازن حالت، مجاز به صرفنظر کردن از یک اکشن خاص هستند و الزامی به پیاده سازی آن ندارند. هدف اصلی این است که اگر اکشنی قرار بود در تمام مخازن حالت پیاده سازی شود و حالتهای آنها را تغییر دهد، روشی را برای مدیریت آن داشته باشیم.
بنابراین اگر به این الگوی جدید دقت کنید، چیزی نیست بجز یک الگوی Observer دو سطحی:
الف) Dispatcher ای (Subject) که مشترکهایی را مانند مخازن حالت دارد (Observers).
ب) مخازن حالتی (Subjects) که مشترکهایی را مانند کامپوننتها دارند (Observers).
اگر پیشتر با React کار کرده باشید، این الگو را تحت عناوینی مانند Flux و یا Redux میشناسید و در اینجا میخواهیم پیاده سازی #C آنرا بررسی کنیم:
در الگوی Flux، در اثر تعامل یک کاربر با کامپوننتی، اکشنی به سمت یک Dispatcher ارسال میشود. سپس Dispatcher این اکشن را به مخزن حالتی جهت مدیریت آن ارسال میکند که در نهایت سبب تغییر حالت آن شده و به روز رسانی UI را در پی خواهد داشت.
پیاده سازی یک Dispatcher برای تعامل با بیش از یک مخزن حالت
پیش از هر کاری نیاز است قالب اکشنهای ارسالی را که قرار است توسط مخازن حالت مورد پردازش قرار گیرند، مشخص کنیم:
namespace BlazorStateManagement.Stores { public interface IAction { public string Name { get; } } }
namespace BlazorStateManagement.Stores.CounterStore { public class IncrementAction : IAction { public const string Increment = nameof(Increment); public string Name { get; } = Increment; } public class DecrementAction : IAction { public const string Decrement = nameof(Decrement); public string Name { get; } = Decrement; } }
پس از تعریف ساختار یک اکشن، اکنون نوبت به پیاده سازی راه حلی برای ارسال آن به تمام مخازن حالت برنامه است:
using System; namespace BlazorStateManagement.Stores { public interface IActionDispatcher { void Dispatch(IAction action); void Subscribe(Action<IAction> actionHandler); void Unsubscribe(Action<IAction> actionHandler); } public class ActionDispatcher : IActionDispatcher { private Action<IAction> _actionHandlers; public void Subscribe(Action<IAction> actionHandler) => _actionHandlers += actionHandler; public void Unsubscribe(Action<IAction> actionHandler) => _actionHandlers -= actionHandler; public void Dispatch(IAction action) => _actionHandlers?.Invoke(action); } }
این سرویس را نیز با طول عمر Scoped به سیستم تزریق وابستگیهای برنامه معرفی میکنیم که سبب میشود تا پایان عمر برنامه (بسته شدن مرورگر یا ریفرش کامل صفحهی جاری)، در حافظه باقی مانده و وهله سازی مجدد نشود. به همین جهت تزریق آن در مخازن حالت مختلف برنامه، دقیقا حالت یک Dispatcher اشتراکی را پیدا خواهد کرد.
namespace BlazorStateManagement.Client { public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); // ... builder.Services.AddScoped<IActionDispatcher, ActionDispatcher>(); // ... } } }
استفاده از IActionDispatcher در مخازن حالت برنامه
در ادامه میخواهیم مخازن حالت برنامه را تحت کنترل سرویس IActionDispatcher قرار دهیم تا کاربر بتواند اکشنی را به Dispatcher ارسال کند و سپس Dispatcher این درخواست را به تمام مخازن حالت موجود، جهت بروز واکنشی (در صورت نیاز)، اطلاعات رسانی نماید.
برای این منظور سرویس ICounterStore قسمت قبل ، به صورت زیر تغییر میکند که اینترفیس IDisposable را پیاده سازی کرده و همچنین دیگر به همراه متدهای عمومی افزایش و یا کاهش مقدار نیست:
using System; namespace BlazorStateManagement.Stores.CounterStore { public interface ICounterStore : IDisposable { CounterState State { get; } void AddStateChangeListener(Action listener); void BroadcastStateChange(); void RemoveStateChangeListener(Action listener); } }
using System; namespace BlazorStateManagement.Stores.CounterStore { public class CounterStore : ICounterStore { private readonly CounterState _state = new(); private bool _isDisposed; private Action _listeners; private readonly IActionDispatcher _actionDispatcher; public CounterStore(IActionDispatcher actionDispatcher) { _actionDispatcher = actionDispatcher ?? throw new ArgumentNullException(nameof(actionDispatcher)); _actionDispatcher.Subscribe(HandleActions); } private void HandleActions(IAction action) { switch (action) { case IncrementAction: IncrementCount(); break; case DecrementAction: DecrementCount(); break; } } public CounterState State => _state; private void IncrementCount() { _state.Count++; BroadcastStateChange(); } private void DecrementCount() { _state.Count--; BroadcastStateChange(); } public void AddStateChangeListener(Action listener) => _listeners += listener; public void RemoveStateChangeListener(Action listener) => _listeners -= listener; public void BroadcastStateChange() => _listeners.Invoke(); public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_isDisposed) { try { if (disposing) { _actionDispatcher.Unsubscribe(HandleActions); } } finally { _isDisposed = true; } } } } }
- با توجه به اینکه CounterStore یک سرویس ثبت شدهی در سیستم است، میتواند از مزیت تزریق سایر سرویسها در سازندهی خودش بهرهمند شود؛ مانند تزریق سرویس جدید IActionDispatcher.
- پس از تزریق سرویس جدید IActionDispatcher، متدهای Subscribe آنرا در سازندهی کلاس و Unsubscribe آنرا در حین Dispose سرویس، فراخوانی میکنیم. البته فراخوانی و یا پیاده سازی Unsubscribe و Dispose در اینجا غیرضروری است؛ چون طول عمر این کلاس با طول عمر برنامه یکی است.
- بر اساس این الگوی جدید، هر اکشنی که به سمت Dispatcher مرکزی ارسال میشود، در نهایت به متد HandleActions یکی از مخازن حالت تعریف شده، خواهد رسید:
private void HandleActions(IAction action) { switch (action) { case IncrementAction: IncrementCount(); break; case DecrementAction: DecrementCount(); break; } }
@inject ICounterStore CounterStore @code { private void IncrementCount() { CounterStore.IncrementCount(); }
- ابتدا در انتهای فایل Client\_Imports.razor، فضای نام سرویس جدید IActionDispatcher را اضافه میکنیم:
@using BlazorStateManagement.Stores
// ... @inject IActionDispatcher ActionDispatcher @code { private void IncrementCount() { ActionDispatcher.Dispatch(new IncrementAction()); }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: BlazorStateManagement-Part-2.zip
مدیریت سفارشی سطوح دسترسی کاربران در MVC
Memento یک الگوی طراحی مفید و ساده است که برای ذخیره و بازیابی state یک object استفاده میشود. در بعضی از مقالات از آن به عنوان snapshot نیز یاد شده است! اگر با git کار کرده باشید، این مفهوم را میتوان در git بسیار یافت؛ هر commit به عنوان یک snapshot میباشد که میتوان به صورت مکرر آن را undo کرد و یا مثال خیلی سادهتر میتوان به ctrl+z در سیستم عامل اشاره کرد.
به مثال زیر توجه کنید:
Int temp; Int a=1; temp=a; a=2; . . a=temp;
شما قطعا در برنامه نویسی با کد بالا زیاد برخورد داشتهاید و آنرا به صورت مکرر انجام دادهاید. کد بالا را در قالب یک object بیان میکنیم. به مثال زیر توجه کنید:
int main() { MyClass One = new MyClass(); MyClass Temp = new MyClass(); // Set an initial value. One.Value = 10; One.Name = "Ten"; // Save the state of the value. Temp.Value = One.Value; Temp.Name = One.Name; // Change the value. One.Value = 99; One.Name = "Ninety Nine"; // Undo and restore the state. One.Value = Temp.Value; One.Name = Temp.Name; }
در کد بالا با استفاده از یک temp، شیء مورد نظر را ذخیره کرده و در آخر مجدد دادهها را درون شیء، restore میکنیم.
از مشکلات کد بالا میتوان گفت :
۱- برای هر object باید یک شیء temp ایجاد کنیم.
۲- ممکن است بخواهیم که حالات یک object را بر روی هارد ذخیره کنیم. با روش فوق کدها خیلی پیچیدهتر خواهند شد.
۳- نوشتن کد به این سبک برای پروژههای بزرگ، پیچیده و مدیریت آن سختتر میشود.
پیاده سازی memento
ما این مثال را در قالب یک پروژه NET Core onsole. ایجاد میکنیم. برای این کار یک پوشهی جدید را ایجاد و درون ترمینال دستور زیر را وارد کنید:
dotnet new console
روشهای زیادی برای پیاده سازی memento وجود دارند. برای پیاده سازی memento ابتدا یک abstract class را به شکل زیر ایجاد میکنیم:
abstract class MementoBase { protected Guid mementoKey = Guid.NewGuid(); abstract public void SaveMemento(Memento memento); abstract public void RestoreMemento(Memento memento); }
اگر به کلاس بالا دقت کنید، این کلاس قرار است parent کلاسهای دیگری باشد که داری دو متد SaveMemento و RestoreMemento برای ذخیره و بازیابی و همچنین یک Guid برای نگهداری stateهای مختلف میباشد.
ورودی متدها از نوع memento میباشد. پس کلاس memento را به شکل زیر ایجاد میکنیم:
class Memento { private Dictionary<Guid, object> stateList = new Dictionary<Guid, object>(); public object GetState(Guid key) { return stateList[key]; } public void SetState(Guid key, object newState) { stateList[key] = newState; } public Memento() { } }
در کد بالا با یک Dictionary میتوان هر object را با کلیدش ذخیره کنیم. توجه کنید که value دیکشنری از نوع object میباشد و چون object پدر تمام objectهای دیگر است پس میتوانیم هر نوع دادهای را در آن ذخیره کنیم. تا اینجا، Memento پیاده سازی شده است. میتوان این کار را با جنریکها نیز پیاده سازی کرد.
در ادامه میخواهیم یک کلاس بسازیم و حالتهای مختلف را در آن بررسی کنیم. کلاس زیر را ایجاد کنید:
class ConcreteOriginator : MementoBase { private int value = 0; public ConcreteOriginator(int newValue) { SetData(newValue); } public void SetData(int newValue) { value = newValue; } public void Speak() { Console.WriteLine("My value is " + value.ToString()); } public override void SaveMemento(Memento memento) { memento.SetState(mementoKey, value); } public override void RestoreMemento(Memento memento) { int restoredValue = (int)memento.GetState(mementoKey); SetData(restoredValue); } }
کلاس ConcreteOriginator از کلاس MementoBase ارث بری کرده و دو متد RestoreMemento و SaveMemento را پیاده سازی میکند و همچنین دارای یک مشخصه value میباشد. برای خروجی گرفتن، متد main را به صورت زیر پیاده سازی میکنیم:
static void Main(string[] args) { Memento memento = new Memento(); // Create an originator, which will hold our state data. ConcreteOriginator myOriginator = new ConcreteOriginator("Hello World!", StateType.ONE); ConcreteOriginator anotherOriginator = new ConcreteOriginator("Hola!", StateType.ONE); ConcreteOriginator2 thirdOriginator = new ConcreteOriginator2(7); // Set some state data. myOriginator.Speak(); anotherOriginator.Speak(); thirdOriginator.Speak(); // Save the states into our memento. myOriginator.SaveMemento(memento); anotherOriginator.SaveMemento(memento); thirdOriginator.SaveMemento(memento); // Now change our originators' states. myOriginator.SetData("Goodbye!", StateType.TWO); anotherOriginator.SetData("Adios!", StateType.TWO); thirdOriginator.SetData(99); myOriginator.Speak(); anotherOriginator.Speak(); thirdOriginator.Speak(); // Restore our originator's state. myOriginator.RestoreMemento(memento); anotherOriginator.RestoreMemento(memento); thirdOriginator.RestoreMemento(memento); myOriginator.Speak(); anotherOriginator.Speak(); thirdOriginator.Speak(); Console.ReadKey(); }
Hello World! I'm in state ONE Hola! I'm in state ONE My value is 7 Goodbye! I'm in state TWO Adios! I'm in state TWO My value is 99 Hello World! I'm in state ONE Hola! I'm in state ONE My value is 7
OpenCVSharp #18
- مشخصات و روند جمع آوری این مجموعه داده در سال 2007 میلادی در مجله Pattern Recognition Letters منتشر شد
- این مجموعه شامل 102,352 نمونه عدد فارسی است که از 12,000 فرم مربوط به آزمون ورودی کاردانی به کارشناسی و کارشناسی ارشد جمع آوری شده است.