نظرات مطالب
معرفی فریم ورک Blueprint CSS
سلام و تشکر فراوان
به نظر بنده این CSS Framework یک مشکل کوچک دارد و اون محدودیت عرض هستش و رویکرد جدیدی که در طراحی وب سایت‌ها مطرح هستش رو در نظر نگرفتند. سایتهایی که محدودیت عرض ندارند و بنا به رزولوشن صفحه کاربر عرض اونا تغییر میکنه و در حقیقت فقط یک minwidth دارند.یه چیزی شبیه به فریم ورک YAML
آیا همچون طرح هایی شبیه به همین سایت   اشکالی دارند؟ یا این مساله به سلیقه بستگی دارد
ممنون
اشتراک‌ها
پروژه IdentityManager
یک وب اینترفیس سورس باز برای مدیریت ASP.NET Identity 
پروژه IdentityManager
مطالب
آشنایی با ساختار IIS قسمت نهم
در قسمت قبلی  ما یک هندلر ایجاد کردیم و درخواست‌هایی را که برای فایل jpg و به صورت GET ارسال میشد، هندل می‌کردیم و تگی را در گوشه‌ی تصویر درج و آن را در خروجی نمایش میدادیم. در این مقاله قصد داریم که کمی هندلر مورد نظر را توسعه دهیم و برای آن یک UI یا یک رابط کاربری ایجاد نماییم. برای توسعه دادن ماژولها و هندلر‌ها ما یک dll نوشته و باید آن را در GAC که مخفف عبارت Global Assembly Cache ریجستر کنیم.


جهت اینکار یک پروژه از نوع class library ایجاد کنید. فایل class1.cs را که به طور پیش فرض ایجاد می‌شود، حذف کنید و رفرنس‌های Microsoft.Web.Management.dll و Microsoft.Web.Administration.dll را از مسیر زیر اضافه کنید:
\Windows\system32\inetsrv
اولین رفرنس شامل کلاس‌هایی است که جهت ساخت ماژول‌ها برای کنسول IIS مورد نیاز است و دومی هم برای خواندن پیکربندی‌های نوشته شده مورد استفاده قرار می‌گیرد.
برای طراحی UI  بر پایه winform باید رفرنس‌های System.Windows.Forms.dll و System.Web.dll را از سری اسمبلی‌های دات نت نیز اضافه کنیم و در مرحله‌ی بعدی جهت ایجاد امضاء یا strong name (^  و  ^  ) به خاطر ثبت در GAC پروژه را انتخاب و وارد Properties پروژه شوید. در تب signing گزینه sign the assembly را تیک زده و در لیست باز شده گزینه new را انتخاب نمایید و نام  imageCopyrightUI را به آن نسبت داده و گزینه تعیین کلمه عبور را غیرفعال کنید و تایید و تمام. الان باید یک فایل snk مخفف strong name key ایجاد شده باشد تا بعدا با استفاده از این کلید dll ایجاد شده را در GAC ریجستر کنیم.

در مرحله بعدی در تب Build Events کد زیر را در بخش Post-build event command line اضافه کنید. این کد باعث می‌شود بعد از هر بار کامپایل پروژه، به طور خودکار در GAC ثبت شود:

call "%VS80COMNTOOLS%\vsvars32.bat" > NULL 
gacutil.exe /if "$(TargetPath)"

نکته:در صورتی که از VS2005 استفاده می‌کنید در تب Debug در قسمت Start External Program مسیر زیر را قرار بدهید. اینکار برای تست و دیباگینگ پروژه به شما کمک خواهد کرد. این تنظیم شامل نسخه‌های اکسپرس نمی‌شود.
 \windows\system32\inetsrv\inetmgr.exe

بعد از پایان اینکار پروژه را Rebuild کنید. با اینکار dll در GAC ثبت می‌شود. استفاده از سوییچ‌های if به طور همزمان در درستور gacutil به معنی این هست که اگر اولین بار است نصب می‌شود، پس با سوییچ i نصب کن. ولی اگر قبلا نصب شده است نسخه جدید را به هر صورتی هست جایگزین قبلی کن یا همان reinstall کن.
 
ساخت یک Module Provider
رابط‌های کاربری IIS همانند هسته و کل سیستمش، ماژولار و قابل خصوصی سازی است. رابط کاربری، مجموعه‌ای از ماژول هایی است که میتوان آن‌ها را حذف یا جایگزین کرد. تگ ورودی یا معرفی برای هر UI یک module provider است. خیلی خودمانی، تگ ماژول پروایدر به معرفی یک UI در IIS می‌پردازد. لیستی از module provider‌ها را می‌توان در فایل زیر در تگ بخش <modules> پیدا کرد.
%windir%\system32\inetsrv\Administration.config

در اولین گام یک کلاس را به اسم imageCopyrightUIModuleProvider.cs ایجاد کرده و سپس آن‌را به کد زیر، تغییر می‌دهیم. کد زیر با استفاده از ModuleDefinition  یک نام به تگ Module Provider داده و کلاس imageCopyrightUI را که بعدا تعریف می‌کنیم، به عنوان مدخل entry رابط کاربری معرفی کرده:

using System;
using System.Security;
using Microsoft.Web.Management.Server;
    
namespace IIS7Demos           
{
    class imageCopyrightUIProvider : ModuleProvider
    {
        public override Type ServiceType              
        {
            get { return null; }
        }

        public override ModuleDefinition GetModuleDefinition(IManagementContext context)
        {
            return new ModuleDefinition(Name, typeof(imageCopyrightUI).AssemblyQualifiedName);
        }

        public override bool SupportsScope(ManagementScope scope)
        {
            return true;
        }
    }            
}

با ارث بری از کلاس module provider، سه متد بازنویسی می‌شوند که یکی از آن ها SupportsScope هست که میدان عمل پروایدر را مشخص می‌کند، مانند اینکه این پرواید در چه میدانی باید کار کند که می‌تواند سه گزینه‌ی server,site,application باشد. در کد زیر مثلا میدان عمل application انتخاب شده است ولی در کد بالا با برگشت مستقیم true، همه‌ی میدان را جهت پشتیبانی از این پروایدر اعلام کردیم.

 public override bool SupportsScope(ManagementScope scope)
        {
            return (scope == ManagementScope.Application) ;
        }

حالا که پروایدر (معرف رابط کاربری به IIS) تامین شده، نیاز است قلب کار یعنی ماژول معرفی گردد. اصلی‌ترین متدی که باید از اینترفیس ماژول پیاده سازی شود متد initialize است. این متد جایی است که تمام عملیات در آن رخ می‌دهد. در کلاس زیر imageCopyrightUI ما به معرفی مدخل entry رابط کاربری می‌پردازیم. در سازنده‌های این متد، پارامترهای نام، صفحه رابط کاربری وتوضیحی در مورد آن است. تصویر کوچک و بزرگ جهت آیکن سازی (در صورت عدم تعریف آیکن، چرخ دنده نمایش داده می‌شود) و توصیف‌های بلندتر را نیز شامل می‌شود.

 internal class imageCopyrightUI : Module
    {
        protected override void Initialize(IServiceProvider serviceProvider, ModuleInfo moduleInfo)
        {            
            base.Initialize(serviceProvider, moduleInfo);
            IControlPanel controlPanel = (IControlPanel)GetService(typeof(IControlPanel));
            ModulePageInfo modulePageInfo = new ModulePageInfo(this, typeof(imageCopyrightUIPage), "Image Copyright", "Image Copyright",Resource1.Visual_Studio_2012,Resource1.Visual_Studio_2012);
            controlPanel.RegisterPage(modulePageInfo);
        }
    }

شیء ControlPanel مکانی است که قرار است آیکن ماژول نمایش داده شود. شکل زیر به خوبی نام همه قسمت‌ها را بر اساس نام کلاس و اینترفیس آن‌ها دسته بندی کرده است:

پس با تعریف این کلاس جدید ما روی صفحه‌ی کنترل پنل IIS، یک آیکن ساخته و صفحه‌ی رابط کاربری را به نام imageCopyrightUIPage، در آن ریجستر می‌کنیم. این کلاس را پایینتر شرح داده‌ایم. ولی قبل از آن اجازه بدهید تا انواع کلاس هایی را که برای ساخت صفحه کاربرد دارند، بررسی نماییم. در این مثال ما با استفاده از پایه‌ای‌ترین کلاس، ساده‌ترین نوع صفحه ممکن را خواهیم ساخت. 4 کلاس برای ساخت یک صفحه وجود دارند که بسته به سناریوی کاری، شما یکی را انتخاب می‌کنید.

 ModulePage   شامل اساسی‌ترین متدها و سورس‌ها شده و هیچگونه رابط کاری ویژه‌ای را در اختیار شما قرار نمی‌دهد. تنها یک صفحه‌ی خام به شما می‌دهد که می‌توانید از آن استفاده کرده یا حتی با ارث بری از آن، کلاس‌های جدیدتری را برای ساخت صفحات مختلف و ویژه‌تر بسازید. در حال حاضر که هیچ کدام از ویژگی‌های IIS فعلی از این کلاس برای ساخت رابط کاربری استفاده نکرده‌اند.
 ModuleDialogPage   یک صفحه شبیه به دیالوگ را ایجاد می‌کند و شامل دکمه‌های Apply و Cancel میشود به همراه یک سری متدهای اضافی‌تر که اجازه‌ی override کردن آنها را دارید. همچنین یک سری از کارهایی چون refresh  و از این دست عملیات خودکار را نیز انجام میدهد. از نمونه رابط‌هایی که از این صفحات استفاده می‌کنند میتوان  machine key و management service را اسم برد.
 ModulePropertiesPage   این صفحه یک رابط کاربری را شبیه پنجره property که در ویژوال استادیو وجود دارد، در دسترس شما قرار میدهد. تمام عناصر آن در یک حالت گرید grid لیست می‌شوند. از نمونه‌های موجود میتوان به CGI,ASP.Net Compilation اشاره کرد.
 ModuleListPage   این کلاس برای مواقعی کاربرد دارد که شما قرار است لیستی از آیتم‌ها را نشان دهید. در این صفحه شما یک ListView دارید که میتوانید عملیات جست و جو، گروه بندی و نحوه‌ی نمایش لیست را روی آن اعمال کنید.
در این مثال ما از اولین کلاس نامبرده که پایه‌ی همه کلاس هاست استفاده می‌کنیم. کد زیر را در کلاسی به اسم imageCopyrightUIPage  می‌نویسیم:
    public sealed class imageCopyrightUIPage : ModulePage
    {
        public string message;
        public bool featureenabled;
        public string color;

        ComboBox _colCombo = new ComboBox();
        TextBox _msgTB = new TextBox();
        CheckBox _enabledCB = new CheckBox();

        public imageCopyrightUIPage()
        {
            this.Initialize();
        }


        void Initialize()
        {
            Label crlabel = new Label();
            crlabel.Left = 50;
            crlabel.Top = 100;
            crlabel.AutoSize = true;
            crlabel.Text = "Enable Image Copyright:";
            _enabledCB.Text = "";
            _enabledCB.Left = 200;
            _enabledCB.Top = 100;
            _enabledCB.AutoSize = true;

            Label msglabel = new Label();
            msglabel.Left = 150;
            msglabel.Top = 130;
            msglabel.AutoSize = true;
            msglabel.Text = "Message:";
            _msgTB.Left = 200;
            _msgTB.Top = 130;
            _msgTB.Width = 200;
            _msgTB.Height = 50;

            Label collabel = new Label();
            collabel.Left = 160;
            collabel.Top = 160;
            collabel.AutoSize = true;
            collabel.Text = "Color:";
            _colCombo.Left = 200;
            _colCombo.Top = 160;
            _colCombo.Width = 50;
            _colCombo.Height = 90;
            _colCombo.Items.Add((object)"Yellow");
            _colCombo.Items.Add((object)"Blue");
            _colCombo.Items.Add((object)"Red");
            _colCombo.Items.Add((object)"White");

            Button apply = new Button();
            apply.Text = "Apply";
            apply.Click += new EventHandler(this.applyClick);
            apply.Left = 200;
            apply.AutoSize = true;
            apply.Top = 250;

            Controls.Add(crlabel);
            Controls.Add(_enabledCB);
            Controls.Add(collabel);
            Controls.Add(_colCombo);
            Controls.Add(msglabel);
            Controls.Add(_msgTB);
            Controls.Add(apply);
        }

        public void ReadConfig()
        {
            try
            {
                ServerManager mgr;
                ConfigurationSection section;
                mgr = new ServerManager();
                Configuration config =
                mgr.GetWebConfiguration(
                       Connection.ConfigurationPath.SiteName,
                       Connection.ConfigurationPath.ApplicationPath +
                       Connection.ConfigurationPath.FolderPath);

                section = config.GetSection("system.webServer/imageCopyright");
                color = (string)section.GetAttribute("color").Value;
                message = (string)section.GetAttribute("message").Value;
                featureenabled = (bool)section.GetAttribute("enabled").Value;

            }

            catch
            { }

        }
      
        void UpdateUI()
        {
            _enabledCB.Checked = featureenabled;
            int n = _colCombo.FindString(color, 0);
            _colCombo.SelectedIndex = n;
            _msgTB.Text = message;
        }


        protected override void OnActivated(bool initialActivation)
        {
            base.OnActivated(initialActivation);
            if (initialActivation)
            {
                ReadConfig();
                UpdateUI();
            }
        }



        private void applyClick(Object sender, EventArgs e)
        {
            try
            {
                UpdateVariables();
                ServerManager mgr;
                ConfigurationSection section;
                mgr = new ServerManager();
                Configuration config =
                mgr.GetWebConfiguration
                (
                       Connection.ConfigurationPath.SiteName,
                       Connection.ConfigurationPath.ApplicationPath +
                       Connection.ConfigurationPath.FolderPath
                );

                section = config.GetSection("system.webServer/imageCopyright");
                section.GetAttribute("color").Value = (object)color;
                section.GetAttribute("message").Value = (object)message;
                section.GetAttribute("enabled").Value = (object)featureenabled;

                mgr.CommitChanges();

            }

            catch
            { }

        }

        public void UpdateVariables()
        {
            featureenabled = _enabledCB.Checked;
            color = _colCombo.Text;
            message = _msgTB.Text;
        }
    }
اولین چیزی که در کلاس بالا صدا زده می‌شود، سازنده‌ی کلاس هست که ما در آن یک تابع تعریف کردیم به اسم initialize که به آماده سازی اینترفیس یا رابط کاربری می‌پردازد و کنترل‌ها را روی صفحه می‌چیند. این سه کنترل، یکی Combox برای تعیین رنگ، یک Checkbox برای فعال بودن ماژول و دیگری هم یک textbox جهت نوشتن متن است. مابقی هم که سه label برای نامگذاری اشیاست. بعد از اینکه کنترل‌ها روی صفحه درج شدند، لازم است که تنظیمات پیش فرض یا قبلی روی کنترل‌ها نمایش یابند که اینکار را به وسیله تابع readConfig انجام می‌دهیم و تنظیمات خوانده شده را در متغیر‌های عمومی قرار داده و با استفاده از تابع UpdateUI این اطلاعات را روی کنترل‌ها ست می‌کنیم و به این ترتیب UI به روز می‌شود. این دو تابع را به ترتیب پشت سر هم در یک متد به اسم OnActivated  که override کرده‌ایم صدا میزنیم. در واقع این متد یک جورایی همانند رویداد Load می‌باشد؛ اگر true برگرداند اولین فعال سازی رابط کاربری بعد از باز شدن IIS است و در غیر این صورت false بر میگرداند.

در صورتی که کاربر مقادیر را تغییر دهد و روی گزینه apply کلیک کند تابع applyClick اجرا شده و ابتدا به تابع UpdateVariables ارجاع داده میشود که در آن مقادیر خوانده شده و در متغیرهای Global قرار می‌گیرند و سپس با استفاده از دو شیء از نوع serverManger و ConfigSection جایگذاری یا ذخیره می‌شوند.
استفاده از دو کلاس Servermanager و Configsection در دو قسمت خواندن و نوشتن مقادیر به کار رفته‌اند. کلاس servermanager به ما اجازه دسترسی به تنظیمات IIS و قابلیت‌های آن را میدهد. در تابع ReadConfig مسیر وب سایتی را که در لیست IIS انتخاب شده است، دریافت کرده و به وب کانفیگ آن وب سایت رجوع نموده و تگ imageCopyright آن را که در تگ system.webserver قرار گرفته است، میخواند (در صورتی که این تگ در آن وب کانفیگ موجود نباشد، خواندن و سپس ذخیره مجدد آن روی تگ داخل فایل applicationHost.config اتفاق میفتد که نتیجتا برای همه‌ی وب سایت هایی که این تگ را ندارند یا مقدارهای پیش فرض آن را تغییر نداده‌اند رخ میدهد) عملیات نوشتن هم مشابه خواندن است. تنها باید خط زیر را در آخر برای اعمال تغییرات نوشت؛ مثل EF با گزینه Context.SaveChanges:
mgr.CommitChanges();
وقت آن است که رابط کاربری را به IIS اضافه کنیم: پروژه را Rebuild کنید. بعد از آن با خطوطی که قبلا در Post-Build Command نوشتیم باید dll ما در GAC ریجستر شود. برای همین آدرس زیر را در cmd تایپ کنید:
%vs110comntools%\vsvars32.bat
عبارت اول که مسیر ویژوال استودیوی  شماست و عدد 110 یعنی نسخه‌ی 11. هر نسخه‌ای را که استفاده میکنید، یک صفر جلویش بگذارید و جایگزین عدد بالا کنید. مثلا نسخه 8 می‌شود 80 و فایل بچ بالا هم دستورات visual studio را برای شما آزاد می‌کند.
سپس دستور زیر را وارد کنید:
GACUTIL /l ClassLibrary1
کلمه classLibrary1 نام پروژه‌ی ما بود که در GAC ریجستر شده است. با سوییچ l تمامی اطلاعات اسمبلی‌هایی که در GAC ریجستر شده‌اند، نمایش می‌یابند. ولی اگر اسم آن اسمبلی را جلویش بنویسید، فقط اطلاعات آن اسمبلی نمایش میابد. با اجرای خط فوق میتوانیم کلید عمومی public key اسمبلی خود را بدانیم که در شکل زیر مشخص شده است:

پس اگر کلید را دریافت کرده‌اید، خط زیر را به فایل administration.config در تگ <ModuleProviders> اضافه کنید:
<add name="imageCopyrightUI" type="ClassLibrary1.imageCopyrightUIProvider,   ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d0b3b3b2aa8ea14b"/>
عبارت ClassLibrary1.imageCopyrightUIProvider به کلاس imageCopyrightUIProvider اشاره می‌کند که در این کلاس UI معرفی می‌شود. مابقی عبارت هم کاملا مشخص است و در لینک‌های بالا در مورد Strong name توضیح داده شده اند. 
فایل administration.config  در مسیر زیر قرار دارد:
%windir%\system32\inetsrv\config\administration.config
حالا تنها کاری که نیاز است، باز کردن IIS است. به بخش وب سایت‌ها رفته و اپلیکیشنی که قبلا با نام mypictures را ایجاد کرده بودیم، انتخاب کنید. در سمت راست، آخر لیست، بخش others باید ماژول ما دیده شود. بازش کنید و تنظمیات آن را تغییر دهید و حالا یک تصویر را از اپلیکیشن mypictures، روی مرورگر درخواست کنید تا تغییرات را روی تگ مشاهده کنید:

 
حالا دیگر باید ماژول نویسی برای IIS را فراگرفته باشیم. این ماژول‌ها می‌توانند از یک مورد ساده تا یک کلاس مهم و امنیتی باشند که روی سرور شما برای همه یا بعضی از وب سایت‌ها در حال اجرا هستند و در صورت لزوم و اجازه شما، برنامه نویس‌ها میتوانند مثل همه‌ی تگ‌های موجود در وب کانفیگ سایتی را که  مینویسند، تگ ماژول شما و  تنظیمات آن را با استفاده از attribute یا خصوصیت‌های تعریف شده، بر اساس سلایق و نیازهایشان تغییر دهند و روی سرور شما آپلود کنند. الان شما یک سرور خصوصی سازی شده دارید.
از آنجا که این مقاله طولانی شده است، باقی موارد ویرایشی روی این UI را در مقاله بعدی بررسی خواهیم کرد. 
نظرات مطالب
اجرای وظایف زمان بندی شده با Quartz.NET - قسمت دوم
البته نگفتید که منظورتون desktop یا وب هست، با این فرض که در مورد desktop می‌پرسید، یکی از روش‌ها اینه که یک متغیر عمومی تعریف کنید که ارجاعی به فرمی که قرار هست آپدیت باشه داشته باشه:
using System.Windows.Forms;

public static class GlobalData
{
    public static Form ScheduleForm { get; set; }
}
در سازنده‌ی فرم می‌تونید اون رو به فرم جاری مقداردهی کنید:
GlobalData.ScheduleForm = this;
با این فرض که قرار هست عنوان یک دکمه در فرم با نام myButton به My Text تغییر کنه، کلاس پیاده ساز اینترفیس IJob به صورت زیر خواهد بود.
namespace SchedulerDemo.Jobs
{
    using System.Linq;
    using System.Windows.Forms;
    using Quartz;

    public class HelloJob : IJob
    {
        private delegate void ButtonTextWriter(string buttonId, string text);
        
        MainForm form = GlobalData.ScheduleForm as MainForm;

        private void SetButtonText(string buttonId, string text)
        {
            (form.Controls.Find(buttonId, true).FirstOrDefault() as Button).Text = text;
        }

        public void Execute(IJobExecutionContext context)
        {
            form.BeginInvoke(new ButtonTextWriter(SetButtonText), new object[] { "myButton", "My Text" });
        }
    }
}

مطالب
اصول طراحی شیء گرا: OO Design Principles - قسمت پنجم

دانای اطلاعات ( Information Expert )

بر طبق این اصل می­توان برای واگذاری هر مسئولیت، کلاسی را انتخاب کرد که بیشترین اطلاعات را در مورد انجام آن در اختیار دارد و لذا نیاز کمتری به ایجاد ارتباط با دیگر مولفه‌ها خواهد داشت.

در مثال زیر مشاهده میکنید که کلاس User، اطلاعات کاملی را از عملیات اضافه کردن آیتمی را به لیست خرید و تسویه حساب، ندارد و پیاده سازی این عملیات در این کلاس، نیاز به ایجاد وابستگی‌های پیچیده‌ای دارد. 

public class User
    {
        public ShoppingCart ShoppingCart { get; set; }
        public void AddItem(string name) {
            // User class must know how to create OrderItem
            var item = new OrderItem() { Name = name };

            // User class must know how to add item to shopping cart
            ShoppingCart.Items.Add(item);
            
        }
        public void CheckOut() {
            // User class must know logic behind cost and discount calculations:
            // check for discount
            // check shipping method
            // check promotions
            // calculate total cost of items 
        }
    }
    public class OrderItem
    {
        public int Id { get; set; }
        public string Name { get; set; }

    }
    public class ShoppingCart
    {
        public int Id { get; set; }
        public List<OrderItem> Items { get; set; }
       
    }

بنابراین به جای این طراحی، مسئولیت‌ها را به ShoppingCart منتقل میکنیم:

 public class User
    {
        public ShoppingCart ShoppingCart { get; set; }
        
    }
    public class OrderItem
    {
        public int Id { get; set; }
        public string Name { get; set; }

    }
    public class ShoppingCart
    {
        public int Id { get; set; }
        public List<OrderItem> Items { get; set; }
        public void AddItem(string name)
        {
            // ShoppingCart class know how to create OrderItem
            var item = new OrderItem() { Name = name };

            // ShoppingCart class already know how to add item 
            Items.Add(item);

        }
        public void CheckOut()
        {
            // ShoppingCart class know logic behind cost and discount calculations:
            // check for discount
            // check shipping method
            // check promotions
            // calculate total cost of items 
        }
    }


اتصال ضعیف ( Low Coupling )

با اتصال ضعیف نیز که از ویژگی‌های یک طراحی خوب است آشنا هستیم. هر چه تعداد و نوع اتصال بین مولفه‌ها کم‌تر و ضعیف‌تر باشد، اعمال تغییرات راحت‌تر صورت خواهد گرفت. طراحی با اتصال مناسب سه ویژگی را دارد:

-  وابستگی بین کلاس‌ها کم است.

-  تغییرات در یک کلاس، اثر کمی بر دیگر کلاس‌ها دارد.

-  پتانسیل استفاده‌ی مجدد از مؤلفه‌ها بالا است.

چنانچه قبلا هم اشاره کردم، نوشتن نرم افزاری بدون اتصال، ممکن نیست و باید مؤلفه‌ها با هم همکاری کرده و وظایف را انجام دهند. با این حال میتوان نوع اتصالات و تعداد آنرا بهبود بخشید.


چند ریختی ( Polymorphism )

چند ریختی که از ویژگی‌های اساسی برنامه نویسی و زبان‌های شیء گراست، به منظور بالا بردن قابلیت استفاده‌ی مجدد، استفاده میشود. بر طبق این اصل، مسئولیت تعریف رفتارهای وابسته به نوع کلاس (زیرنوع‌ها در روابط ارث بری) باید به کلاسی واگذار شود که تغییر رفتار در آن اتفاق می­افتد. به عبارت دیگر باید به صورت خودکار رفتار را بر اساس نوع کلاس تصحیح کنیم. این روش در مقابل بررسی نوع داده‌ای برای انجام رفتار مناسب می­باشد.

به عنوان مثال اگر کلاس‌های چهار ضلعی، مربع، مستطیل و ذوزنقه را داشته باشیم، برای پیاده سازی مساحت در کلاس چهار ضلعی، طول را در عرض ضرب میکنیم. با این حال نوع رفتار مساحت ذوزنقه متفاوت از دیگران است. طبق این اصل، برای اعمال کردن این تغییر، فقط خود کلاس ذوزنقه باید رفتار مربوطه را پیاده سازی کند و هیچ منطق و کدی نباید برای چک کردن نوع کلاس استفاده گردد.

public class ShapeWithoutPolymorphism
    {
        public double X { get; set; }
        public double Y { get; set; }
        public double Z { get; set; }
        public double Area(string shapeType)
        {
            switch (shapeType)
            {
                case "square":
                    return X * Y;
                case "rectangle":
                    return X * Y;
                case "trapze":
                    return (X + Z) * Y / 2;
                default:
                    return 0;
            }
        }
    }

با استفاده از چندریختی، طراحی به این صورت در خواهد آمد:

  public abstract class Shape
    {
        public double X { get; set; }
        public double Y { get; set; }
        public virtual double Area()
        {
            return X * Y;
        }
    }
    public class Rectangle : Shape
    {
        // No need to override
    }
    public class Square : Shape
    {
        // No need to override
    }
    public class Trapze : Shape
    {
        public double Z { get; set; }
        public override double Area()
        {
            return (X + Z) * Y / 2;

        }
    }


مصنوع خالص ( Pure Fabrication )

مصنوع خالص کلاسی است که در دامنه مساله وجود ندارد و به منظور کاهش اتصال، افزایش انسجام و افزایش امکان استفاده مجدد کد ایجاد میشود. سرویس‌ها را میتوان از این دسته نامید. کلاس‌هایی که دقیقا برای کاهش اتصال، افزایش انسجام و افزایش امکان استفاده مجدد کد استفاده میگردند. سرویس‌ها عملیاتی تکراری هستند که توسط مولفه‌های دیگر استفاده میشوند. اگر سرویس‌ها وجود نداشتند هر مولفه‌ای میبایست عملیات را در درون خود پیاده سازی میکرد که این هم باعث افزایش حجم کد و هم باعث کابوس شدن اعمال تغییرات میشد.

برای تشخیص زمان استفاده از این اصل میتوان گفت زمانیکه رفتاری را نمیدانیم به کدام کلاس واگذار کنیم، کلاس جدیدی را ایجاد میکنیم. در اینجا بجای آنکه به زور مسئولیتی را به کلاس نامربوطی بچسبانیم، آنرا به کلاس جدیدی که فقط رفتاری را دارد، منتقل میکنیم. با اینکار انسجام کلاس‌ها را حفظ کرده‌ایم و هیچ اشکالی ندارد که کلاسی بدون داده بوده و فقط متد داشته باشد. اگر به یاد داشته باشید، در اصل واسطه گری (Indirection ) کلاس جدیدی برای ایجاد ارتباط ساختیم. در حقیقت مسئولیت برقراری ارتباط بین مؤلفه‌ها را به کلاس دیگری واگذار کردیم که چنانچه میبینید، بدون آنکه بدانیم، برای حل مشکل از اصل مصنوع خالص استفاده کردیم.

در مثال زیر این مساله مشهود است:

public class User
    {
        public int Id { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
    }

    public class LibraryManagement
    {
        public User CurrentUser { get; set; }
        public void AddBookToLibrary(int bookId)
        {
            // check for CurrentUser authority:
            // not user's responsibility nor LibraryManagement
        }
        public void RearrangeBook(int bookId, int shelfId)
        {
            // check for CurrentUser authority
            // not user's responsibility nor LibraryManagement
        }
    }
    public class UserManagement
    {
        public User CurrentUser { get; set; }
        public void AddUser(string name)
        {
            // check for CurrentUser authority:
            // not user's responsibility nor UserManagement
        }
        public void ChangeUserRole(int userId, int roleId)
        {
            // check for CurrentUser authority
            // not user's responsibility nor UserManagement
        }
    }
    public class AuthorizationService
    {
        public bool IsAuthorized(int userId, int roleId)
        {
            // get user roles from data base
            // return true if user has the authority
        }
    }

عملیات بررسی مجوز‌ها باید در کلاس جدیدی به نام AuthorizationService ارائه شود. بدین صورت تمام قسمت‌ها، از این کد بدون وابستگی اضافی میتوانند استفاده کنند.


حفاظت از تاثیر تغییرات ( Protected Variations )

این اصل میگوید که کلاس‌ها باید از تغییرات یکدیگر مصون بمانند. در واقع این اصل غایت یک طراحی خوب است. تمام اصولی را که تا به حال بررسی کرده‌ایم، به منظور دستیابی به چنین رفتاری از طراحی بوده‌است. بدین منظور باید از اصول Open/Closed برای واسط­ها، چند ریختی در توارث و ... استفاده کرد تا از تاثیرات زنجیره‌ای تغییرات در امان بمانیم.

مطالب
اصول طراحی شیء گرا: OO Design Principles - قسمت سوم

اصل هفتم: Liskove Substitution Principle

"ارث بری باید به صورتی باشد که زیر نوع را بتوان بجای ابر نوع استفاده کرد"

این اصل می‌گوید اگر قرار است از ارث بری استفاده شود، نحوه‌ی استفاده باید بدین گونه باشد که اگر یک شیء از کلاس والد ( Base-Parent-Super type ) داشته باشیم، باید بتوان آن را با شیء کلاس فرزند ( Sub Type-Child ) بدون هیچ گونه تغییری در منطق کد استفاده کننده از شیء مورد نظر، تغییر داد. به زبان ساده باید بتوان شیء فرزند را جایگزین شیء والد کرد.

نکته مهم: این اصل در مورد عکس این رابطه صحبتی نمی‌کند و دلیل آن هم منطق طراحی می‌باشد. تصور کنید که شیء ای داشته باشید که از یک کلاس والد، ارث برده باشد. نوشتن  کدی که شیء والد را بتوان جایگزین شیء فرزند کرد، بسیار سخت است؛ چرا که منطق متکی بر کلاس فرزند بسیار وابسته به جزییات کلاس فرزند است. در غیر این صورت وجود شیء فرزند، کم اهمیت میباشد.

با رعایت این اصل، میتوانیم در مواقعی که شروط مرتبط با کلاس فرزند را نداریم و یک سری منطق و قیود کلی مرتبط با کلاس والد را داریم، از شیء کلاس والد استفاده نماییم و وظیفه  نمونه گیری (instantiation ) آن را به یک کلاس دیگر محول کنیم. به مثال زیر توجه کنید:

 public class Parent
    {
        public string Name { get; set; }
        public int X { get; set; }
        public int Y { get; set; }
        public Parent()
        {
            X = Y = 0;
        }
        public virtual void Move()
        {
            X += 5;
            Y += 5;
        }
        public void Shoot() { }
        public virtual void Pass() { } 
    }
    public class Child1 : Parent
    {
        public override void Move()
        {
            X += 10;
            Y += 10;
        }
    }
    public class Child2 : Parent
    {
        public override void Move()
        {
            X += 20;
            Y += 20;
        }
    }
    public enum State
    {
        Start,
        Move,
        Shoot,
        Pass
    }
    public class Creator
    {
        public static Parent GetInstance(bool? condition)
        {
            if (condition == null)
            {
                return new Parent();
            }
            if (condition == true)
            {
                return new Child1();
            }
            else
            {
                return new Child2();
            }
        }
    }
    public class Context
    {
        public void SetState(ref State s)
        {
            s = State.Move;
        }
        public void Main()
        {
            State state =State.Start;
            
            // در مورد نوع این شیء چیزی نمیدانیم و وابسته به شرایط نوع آن متغیر است
            // در حقیقت شیء کلاس فرزند را جای شیء کلاس والد  قرار می‌دهیم و نه بالعکس

            Parent obj = Creator.GetInstance(null);
            
            // منطق برنامه وضعیت را تغییر می‌دهد
            SetState(ref state);

            // قواعد کلی و عمومی که بدون در نظر گرفتن کلاس (نوع) شیء بر آن اعمال می‌شود
            switch (state)
            {
                case State.Move:
                    obj.Move();
                    break;
                case State.Shoot:
                    obj.Shoot();
                    break;
                case State.Pass:
                    obj.Pass();
                    break;
                default:
                    break;
            }           
        }
    }

همانطور که در کدها نیز توضیح داده‌ام، کلاس‌های فرزند را جایگزین کلاس والد کرده‌ایم. اگر می‌خواستیم عکس رابطه را (شیء والد را به شیء فرزند انتقال دهیم) اعمال کنیم باید تغییر زیر را ایجاد میکردیم که با خطا روبرو خواهد شد:

Child1 obj = Creator.GetInstance(null);



اصل هشتم: Interface segregation

"واسط‌های کوچک بهتر از واسط‌های حجیم است"

این اصل به ما می‌گوید در تعریف واسط‌های متعدد خساست به خرج ندهیم و بجای آنکه یک واسط اصلی با وظیفه‌های بسیار داشته باشیم، بهتر است واسط‌های متعددی با وظیفه‌های کمتر داشته باشیم. برای درک این اصل ساده به عقب برمیگردیم، جایی که نیاز به واسط را توضیح دادیم. واسط، نقش تعریف پروتکل را دارد. اگر قرار باشد واسطی بزرگ با چندین مسئولیت داشته باشیم، آنگاه تعریف مستحکمی را از وظیفه‌ی واسط ارائه نداده‌ایم. لذا هر کلاس پیاده ساز این واسط، برخی وظیفه‌هایی را که نیاز به آن ندارد، باید تعریف و پیاده سازی کند. به مثال زیر نگاه کنید:

 public interface IHuman
    {
        void Move();
        void Eat();
        void LevelUp();
        void FireBullet();
    }
    public class Player : IHuman
    {
        public void Eat() { }
        public void FireBullet() { }
        public void LevelUp() { }
        public void Move() { }
    }
    public class Enemy : IHuman
    {
        public void Eat() { }
        public void FireBullet() { }
        public void LevelUp() { }
        public void Move() { }
    }
    public class Citizen : IHuman
    {
        public void Eat() { }
        public void FireBullet() { }
        public void LevelUp() { }
        public void Move() { }
    }

در این مثال که مربوط به مدل یک بازی با نقش‌های بازیکن، دشمن و شهروند (بی گناه!) است، طراحی به گونه‌ای است که دشمن و شهروند، توابعی را که نیاز ندارند، باید پیاده سازی کنند. در دشمن: Eat(), LevelUp() و در شهروند: Eat(), LevelUp(), FireBullet() . لذا واسط IHuman یک واسط کلی با وظیفه‌های متعدد است.

در مدل بهبود یافته که کلاس‌ها با پسوند Better بازنویسی شده‌اند داریم:

public interface IMovable { void Move(); }
    public interface IEatable { void Eat(); }
    public interface IPlayer { void LevelUp(); }
    public interface IShooter { void FireBullet(); }
    public class PlayerBetter : IPlayer, IMovable, IEatable, IShooter
    {
        public void Eat() { }
        public void FireBullet() { }
        public void LevelUp() { }
        public void Move() { }
    }
    public class EnemyBetter : IMovable, IShooter
    {
        public void FireBullet() { }
        public void Move() { }
    }
    public class CitizenBetter : IMovable
    {
        public void Move() { }
    }

در اینجا برای هر وظیفه یک واسط تعریف کرده ایم که باعث قوی شدن معنای هر واسط می‌شود.



اصل نهم: Dependency inversion

"وابستگی بین ماژول‌ها  را به وابستگی آن‌ها به انتزاع (واسط) تغییر بده"

این اصل که نمود آن را در الگو‌های طراحی dependency injection و factory میبینیم، میگوید که ماژول‌های بالادست (ماژول استفاده کننده ماژول پایین دست) به جای آنکه ارجاع مستقیمی را به ماژول‌های پایین دست داشته باشند، به انتزاعی (واسط) ارجاع بدهند که ماژول پایین دست آنرا پیاده سازی می‌کند یا به ارث میبرد. در واقع این اصل برای از بین بردن وابستگی قوی بین ماژول‌های بالا دست و پایین دست، به میدان آمده است. دو حکم اصلی از این اصل بر می‌آید:

الف – ماژول‌های بالا دست نباید وابسته به ماژول‌های پایین دست باشند. هر دو باید وابسته به انتزاع (واسط) باشند. وابستگی ماژول بالا دست از نوع ارجاع و وابستگی ماژول پایین دست از نوع ارث بری است.

ب – انتزاع نباید وابسته به جزییات باشد، بلکه جزییات باید وابسته به انتزاع باشد. یعنی در پیاده سازی منطق برنامه (که جزییات محسوب می‌شود) باید از واسط‌ها یا کلاس‌های انتزاعی استفاده کنیم و همچنین در نوشتن کلاس‌های انتزاعی نباید هیچ گونه ارجاعی را به کلاس‌های جزیی داشته باشیم.

در مثال زیر با نمونه‌ای از طراحی ناقض این اصل روبرو هستیم:

public class Controller
    {
        public Service Service { get; set; }
        public Controller()
        {
            // کنترلر باید نحوه نمونه گیری را بداند (ورودی‌های لازم) و این از وظایف آن خارج است
            Service = new Service(1);
        }
        public void DoWork()
        {
            Service.RunService();
        }
    }

    public class Service
    {
        public int State { get; set; }
        public Service(int s)
        {
            State = s;
        }
        public void RunService() { }
    }

در این مثال کلاس کنترلر، ماژول بالادست و کلاس سرویس، ماژول پایین دست محسوب میگردد. در ادامه طراحی مطلوب را نیز ارائه داده‌ام:

public class ControllerBetter
    {
        // ارجاع به واسط باعث انعطاف و کاهش وابستگی شده است
        public IService Service { get; set; }
        public ControllerBetter(IService service)
        {
            // یک کلاس دیگر وظیفه ارسال سرویس به سازنده کلاس کنترلر را دارد 
            // و مسئولیت نمونه گیری را از دوش کنترلر برداشته است
            Service = service;
        }
        public void DoWork()
        {
            Service.RunService();
        }
    }
    // کاهش وابستگی با تعریف واسط و تغییر وابستگی مستقیم بین کنترلر و سرویس
    public interface IService
    {
        void RunService();
    }
    // وابستگی جزییات به انتزاع
    public class ServiceBetter : IService
    {
        public int State { get; set; }
        public ServiceBetter(int s)
        {
            State = s;
        }
        public void RunService() { }
    }

نحوه بهبود طراحی را در توضیحات داخل کد مشاهده میکنید. در مقاله بعدی به اصول GRASP خواهم پرداخت. 

نظرات اشتراک‌ها
10 دلیل برای اینکه برنامه نویس‌های وب باید AngularJS را فرا بگیرند
مسلما نیاز به تیم قوی‌تری نسبت به روش‌های متداول توسعه برنامه‌های وب دارد. دانش بالاتری از جاوا اسکریپت را نیاز دارد. به علت تازگی، دانش فنی چندانی از آن نسبت به سایر روش‌ها منتشر نشده‌است و تعداد کمتری به آن مشغول هستند.
نظرات مشابهی در این مورد:
SPA سخت است و همیشه خواهد بود!
مالیات SPA