اگر از الگوی MVVM استفاده میکنید، یک پیاده سازی AsyncCommand را در اینجا میتوانید ملاحظه کنید:
Patterns for Asynchronous MVVM Applications: Commands
namespace System { public interface IProgress<in T> { void Report( T value ); } }
namespace System { public class Progress<T> : IProgress<T> { public Progress(); public Progress( Action<T> handler ); protected virtual void OnReport( T value ); } }
using System; using System.Threading; using System.Threading.Tasks; namespace Async09 { public class TestProgress { public async Task DoProcessingReportProgress() { var progress = new Progress<int>(percent => { Console.WriteLine(percent + "%"); }); var cts = new CancellationTokenSource(); // call some where cts.Cancel(); try { await doProcessing(progress, cts.Token); } catch (OperationCanceledException ex) { //todo: handle cancellations Console.WriteLine(ex); } Console.WriteLine("Done!"); } private static async Task doProcessing(IProgress<int> progress, CancellationToken ct) { await Task.Run(async () => { for (var i = 0; i != 100; ++i) { await Task.Delay(100, ct); if (progress != null) progress.Report(i); ct.ThrowIfCancellationRequested(); } }, ct); } } }
دانای اطلاعات ( 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 برای واسطها، چند ریختی در توارث و ... استفاده کرد تا از تاثیرات زنجیرهای تغییرات در امان بمانیم.
public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { public ApplicationDbContext() : base("DefaultConnection") { } }
public class AppUserManager : UserManager<AppUser>{ public AppUserManager() : base(new UserStore<AppUser>(new ShirazBilitDbContext())) { } }
public class AppUser : IdentityUser { public string Email { get; set; } public string ConfirmationToken { get; set; } public bool IsConfirmed { get; set; } }
توضیح | انتخاب کننده |
عناصری را انتخاب میکند که تحت کنترل انیمیشن میباشند. در پستهای بعدی انیمیشنها توضیح داده میشوند. | animated: |
عناصر دکمه را انتخاب میکند، عناصری مانند (input[type=submit]، input[type=reset]، input[type=button]، یا button) | button: |
عناصر Checkbox را انتخاب میکند، مانند ([input[type=checkbox). | checkbox: |
عناصر checkboxها یا دکمههای رادیویی را انتخاب میکند که در حالت انتخاب باشند. | checked: |
عناصری ر انتخاب میکند که دارای عبارت foo باشند. | contains(foo) //c: |
عناصر در حالت disabled را انتخاب میکند. | disabled: |
عناصر در حالت enabledرا انتخاب میکند. | enabled: |
عناصر فایل را انتخاب میکند، مانند ([input[type=file). | file: |
عناصر هدر مانند h1 تا h6 را انتخاب میکند. | header: |
عناصر مخفی شده را انتهاب میکند. | hidden: |
عناصر تصویر را انتخاب میکند، مانند ([input[type=image). | image: |
عناصر فرم مانند input ، select، textarea، button را انتخاب میکند. | input: |
انتخاب کنندهها را برعکس میکند. | not(filter)//c: |
عناصری که فرزندی دارند را انتخاب میکند. | parent: |
عناصر password را انتخاب میکند، مانند ([input[type=password). | password: |
عناصر radio را انتخاب میکند، مانند ([input[type=radio). | radio: |
دکمههای reset را انتخاب میکند، مانند ([input[type=reset یا [button[type=reset). | raset: |
عناصری (عناصر option) را انتخاب میکند که در وضعیت selected قراردارند. | selected: |
دکمههای submit را انتخاب میکند، مانند ([input[type=submit یا [button[type=submit). | submit: |
عناصر text را انتخاب میکند، مانند ([input[type=text). | text: |
عناصری را که در وضعیت visibleباشند انتخاب میکند. | visible: |
:checkbox:checked:enabled
input:not(:checkbox)
div span
div:has(span)
$('tr:has(img[src$="foo.png"])')
interface IPostService { void AddPost(Post post); IList<Post> GetPosts(); Post GetPost(int PostId); int RemovePost(Post post); int UpdatePost(Post post); }
public class PostService<T>:IPostService where T:Post { private readonly IUnitOfWork _uow; private readonly IDbSet<T> _post; public PostService(IUnitOfWork uow) { _uow = uow; _post = _uow.Set<T>(); } public void AddPost(T post) {} public IList<T> GetPosts() {} //... {
تعریف Interaction Design در زبان طراحی، تعامل انسان و کامپیوتر و توسعه نرمافزار اینگونه بیان میشود:
« عمل طراحی تعاملی محصولات دیجیتالی، محیطها، سیستمها و سرویسها. مانند سایر رشتههای طراحی، Interaction Design دارای شاخهها و توجهاتی است، اما به طور ساده میتوان گفت که تمرکز اصلی این رشته برروی رفتارها است.»
طراحی تعاملی یا Interaction Design که به اختصار به آن IxD نیز گفته میشود، بر روی ایجاد واسطهای کاربری جذاب با رفتارهای خوب تمرکز دارد. فهم این نکته که کاربران و تکنولوژی چگونه با یکدیگر ارتباط دارند، در این شاخه بسیار مهم و ضروری است. با این درک، شما میتوانید موارد زیر را پیشبینی نماید: اینکه چگونه یک فرد با سیستم تعامل دارد؟ چگونه مشکلات را با داشتن آن سیستم رفع میکند؟ و در نهایت با استفاده از این موارد راههای جدیدی برای توسعه سیستم، برای انجام کارها پیشنهاد دهید. در ادامه به بررسی Best Practice های Interaction Design خواهیم پرداخت.
در هنگام طراحی و توسعه یک محصول نرمافزاری با المانهای تعاملی، ویژگیها و سوالات مطرح شدهی زیر را در نظر بگیرید:
سوالات مهم در هنگام لحاظ کردن طراحی تعاملگرا
کاربران به چه صورتهایی میتوانند با واسط کاربری در ارتباط باشند | - کاربر چه تعاملاتی را میتواند به طور مستقیم با ماوس، انگشت یا stylus با واسط کاربری داشته باشد؟ - چه دستوراتی را کاربر میتواند صادر کند و با آنها تعامل داشته باشد که به طور مستقیم جزء محصول نیست؟ به عنوان مثال Ctrl+C که درون مرورگرها فعال است و جزئی از خود محصول نیست. |
دادن اطلاعاتی به کاربران، در مورد رفتارهای سیستم، پیش از انجام یک عمل | - ظاهر المانهای صفحه (رنگ، شکل، اندازه و ...) چه سرنخهایی را در مورد عملکرد آنها به کاربر خواهد داد؟ این المانها به کاربر میفهماند که چگونه باید از آنها استفاده کند. - شما چه اطلاعاتی را میتوانید در المانها بگنجانید که کاربر پیش از انجام یک عملیات از عملکرد آن المان مطلع شود؟ این مفاهیم میتوانند با گنجاندن label های با معنا در دکمهها، یا دستورالعملهای بسیار کوتاه برای تاییدیههای نهایی کامل شود. |
پیشبینی و کاهش خطاها | - آیا پیامهای خطا، راه روشنی را برای کاربر باز میکند تا بتواند مشکل کار خود را پیدا کند و منشا خطا را کشف نماید؟ - آیا در برخی موارد فشار و اجبار ( Constraint ) برای تحمیل عملیاتی خاص به کاربر جهت جلوگیری از خطا وجود دارد؟ اصل Poka-Yoka میگوید برای جلوگیری از سردرگمی کاربر و همچنین جلوگیری از خطاهای ممکن، در برخی موارد لازم است که کاربر را در محدودهای خاص و در یک مسیر مشخص (مانند مراحل تکمیل یک فرم) نگه داریم. این ایجاد فشار هم به کاربر کمک میکند و هم به تیم توسعه. |
در نظر گرفتن فیدبک و زمان پاسخ سیستم | - چگونه قرار است که به کاربر بازخورد بدهیم که پروسهای در حال اجرا است؟ هنگامیکه کاربر درگیر انجام عملیاتی است، سیستم باید متعاقبا یک پاسخ را برای کاربر نمایش دهد و چه بهتر که کاربر را در حین انجام پروسه (اگر پروسه طولانی باشد، مثلا بیش از 30 ثانیه) از آنچه که در سمت سرور صورت میگیرد آگاه سازد. این فرآیندها را میتوان با یک progress bar ساده مدل کرد. - بین یک عمل و پاسخ آن چه مدت زمانی طول خواهد کشید؟ واکنش پاسخ را میتوان در چهار سطح مشخص نمود: فوری یا immediate (کمتر از 0.1 ثانیه)، کند یا stammer (بین 0.1 تا 1 ثانیه)، وقفه یا interruption (بین 1 تا 10 ثانیه) و اختلال یا disruption (بیش از 10 ثانیه). |
نگاه استراتژیک دربارهی هر یک از عناصر درون صفحه | - آیا عناصر واسط کاربری اندازهی معقولی برای تعامل با کاربر دارند؟ عناصری مانند دکمهها، باید به اندازه کافی بزرگ باشند تا کاربر بتواند بر روی آنها کلیک کند. اما یک طراح نباید این نگاه را تنها به یک مرورگر منتهی کند. عمدهی مشکل در دستگاههای قابل حمل، مثل موبایلها و تبلتها رخ میدهد. - آیا لبهها و گوشهها (فضاهای خالی) به خوبی برای گنجاندن عناصر تعاملی مانند منوها استفاده شدهاند؟ یک قانون مهم در این زمینه میگوید که لبهها و گوشهها و نواحی مرزی، نواحی خوبی برای قرارگیری عناصر هستند. زیرا این نواحی معمولا نواحی مرزی هستند و کاربر به راحتی میتواند بر روی آنها کلیک و یا آنها را لمس نماید. - آیا شما از استانداردها پیروی میکنید؟ بالاخره کاربران آنقدرها هم بیاطلاع نیستند. آنها کمی هم دربارهی اینکه یک رابط کاربری چگونه است و عناصر آنها چگونه رفتار میکنند، اطلاعات دارد. پس، از این رو نیازی به خلق و بدعتگذاری نیست. تنها کافیاست اندکی از آنچه که در UX متداول شده، بهتر باشید. اگر روش شما بتواند خلاقانه و در عین حال ساده باشد، شما نیز میتوانید صاحب سبک شوید. |
سادهسازی برای افزایش سرعت یادگیری | - آیا اطلاعات مورد نیاز کاربر درون نرمافزار به هفت (به علاوه منهای دو) تکه تقسیم شدهاند؟ George Miller طی آزمایشاتی کشف کرد که افراد تنها قادرند پنج تا نه مورد را در حافظهی کوتاه مدت خود قرار دهند. - آیا واسط User End تا حد ممکن ساده شده است؟ قانون Tesler بیان میکند که شما باید سعی کنید که تمامی پیچیدگیها را تا آنجا که ممکن است از واسط User End حذف کنید. |