Domain Driven Design - بخش هفتم

در این بخش و در ادامه الگوهای کاربردی، به الگوی Decorator و ترکیب آن با Command pattern می پردازیم و موضوع Framework را بررسی می کنیم.Domain Driven Designجمله "Favor Composition Over Inheritance" را در قوانین طراحی بارها شنیده ایم که نشان از برتری composition نسبت به inheritance دارد. اما چه محدودیتی در inheritance می تواند وجود داشته باشد؟مفهوم inheritance، به صورت static و compile time اتفاق می افتد که می تواند بزرگترین محدودیت آن باشد. محدودیت های دیگری مانند عمق ارث بری وجود دارد که ممکن است باعث ایجاد ساختارهای شکننده شود. الگوی decorator، به اضافه کردن یک رفتار به یک object به صورت dynamic می پردازد و می تواند یک جایگزین مناسب برای مفهوم subclassing باشد.decoratorدر این دیاگرام، یک component را ملاحظه می کنید که یک abstraction از کلاسی است که قرار است decorate شود و رفتاری به آن اضافه شود. جزء دیگری به نام decorator را ملاحظه می کنید که از component ارث بری کرده است و یک instance نیز از آن دارد.برای مثال یک abstraction با نام IWriter و یک پیاده سازی به عنوان concrete component برای آن در نظر می گیریم.public interface IWriter { void Write(); } public class Writer : IWriter { public void Write() { Console.WriteLine(&quotI'm writing&quot); } }از طرفی یک کلاس به عنوان decorator داریم که از component ارث بری می کند و یک instance از آن را نیز در اختیار دارد.public class DashSeparatorDecotator : IWriter { private readonly IWriter _writer; public DashSeparatorDecotator(IWriter writer) { _writer = writer; } public void Write() { Console.WriteLine(&quot--------&quot); _writer.Write(); Console.WriteLine(&quot--------&quot); } }در سطح client، با فرض این که Writer به یک طریقی به دست client می رسد، می تونیم آن را به این شکل در نظر بگیریم.IWriter writer = CreateWriter(); writer.Write(); public IWriter CreateWriter() { return new DashSeparatorDecotator(new Writer()); }از آنجایی که client فقط interface را می شناسد، قابلیت این را داریم که آن را به بینهایت شکل درست کنیم و به دست client برسانیم. با فرض اینکه یک decorator دیگر می تونیم داشته باشیم.public class StarDecorator : IWriter { private readonly IWriter _writer; public StarDecorator(IWriter writer) { _writer = writer; } public void Write() { Console.WriteLine(&quot********&quot); _writer.Write(); Console.WriteLine(&quot********&quot); } }از آنجایی که با یک رابطه ارث بری طرف نیستیم، می تونیم به صورت dynamic، رفتار را کم یا زیاد کنیم.public IWriter CreateWriter() { IWriter writer = new Writer(); Console.WriteLine(&quotInsert dashes?&quot); var dashResponse = Console.ReadLine(); if (dashResponse == &quoty&quot) writer = new DashSeparatorDecotator(writer); Console.WriteLine(&quotInsert star?&quot); var starResponse = Console.ReadLine(); if (starResponse == &quoty&quot) writer = new StarDecorator(writer); return writer; }در ترکیب decorator و command pattern، از آنجایی که command hanlderها دارای interface یکسانی هستند، براحتی می تونیم decoratorهای متفاوتی را بنویسیم.public class LoggingCommandHandlerDecorator<T> : ICommandHandler<T> where T : ICommand { private readonly ICommandHandler<T> _handler; public LoggingCommandHandlerDecorator(ICommandHandler<T> handler) { _handler = handler; } public void Handle(T command) { Console.WriteLine($&quotCommand stated! {typeof(T).Name}&quot); _handler.Handle(command); Console.WriteLine($&quotCommand {typeof(T).Name} executed successfully&quot); } }از این طریق می تونیم با در نظر گرفتن Open-Closed Principle در اصول SOLID، یک رفتار را به تمام command handlerها اضافه کنیم. این جریان را می تونیم از طریق IOC Containerها اضافه کنیم. برای مثال در Autofac از طریق متد RegisterGenericDecorator، این امکان وجود دارد.builder.RegisterGenericDecorator (typeof(LoggingCommandHandlerDecorator<>), typeof(ICommandHandler<>));در مفهوم AOP یا Aspect-oriented programming، نیازمندی هایی در نرم افزار داریم که لایه های مختلف را درگیر می کنند. برای مثال در لاگ کردن commandها، در exception handling و این گونه موارد. AOP مجموعه ای از تکنیک ها و ابزارها است که به ما کمک می کند بجای اینکه در تک تک جاها این موارد را بنویسیم، یک جا بنویسیم و در کل پروژه apply کنیم.روش هایی مثل Code Weaving, Interceptor, Decorator, Proxy و غیره، می توانند برای AOP استفاده شوند. با وارد کردن decorator به این جریان، می تونیم کارهای AOP بر روی command handler را مدیریت کنیم. Frameworkزمانی که پروژه ای را design می کنیم، یکسری طراحی انجام می دهیم که ممکن است در BCهای مختلف استفاده شوند. برای مثال قابلیت هایی که برای استفاده از command pattern اضافه کردیم، قابلیت هایی هستند که اگر قرار باشد BCهای domain centric داشته باشیم، باید در تمام آن ها این قابلیت ها را داشته باشیم. برای این که effort برنامه نویسان از نوشتن موضوعات این چنینی برداشته شود و بر روی model کردن business گذاشته شود، این نیازمندی ها را بر روی پکیج هایی می گذاریم و به شکل قابل نصب در اختیار تیم های مختلف قرار می گیرد.نکته ای که در رابطه با framework وجود دارد این است که frameworkها را برای handle کردن business طراحی نمی کنیم. framworkها برای این موضوع طراحی می شوند که یکسری طراحی و نیازمندی های کلی را در آن ها ببینیم و تمرکز برنامه نویس را به سمت business و domain model ببریم.همچنین framework باید flexibility لازم را داشته باشد و برنامه نویس را مجبور به استفاده از یک چارچوب یا ابزار خاص نکند. از frameworkهای آماده ای که برای دات نت وجود دارند، می تونیم به ASP.NET Boilerplate به عنوان نمونه اشاره کنیم.