مقدمه :
زمانیکه هدفمان تولید سامانهی نرم افزاری باشد که تعداد بسیار زیادی از کاربران با آن سرو کار دارند و اتفاقاً این سامانه قرار است عملیات بسیار حساسی (نظیر عملیات بانکی و مالی، مخابراتی و ...) را انجام دهد و عدم سرویس دهی مناسب آن قابل تحمل نبوده و باعث خسارات مالی، نارضایتی و ... گردد میبایست از روشهای خاصی برای توسعهی این گونه سیستمها استفاده نمود. این نرم افزارها برای اینکه بتوانند به تعداد درخواستهای بسیار زیاد همزمان پاسخگو باشند و سرویس خود را با کیفیت مناسب ارائه دهند، میبایست دارای ویژگیهای خاصی نظیر مقیاس پذیری (scalable) و تحمل پذیری در مقابل خطا (fault tolerance) باشند.
خوب حالا که صورت مسئله مشخص شد، یکی از راه حلهای موجود را که مدل Actor Based است، بررسی میکنیم.
مدل Actor Based یکـی از مـدل هـای اسـتاندارد بـرای توسـعهی نـرم افزارهـایی بـا قابلیـت اطمینـان بسـیار بالا، تحمل پذیر درمقابـل خطـا و پاسـخ دهـی بسـیار سـریع مـی باشـد. در ایـن مـدل، وظـایف نـرم افـزار بـه مجموعــهای از Actor هــا تقســیم (توزیع) گردیـده و هــر یــک از Actor هــا بــه صــورتی کــاملاً ایزولـه، در نــخ(thread) خـاص خودشـان اجـرا شـده و بخشـی از وظـایف سیسـتم را انجـام مـی دهنـد. سـپس بـا اتصـال Actor هـا بـه یکـدیگر، یـک خـط لولـه (Pipeline) تشـکیل شـده و بـا اسـتفاده از مکـانیزم هـای ارسـال و دریافت پیـام، امکـان همکـاری و برقـراری ارتبـاط بـین Actor هـا فـراهم شـده و در نتیجـه وظیفـهی اصـلی و کلی نرم افـزار بـا حرکـت در یـک خـط لولـه و عبـور از Actor هـای مختلـف بـه صـورت مـوازی و همزمـان انجام خواهد شد. با توجه بـه اینکـه هـر یـک از پیـامهـای وارده بـه یـک Actor در یـک thread جداگانـه اجـرا مـیشـود، امکــان اینکــه در یــک لحظــه چنــدین Thread در یــک Actor در حــال اجــرا باشـنـد و جــود دارد و درنتیجه باید مکانیزمهایی وجود داشته باشد کـه تضـمین کنـد پیـام هـای وارد شـده بـه خـط لولـه، بـه ترتیـب معین شده، اجـرا و از خط لوله خارج میشوند.
در این مدل هر یک از Actor هـا مـی تواننـد بـه صـورت توزیـع شـده و بـر روی سـروری مجزا اجـرا شـوند. خوشـبختانه فریمـورک هـای متفـاوت و بسـیار قـوی جهـت توسـعه بـه روش Actor Base وجـود دارند؛ بـه عنـوان مثال TPL DataFlow در Net. یکـی از نمونههای ساده آن بـوده کـه در سـال 2012 توسط Microsoft معرفـی شـد و Akka هم یک نمونهی بسیار پختهتر و در بستر جاوا مطرح میباشد که پیاده سازی دات نتی آن هم با نام Akka.net موجود است. Erlang نیز محصول Ericsson بوده و دنیای خاص خود را دارد.
در این روش وظیفه توسعه دهنده این است که اولاً یک خط لوله از اکتورها را تشکیل داده (کانفیگ) و یک عمل بزرگ را به چندین عمل کوچکتر تقسیم نموده و هر کدام را به یک اکتور جهت اجرا ارسال نماید. تصویر نمونه زیر یک خط لوله متشکل از 4 اکتور را نشان میدهد که از طریق ارسال پیام با یکدیگر در ارتباط هستند تا با همکاری یکدیگر عملی را انجام دهند. این ساختار، Pipeline یا خط لوله نامیده میشود.
در قسمت بعدی با جزئیات بیشتر و با نمونههای عملی این روش را بررسی میکنیم.
در نرم افزارهای تحت ویندوز روشها و سلیقههای متفاوتی برای چینش فرمها ، منوها و دیگر اجزای برنامه وجود دارد. در یک نرم افزار اتوماسیون اداری که فرمهای ورود اطلاعات زیادی دارد فضای کافی برای نمایش همهی فرمها به کاربر نیست. یکی از روش هایی که میتواند به کار رود تقسیم قسمتهای مختلف نرم افزار در Viewهای جداگانه است. این کار استفادهی مجدد از قسمتهای مختلف و نگهداری کد را سهولت میبخشد.
الگوی متداولی که در نرم افزارهای WPF و Silverlight استفاده میشود الگوی MVVM است. (این الگو در جاوااسکریپت هم به سبب Statefull بودن استفاده میشود.) قبلا مطالب زیادی در این سایت جهت آموزش و توضیح این الگوی منتشر شده است.
فرض کنید نرم افزار از چند بخش تشکیل شده :
- صفحهی اصلی
- منو
- یک صفحهی خوش آمدگویی
- صفحهی ورود و نمایش اطلاعات
می توان اجزا و تعریف هر یک از این قسمتها را در یک UserControl قرار داد و در زمان مناسب آن را بارگذاری کرد.
سوالی که مطرح است بارگذاری UserControlها به کمک الگوی MVVM چگونه است ؟
کدهای XAML صفحهی اصلی :
<Window x:Class="TwoViews.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="MVVM Light View Switching" d:DesignHeight="300" d:DesignWidth="300" DataContext="{Binding Main, Source={StaticResource Locator}}" ResizeMode="NoResize" SizeToContent="WidthAndHeight" mc:Ignorable="d"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <ContentControl Content="{Binding CurrentViewModel}" /> <DockPanel Grid.Row="1" Margin="5"> <Button Width="75" Height="23" Command="{Binding SecondViewCommand}" Content="Second View" DockPanel.Dock="Right" /> <Button Width="75" Height="23" Command="{Binding FirstViewCommand}" Content="First View" DockPanel.Dock="Left" /> </DockPanel> </Grid> </Window>
2 دکمه در صفحهی اصلی وجود دارد ، یکی از آنها وظیفهی بارگذاری View اول و دیگری وظیفهی بارگذاری View دوم را دارد ، این دکمهها نقش منو را در یک نرم افزار واقعی به عهده دارند.
کدهای View-Model گره خورده (به کمک الگوی ViewModolLocator ) به View اصلی :
/// This is our MainViewModel that is tied to the MainWindow via the /// ViewModelLocator class. /// </summary> public class MainViewModel : ViewModelBase { /// <summary> /// Static instance of one of the ViewModels. /// </summary> private static readonly SecondViewModel SecondViewModel = new SecondViewModel(); /// <summary> /// Static instance of one of the ViewModels. /// </summary> private static readonly FirstViewModel FirstViewModel = new FirstViewModel(); /// <summary> /// The current view. /// </summary> private ViewModelBase _currentViewModel; /// <summary> /// Default constructor. We set the initial view-model to 'FirstViewModel'. /// We also associate the commands with their execution actions. /// </summary> public MainViewModel() { CurrentViewModel = FirstViewModel; FirstViewCommand = new RelayCommand(ExecuteFirstViewCommand); SecondViewCommand = new RelayCommand(ExecuteSecondViewCommand); } /// <summary> /// The CurrentView property. The setter is private since only this /// class can change the view via a command. If the View is changed, /// we need to raise a property changed event (via INPC). /// </summary> public ViewModelBase CurrentViewModel { get { return _currentViewModel; } set { if (_currentViewModel == value) return; _currentViewModel = value; RaisePropertyChanged("CurrentViewModel"); } } /// <summary> /// Simple property to hold the 'FirstViewCommand' - when executed /// it will change the current view to the 'FirstView' /// </summary> public ICommand FirstViewCommand { get; private set; } /// <summary> /// Simple property to hold the 'SecondViewCommand' - when executed /// it will change the current view to the 'SecondView' /// </summary> public ICommand SecondViewCommand { get; private set; } /// <summary> /// Set the CurrentViewModel to 'FirstViewModel' /// </summary> private void ExecuteFirstViewCommand() { CurrentViewModel = FirstViewModel; } /// <summary> /// Set the CurrentViewModel to 'SecondViewModel' /// </summary> private void ExecuteSecondViewCommand() { CurrentViewModel = SecondViewModel; } }
این ViewModel از کلاس پایهی چارچوب MVVM Light مشتق شده است. Commandها جهت Handle کردن کلیک دکمهها هستند . نکتهی اصلی این ViewModel پراپرتی CurrentViewModel میباشد. این پراپرتی به ویژگی Content کنترل ContentControl مقید (Bind) شده است. با کلیک شدن روی دکمهها View مورد نظر به کاربر نمایش داده میشود.
WPF از کجا میداند کدام View را به ازای ViewModel خاص render کند ؟
در فایل App.xaml یک سری DataTemplate تعریف شده است :
<Application.Resources> <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" /> <!-- We define the data templates here so we can apply them across the entire application. The data template just says that if our data type is of a particular view-model type, then render the appropriate view. The framework takes care of this dynamically. Note that the DataContext for the underlying view is already set at this point, so the view (UserControl), doesn't need to have it's DataContext set directly. --> <DataTemplate DataType="{x:Type vm:SecondViewModel}"> <views:SecondView /> </DataTemplate> <DataTemplate DataType="{x:Type vm:FirstViewModel}"> <views:FirstView /> </DataTemplate> </Application.Resources>
به کمک این DataTemplateها مشخص شده اگر نوع دادهی ما از یک نوع View-Model خاص میباشد View مناسب را به ازای آن Render کند. با تعریف DataTemplateها در App.Xaml میتوان از آنها در سطح نرم افزار استفاده کرد. میتوان DataTemplateها را جهت خلوت کردن App.xaml به Resource دیگری انتقال داد.
دریافت مثال : TwoViews.zip
زمانیکه کلاسی، دو یا چند کار را انجام میدهد، بهتر است این امور در کلاسهای مجزایی انجام شوند. راه اصلی این کار، بازسازی کد استخراج کلاس است. ایده اصلی این بازسازی کد با ساختن کلاسی جدید و انتقال خصوصیتها، فیلدها و متدهای مورد نظر به آن انجام میشود.
کلاسها معمولا از ابتدا به صورت چند وظیفهای و پیچیده طراحی و پیاده سازی نمیشوند. اما با گذشت زمان معمولا کلاسها پیچیدهتر میشوند. این پیچیدگی تاثیر مستقیمی را بر روی قابلیت نگهداری نرم افزار خواهد داشت.
تشخیص کلاسی که نیاز به جداسازی دارد امر مهمی است. روشهای مختلفی نیز برای این کار وجود دارد که در زیر اشاره شدهاست. با دیدن نشانههایی مانند نشانههای زیر میتوانید اطمینان داشته باشید که کلاس مورد نظر شما نیاز به جداسازی دارد.
- تعدادی خصوصیت و فیلد و متد در کلاس وجود دارند که به نظر میرسد فقط باهم کار میکنند.
- زیر مجموعهای از دادههای کلاس، معمولا همراه هم تغییر میکنند یا به یکدیگر وابسته هستند.
- با تغییر بدنه متدی، نیاز شود متدهای دیگری در همان راستا تغییر کنند.
- ارث بریها تنها به صورت دستهای بر اعضای کلاس اثر میگذارند.
مراحل انجام این بازسازی کد
- تصمیم بگیرید که به چه صورت کلاسی را تقسیم خواهید کرد.
- کلاس جدیدی بسازید که نشان دهنده مسئولیتهای جدید تقسیم شده باشد. اگر نام کلاس قدیمی با مسئولیتهای باقی مانده برای آن همخوانی نداشت، نام آن را تغییر دهید
- فیلدها و خصوصیات کلاس قدیمی را با استفاده از بازسازی Move field به کلاس جدید منتقل نمایید.
- کد را برای هر انتقال کامپایل و تست نمایید.
- متدهای کلاس قدیمی را با استفاده از بازسازی جابجایی متد به کلاس جدید منتقل نمایید.
- کد را برای هر انتقال، کامپایل و تست نمایید.
مثال (برای سادگی توضیح، موضوع مثالها بسیار ساده در نظر گرفته شدهاند)
فرض کنید بخشی از نرم افزار تولید شده، مسئولیت مدیریت صورت حسابها و پرداختها را بر عهده دارد. در این زیر سیستم کلاس مدیریت کننده صورت حسابها و پرداختها به اسم InvoiceService ساخته شدهاست. بدنه این کلاس به صورت زیر است:
public class InvoiceService { public void AddInvoice() { } public void DeleteInvoice() { } public void AddOfflinePayment(int invoiceId) { } public void AddOnlinePayment(int invoiceId) { } }
متدهای AddOfflinePayment و AddOnlinePayment مسئولیت ذخیره یک پرداخت را برای صورت حساب مشخص شده، دارند.
متدهای دیگری نیز برای ذخیره و حذف صورت حسابها در این کلاس مشاهده میشود.
اگر کلاس InvoiceService با این تعداد عضو باقی بماند احتمالا مشکل خاصی پیش نخواهد آمد. مشکل زمانی بوجود میآید که تعداد اعضای بیشتری برای مدیریت پرداختها و صورت حسابها نیاز باشد. طراحی فعلی این کلاس موارد مربوط به پرداخت و صورت حساب را تلفیق کردهاست. طراحی بهتر این کلاس، بازسازی استخراج کلاس است. به این صورت که کلاسی مجزا برای مدیریت امور پرداخت ایجاد شود. به این ترتیب کلاسهایی با مسئولیتهای مشخص خواهیم داشت. تکه کد زیر تغییرات کلاس InvoiceService را نشان میدهد:
public class InvoiceService { public void AddInvoice() { } public void DeleteInvoice() { } } public class PaymentService { public void AddOfflinePayment(int invoiceId) { } public void AddOnlinePayment(int invoiceId) { } }
تغییرات اعمال شده ساده هستند، اما با در نظر گرفتن تاثیر مثبت فراوان کلاسهای تک وظیفهای، این تغییر میتواند اثر بسیار خوبی بر روی قابلیت نگهداری نرم افزار نیز داشته باشد.
نمونههای دیگری از بازسازی کد استخراج متد که بیشتر مشاهده کردهام، به صورت زیر هستند:
- تقسیم کلاسهای controller بر اساس قوانین طراحی REST
- تقسیم کلاسهای داده (data model) بر اساس قوانین شیءگرایی و تک وظیفگی
نظر شما چیست؟ بیشترین موارد نیاز به استخراج کلاس را در چه بخشهایی از کد مشاهده کردهاید؟
اشتراکها
چرا CSV هنوز مهم است؟
اشتراکها