مطالب
طراحی و پیاده سازی زیرساختی برای تولید خودکار کد منحصر به فرد در زمان ثبت رکورد جدید

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


یک مثال واقعی

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

ایجاد یک قرارداد برای موجودیت‌های دارای شماره منحصر به فرد
public interface INumberedEntity
{
    string Number { get; set; }
}
با استفاده از این واسط می‌توان از تکرار یکسری از تنظیمات مانند تنظیم طول فیلد Number و همچنین ایجاد ایندکس منحصر به فرد برروی آن، به شکل زیر جلوگیری کرد.
foreach (var entityType in builder.Model.GetEntityTypes()
    .Where(e => typeof(INumberedEntity).IsAssignableFrom(e.ClrType)))
{
    builder.Entity(entityType.ClrType)
        .Property(nameof(INumberedEntity.Number)).IsRequired().HasMaxLength(50);

    if (typeof(IMultiTenantEntity).IsAssignableFrom(entityType.ClrType))
    {
        builder.Entity(entityType.ClrType)
            .HasIndex(nameof(INumberedEntity.Number), nameof(IMultiTenantEntity.TenantId))
            .HasName(
                $"UIX_{entityType.ClrType.Name}_{nameof(IMultiTenantEntity.TenantId)}_{nameof(INumberedEntity.Number)}")
            .IsUnique();
    }
    else
    {
        builder.Entity(entityType.ClrType)
            .HasIndex(nameof(INumberedEntity.Number))
            .HasName($"UIX_{entityType.ClrType.Name}_{nameof(INumberedEntity.Number)}")
            .IsUnique();
    }
}

ایجاد یک Entity برای نگهداری شماره قابل استفاده بعدی مرتبط با موجودیت‌ها
public class NumberedEntity : Entity, IMultiTenantEntity
{
    public string EntityName { get; set; }
    public long NextNumber { get; set; }
    
    public long TenantId { get; set; }
}

با تنظیمات زیر:
public class NumberedEntityConfiguration : IEntityTypeConfiguration<NumberedEntity>
{
    public void Configure(EntityTypeBuilder<NumberedEntity> builder)
    {
        builder.Property(a => a.EntityName).HasMaxLength(256).IsRequired().IsUnicode(false);
        builder.HasIndex(a => a.EntityName).HasName("UIX_NumberedEntity_EntityName").IsUnique();
        builder.ToTable(nameof(NumberedEntity));
    }
}

شاید به نظر، استفاده از این موجودیت ضروریتی نداشته باشد و خیلی راحت می‌توان آخرین شماره ثبت شده‌ی در جدول مورد نظر را واکشی، مقداری را به آن اضافه و به عنوان شماره منحصر به فرد رکورد جدید استفاده کرد؛ با این رویکرد حداقل دو مشکل زیر را خواهیم داشت:

  • ایجاد Gap مابین شماره‌های تولید شده، که مدنظر ما نمی‌باشد. (با توجه به اینکه امکان ثبت دستی را هم داریم، ممکن است کاربر شماره‌ای را وارد کرده باشد که با آخرین شماره ثبت شده تعداد زیادی فاصله دارد که به خودی خود مشکل ساز نیست؛ ولی در زمان ثبت رکورد بعدی اگر به صورت خودکار ثبت شماره داشته باشد، قطعا آخرین شماره (بزرگترین) را که به صورت دستی وارد شده بود، از جدول دریافت خواهد کرد)


پیاده سازی یک PreInsertHook برای مقداردهی پراپرتی Number

internal class NumberingPreInsertHook : PreInsertHook<INumberedEntity>
{
    private readonly IUnitOfWork _uow;
    private readonly IOptions<NumberingConfiguration> _configuration;

    public NumberingPreInsertHook(IUnitOfWork uow, IOptions<NumberingConfiguration> configuration)
    {
        _uow = uow ?? throw new ArgumentNullException(nameof(uow));
        _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
    }

    protected override void Hook(INumberedEntity entity, HookEntityMetadata metadata)
    {
        if (!entity.Number.IsNullOrEmpty()) return;

        bool retry;
        string nextNumber;
        
        do
        {
            nextNumber = GenerateNumber(entity);
            var exists = CheckDuplicateNumber(entity, nextNumber);
            retry = exists;
            
        } while (retry);
        
        entity.Number = nextNumber;
    }

    private bool CheckDuplicateNumber(INumberedEntity entity, string nextNumber)
    {
       //...
    }

    private string GenerateNumber(INumberedEntity entity)
    {
       //...
    }
}

ابتدا بررسی می‌شود اگر پراپرتی Number مقداردهی شده‌است، عملیات مقداردهی خودکار برروی آن انجام نگیرد. سپس با توجه به اینکه ممکن است به صورت دستی قبلا شماره‌ای مانند Task_1000 وارد شده باشد و NextNumber مرتبط هم مقدار 1000 را داشته باشد؛ در این صورت به هنگام ثبت رکورد بعدی، با توجه به Prefix تنظیم شده، دوباره به شماره Task_1000 خواهیم رسید که در این مورد خاص با استفاده از متد CheckDuplicateNumber این قضیه تشخیص داده شده و سعی مجددی برای تولید شماره جدید صورت می‌گیرد.


بررسی متد GenerateNumber

private string GenerateNumber(INumberedEntity entity)
{
    var option = _configuration.Value.NumberedEntityOptions[entity.GetType()];

    var entityName = $"{entity.GetType().FullName}";

    var lockKey = $"Tenant_{_uow.TenantId}_" + entityName;

    _uow.ObtainApplicationLevelDatabaseLock(lockKey);

    var nextNumber = option.Start.ToString();

    var numberedEntity = _uow.Set<NumberedEntity>().AsNoTracking().FirstOrDefault(a => a.EntityName == entityName);
    if (numberedEntity == null)
    {
        _uow.ExecuteSqlCommand(
            "INSERT INTO [dbo].[NumberedEntity]([EntityName], [NextNumber], [TenantId]) VALUES(@p0,@p1,@p2)", entityName,
            option.Start + option.IncrementBy, _uow.TenantId);
    }
    else
    {
        nextNumber = numberedEntity.NextNumber.ToString();
        _uow.ExecuteSqlCommand("UPDATE [dbo].[NumberedEntity] SET [NextNumber] = @p0 WHERE [Id] = @p1 ",
            numberedEntity.NextNumber + option.IncrementBy, numberedEntity.Id);
    }

    if (!string.IsNullOrEmpty(option.Prefix))
        nextNumber = option.Prefix + nextNumber;
    
    return nextNumber;
}

ابتدا با استفاده از متد الحاقی ObtainApplicationLevelDatabaseLock یک قفل منطقی را برروی یک منبع مجازی (lockKey) در سطح نرم افزار از طریق sp_getapplock ایجاد می‌کنیم. به این ترتیب بدون نیاز به درگیر شدن با مباحث isolation level بین تراکنش‌های همزمان یا سایر مباحث locking در سطح row یا table، به نتیجه مطلوب رسیده و تراکنش دوم که خواهان ثبت Task جدید می‌باشد، با توجه به اینکه INumberedEntity می‌باشد، لازم است پشت این global lock صبر کند و بعد از commit یا rollback شدن تراکنش جاری، به صورت خودکار قفل منبع مورد نظر باز خواهد شد.

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

public static void ObtainApplicationLevelDatabaseLock(this IUnitOfWork uow, string resource)
{
    uow.ExecuteSqlCommand(@"EXEC sp_getapplock @Resource={0}, @LockOwner={1}, 
                @LockMode={2} , @LockTimeout={3};", resource, "Transaction", "Exclusive", 15000);
}

با توجه به اینکه ممکن است درون تراکنش جاری چندین نمونه از موجودیت‌های INumberedEntity در حال ذخیره سازی باشند و از طرفی Hook ایجاد شده به ازای تک تک نمونه‌ها قرار است اجرا شود، ممکن است تصور این باشد که اجرای مجدد sp مذکور مشکل ساز شود و در واقع به Lock خود برخواهد خورد؛ ولی از آنجایی که پارامتر LockOwner با "Transaction" مقداردهی می‌شود، لذا فراخوانی مجدد این sp درون تراکنش جاری مشکل ساز نخواهد بود. 

گام بعدی، واکشی NextNumber مرتبط با موجودیت جاری می‌باشد؛ اگر در حال ثبت اولین رکورد هستیم، لذا numberedEntity مورد نظر مقدار null را خواهد داشت و لازم است شماره بعدی را برای موجودیت جاری ثبت کنیم. در غیر این صورت عملیات ویرایش با اضافه کردن IncrementBy به مقدار فعلی انجام می‌گیرد. در نهایت اگر Prefix ای تنظیم شده باشد نیز به ابتدای شماره تولیدی اضافه شده و بازگشت داده خواهد شد.

ساختار NumberingConfiguration

public class NumberingConfiguration
{
    public bool Enabled { get; set; }

    public IDictionary<Type, NumberedEntityOption> NumberedEntityOptions { get; } =
        new Dictionary<Type, NumberedEntityOption>();
}
public class NumberedEntityOption
{
    public string Prefix { get; set; }
    public int Start { get; set; } = 1;
    public int IncrementBy { get; set; } = 1;
}

با استفاده از دوکلاس بالا، امکان تنظیم الگوی تولید برای موجودیت‌ها را خواهیم داشت.

گام آخر: ثبت PreInsertHook توسعه داده شده و همچنین تنظیمات مرتبط با الگوی تولید شماره موجودیت‌ها

public static void AddNumbering(this IServiceCollection services,
    IDictionary<Type, NumberedEntityOption> options)
{
    services.Configure<NumberingConfiguration>(configuration =>
    {
        configuration.Enabled = true;
        configuration.NumberedEntityOptions.AddRange(options);
    });
    
    services.AddTransient<IPreActionHook, NumberingPreInsertHook>();
}

و استفاده از این متد الحاقی در Startup پروژه

services.AddNumbering(new Dictionary<Type, NumberedEntityOption>
{
    [typeof(Task)] = new NumberedEntityOption
    {
        Prefix = "T_",
        Start = 1000,
        IncrementBy = 5
    }
});

و موجودیت Task

public class Task : TrackableEntity, IAggregateRoot, INumberedEntity
{
    public const int MaxTitleLength = 256;
    public const int MaxDescriptionLength = 1024; 

    public string Title { get; set; }
    public string NormalizedTitle { get; set; }
    public string Description { get; set; }
    public TaskState State { get; set; } = TaskState.Todo; 
    public byte[] RowVersion { get; set; }
    public string Number { get; set; }
}

با خروجی‌های زیر

پ.ن ۱: در برخی از Domain‌ها نیاز به ریست کردن این شماره‌ها براساس یکسری فیلد موجود در موجودیت مورد نظر نیز مطرح می‌باشد. به عنوان مثال در یک سیستم انبارداری شاید براساس FiscalYear و در یک سیستم فروش با توجه به نحوه فروش (SaleType)، لازم باشد این ریست برای شماره‌های موجودیت «سفارش»، انجام پذیرد. در کل با کمی تغییرات می‌توان از این روش مطرح شده در چنین حالاتی نیز به عنوان یک ابزار شماره گذاری خودکار کمک گرفت.
پ.ن ۲: استفاده از امکانات  Sequence در Sql Server هم شاید اولین راه حلی باشد که به ذهن می‌رسد؛ ولی از آنجایی که از تراکنش‌ها پشتیبانی ندارد، مسئله Gap بین شماره‌ها پابرجاست و همچنین آزادی عملی را به این شکل که در مطلب مطرح شد، نداریم.
مطالب
Angular Material 6x - قسمت چهارم - نمایش پویای اطلاعات تماس‌ها
در قسمت قبل، یک لیست ثابت item 1/item 2/… را در sidenav نمایش دادیم. در این قسمت می‌خواهیم این لیست را با اطلاعات دریافت شده‌ی از سرور، پویا کنیم و همچنین با کلیک بر روی هر کدام، جزئیات آن‌ها را نیز در قسمت main-content نمایش دهیم.



تهیه سرویس اطلاعات پویای برنامه

سرویس Web API ارائه شده‌ی توسط ASP.NET Core در این برنامه، لیست کاربران را به همراه یادداشت‌های آن‌ها به سمت کلاینت باز می‌گرداند و ساختار موجودیت‌های آن‌ها به صورت زیر است:

موجودیت کاربر که یک رابطه‌ی one-to-many را با UserNotes دارد:
using System;
using System.Collections.Generic;

namespace MaterialAspNetCoreBackend.DomainClasses
{
    public class User
    {
        public User()
        {
            UserNotes = new HashSet<UserNote>();
        }

        public int Id { set; get; }
        public DateTimeOffset BirthDate { set; get; }
        public string Name { set; get; }
        public string Avatar { set; get; }
        public string Bio { set; get; }

        public ICollection<UserNote> UserNotes { set; get; }
    }
}
و موجودیت یادداشت‌های کاربر که سر دیگر رابطه را تشکیل می‌دهد:
using System;

namespace MaterialAspNetCoreBackend.DomainClasses
{
    public class UserNote
    {
        public int Id { set; get; }
        public DateTimeOffset Date { set; get; }
        public string Title { set; get; }

        public User User { set; get; }
        public int UserId { set; get; }
    }
}
در نهایت اطلاعات ذخیره شده‌ی در بانک اطلاعاتی توسط سرویس کاربران:
    public interface IUsersService
    {
        Task<List<User>> GetAllUsersIncludeNotesAsync();
        Task<User> GetUserIncludeNotesAsync(int id);
    }
در اختیار کنترلر Web API برنامه، برای ارائه‌ی به سمت کلاینت، قرار می‌گیرد:
namespace MaterialAspNetCoreBackend.WebApp.Controllers
{
    [Route("api/[controller]")]
    public class UsersController : Controller
    {
        private readonly IUsersService _usersService;

        public UsersController(IUsersService usersService)
        {
            _usersService = usersService ?? throw new ArgumentNullException(nameof(usersService));
        }

        [HttpGet]
        public async Task<IActionResult> Get()
        {
            return Ok(await _usersService.GetAllUsersIncludeNotesAsync());
        }

        [HttpGet("{id:int}")]
        public async Task<IActionResult> Get(int id)
        {
            return Ok(await _usersService.GetUserIncludeNotesAsync(id));
        }
    }
}
کدهای کامل لایه سرویس، تنظیمات EF Core و تنظیمات ASP.NET Core این قسمت را از پروژه‌ی پیوستی انتهای بحث می‌توانید دریافت کنید.
در این حالت اگر برنامه را اجرا کنیم، در مسیر زیر
 https://localhost:5001/api/users
یک چنین خروجی قابل مشاهده خواهد بود:


و آدرس https://localhost:5001/api/users/1 صرفا مشخصات اولین کاربر را بازگشت می‌دهد.


تنظیم محل تولید خروجی Angular CLI

ساختار پوشه بندی پروژه‌ی جاری به صورت زیر است:


همانطور که ملاحظه می‌کنید، کلاینت Angular در یک پوشه‌است و برنامه‌ی سمت سرور ASP.NET Core در پوشه‌ای دیگر. برای اینکه خروجی نهایی Angular CLI را به پوشه‌ی wwwroot پروژه‌ی وب کپی کنیم، فایل angular.json کلاینت Angular را به صورت زیر ویرایش می‌کنیم:
"build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "../MaterialAspNetCoreBackend/MaterialAspNetCoreBackend.WebApp/wwwroot",
تنظیم این outputPath به wwwroot پروژه‌ی وب سبب خواهد شد تا با صدور فرمان زیر:
 ng build --no-delete-output-path --watch
برنامه‌ی Angular در حالت watch (گوش فرا دادان به تغییرات فایل‌ها) کامپایل شده و سپس به صورت خودکار در پوشه‌ی MaterialAspNetCoreBackend.WebApp/wwwroot کپی شود. به این ترتیب پس از اجرای برنامه‌ی ASP.NET Core توسط دستور زیر:
 dotnet watch run
 این برنامه‌ی سمت سرور، در همان لحظه هم API خود را ارائه خواهد داد و هم هاست برنامه‌ی Angular می‌شود.
بنابراین دو صفحه‌ی کنسول مجزا را باز کنید. در اولی ng build (را با پارامترهای یاد شده در پوشه‌ی MaterialAngularClient) و در دومی dotnet watch run را در پوشه‌ی MaterialAspNetCoreBackend.WebApp اجرا نمائید.
هر دو دستور در حالت watch اجرا می‌شوند. مزیت مهم آن این است که اگر تغییر کوچکی را در هر کدام از پروژه‌ها ایجاد کردید، صرفا همان قسمت کامپایل می‌شود و در نهایت سرعت کامپایل نهایی برنامه به شدت افزایش خواهد یافت.


تعریف معادل‌های کلاس‌های موجودیت‌های سمت سرور، در برنامه‌ی Angular

در ادامه پیش از تکمیل سرویس دریافت اطلاعات از سرور، نیاز است معادل‌های کلاس‌های موجودیت‌های سمت سرور خود را به صورت اینترفیس‌هایی تایپ‌اسکریپتی تعریف کنیم:
ng g i contact-manager/models/user
ng g i contact-manager/models/user-note
این دستورات دو اینترفیس خالی کاربر و یادداشت‌های او را در پوشه‌ی جدید models ایجاد می‌کنند. سپس آن‌ها را به صورت زیر و بر اساس تعاریف سمت سرور آن‌ها، تکمیل می‌کنیم:
محتویات فایل contact-manager\models\user-note.ts :
export interface UserNote {
  id: number;
  title: string;
  date: Date;
  userId: number;
}
محتویات فایل contact-manager\models\user.ts :
import { UserNote } from "./user-note";

export interface User {
  id: number;
  birthDate: Date;
  name: string;
  avatar: string;
  bio: string;

  userNotes: UserNote[];
}


ایجاد سرویس Angular دریافت اطلاعات از سرور

ساختار ابتدایی سرویس دریافت اطلاعات از سرور را توسط دستور زیر ایجاد می‌کنیم:
 ng g s contact-manager/services/user --no-spec
که سبب ایجاد فایل user.service.ts در پوشه‌ی جدید services خواهد شد:
import { Injectable } from "@angular/core";

@Injectable({
  providedIn: "root"
})
export class UserService {

  constructor() { }
}
قسمت providedIn آن مخصوص Angular 6x است و هدف از آن کم حجم‌تر کردن خروجی نهایی برنامه‌است؛ اگر از سرویسی که تعریف شده، در برنامه جائی استفاده نشده‌است. به این ترتیب دیگر نیازی نیست تا آن‌را به صورت دستی در قسمت providers ماژول جاری ثبت و معرفی کرد.
کدهای تکمیل شده‌ی UserService را در ذیل مشاهده می‌کنید:
import { HttpClient, HttpErrorResponse } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable, throwError } from "rxjs";
import { catchError, map } from "rxjs/operators";

import { User } from "../models/user";

@Injectable({
  providedIn: "root"
})
export class UserService {

  constructor(private http: HttpClient) { }

  getAllUsersIncludeNotes(): Observable<User[]> {
    return this.http
      .get<User[]>("/api/users").pipe(
        map(response => response || []),
        catchError((error: HttpErrorResponse) => throwError(error))
      );
  }

  getUserIncludeNotes(id: number): Observable<User> {
    return this.http
      .get<User>(`/api/users/${id}`).pipe(
        map(response => response || {} as User),
        catchError((error: HttpErrorResponse) => throwError(error))
      );
  }
}
در اینجا از pipe-able operators مخصوص RxJS 6x استفاده شده که در مطلب «ارتقاء به Angular 6: بررسی تغییرات RxJS» بیشتر در مورد آن‌ها بحث شده‌است.
- متد getAllUsersIncludeNotes، لیست تمام کاربران را به همراه یادداشت‌های آن‌ها از سرور واکشی می‌کند.
- متد getUserIncludeNotes صرفا اطلاعات یک کاربر را به همراه یادداشت‌های او از سرور دریافت می‌کند.


بارگذاری و معرفی فایل svg نمایش avatars کاربران به Angular Material

بسته‌ی Angular Material و کامپوننت mat-icon آن به همراه یک MatIconRegistry نیز هست که قصد داریم از آن برای نمایش avatars کاربران استفاده کنیم.
در قسمت اول، نحوه‌ی «افزودن آیکن‌های متریال به برنامه» را بررسی کردیم که در آنجا آیکن‌های مرتبط، از فایل‌های قلم، دریافت و نمایش داده می‌شوند. این کامپوننت، علاوه بر قلم آیکن‌ها، از فایل‌های svg حاوی آیکن‌ها نیز پشتیبانی می‌کند که یک نمونه از این فایل‌ها در مسیر wwwroot\assets\avatars.svg فایل پیوستی انتهای مطلب کپی شده‌است (چون برنامه‌ی وب ASP.NET Core، هاست برنامه است، این فایل را در آنجا کپی کردیم).
ساختار این فایل svg نیز به صورت زیر است:
<?xml version="1.0" encoding="utf-8"?>
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink">
    <defs>
    <svg viewBox="0 0 128 128" height="100%" width="100%" 
             pointer-events="none" display="block" id="user1" >
هر svg تعریف شده‌ی در آن دارای یک id است. از این id به عنوان نام avatar کاربرها استفاده خواهیم کرد. نحوه‌ی فعالسازی آن نیز به صورت زیر است:
ابتدا به فایل contact-manager-app.component.ts مراجعه و سپس این کامپوننت آغازین ماژول مدیریت تماس‌ها را با صورت زیر تکمیل می‌کنیم:
import { Component } from "@angular/core";
import { MatIconRegistry } from "@angular/material";
import { DomSanitizer } from "@angular/platform-browser";

@Component()
export class ContactManagerAppComponent {

  constructor(iconRegistry: MatIconRegistry, sanitizer: DomSanitizer) {
    iconRegistry.addSvgIconSet(sanitizer.bypassSecurityTrustResourceUrl("assets/avatars.svg"));
  }
  
}
MatIconRegistry جزئی از بسته‌ی angular/material است که در ابتدای کار import شده‌است. متد addSvgIconSet آن، مسیر یک فایل svg حاوی آیکن‌های مختلف را دریافت می‌کند. این مسیر نیز باید توسط سرویس DomSanitizer در اختیار آن قرار گیرد که در کدهای فوق روش انجام آن‌را ملاحظه می‌کنید. در مورد سرویس DomSanitizer در مطلب «نمایش HTML در برنامه‌های Angular» بیشتر بحث شده‌است.
در اینجا در صورتیکه فایل svg شما دارای یک تک آیکن است، روش ثبت آن به صورت زیر است:
iconRegistry.addSvgIcon(
      "unicorn",
      this.domSanitizer.bypassSecurityTrustResourceUrl("assets/unicorn_icon.svg")
    );
که در نهایت کامپوننت mat-icon، این آیکن را به صورت زیر می‌تواند نمایش دهد:
 <mat-icon svgIcon="unicorn"></mat-icon>

یک نکته: پوشه‌ی node_modules\material-design-icons به همراه تعداد قابل ملاحظه‌ای فایل svg نیز هست.


نمایش لیست کاربران در sidenav

در ادامه به فایل sidenav\sidenav.component.ts مراجعه کرده و سرویس فوق را به آن جهت دریافت لیست کاربران، تزریق می‌کنیم:
import { User } from "../../models/user";
import { UserService } from "../../services/user.service";

@Component()
export class SidenavComponent implements OnInit {

  users: User[] = [];

  constructor(private userService: UserService) {  }

  ngOnInit() {
    this.userService.getAllUsersIncludeNotes()
      .subscribe(data => this.users = data);
  }
}
به این ترتیب با اجرای برنامه و بارگذاری sidenav، در رخ‌داد OnInit آن، کار دریافت اطلاعات کاربران و انتساب آن به خاصیت عمومی users صورت می‌گیرد.

اکنون می‌خواهیم از این اطلاعات جهت نمایش پویای آن‌ها در sidenav استفاده کنیم. در قسمت قبل، جای آن‌ها را در منوی سمت چپ صفحه به صورت زیر با اطلاعات ایستا مشخص کردیم:
    <mat-list>
      <mat-list-item>Item 1</mat-list-item>
      <mat-list-item>Item 2</mat-list-item>
      <mat-list-item>Item 3</mat-list-item>
    </mat-list>
اگر به مستندات mat-list مراجعه کنیم، در میانه‌ی صفحه، navigation lists نیز ذکر شده‌است که می‌تواند لیستی پویا را به همراه لینک به آیتم‌های آن نمایش دهد و این مورد دقیقا کامپوننتی است که در اینجا به آن نیاز داریم. بنابراین فایل sidenav\sidenav.component.html را گشوده و mat-list فوق را با mat-nav-list تعویض می‌کنیم:
    <mat-nav-list>
      <mat-list-item *ngFor="let user of users">
        <a matLine href="#">
          <mat-icon svgIcon="{{user.avatar}}"></mat-icon> {{ user.name }}
        </a>
      </mat-list-item>
    </mat-nav-list>
اکنون اگر برنامه را اجرا کنیم، یک چنین شکلی قابل مشاهده است:


که در اینجا علاوه بر لیست کاربران که از سرویس Users دریافت شده، آیکن avatar آن‌ها که از فایل assets/avatars.svg بارگذاری شده نیز قابل مشاهده است.


اتصال کاربران به صفحه‌ی نمایش جزئیات آن‌ها

در mat-nav-list فوق، فعلا هر کاربر به آدرس # لینک شده‌است. در ادامه می‌خواهیم با کمک سیستم مسیریابی، با کلیک بر روی نام هر کاربر، در سمت راست صفحه جزئیات او نیز نمایش داده شود:
    <mat-nav-list>
      <mat-list-item *ngFor="let user of users">
        <a matLine [routerLink]="['/contactmanager', user.id]">
          <mat-icon svgIcon="{{user.avatar}}"></mat-icon> {{ user.name }}
        </a>
      </mat-list-item>
    </mat-nav-list>
در اینجا با استفاده از routerLink، هر کاربر را بر اساس Id او، به صفحه‌ی جزئیات آن شخص، متصل کرده‌ایم. البته این مسیریابی برای اینکه کار کند باید به صورت زیر به فایل contact-manager-routing.module.ts اضافه شود:
const routes: Routes = [
  {
    path: "", component: ContactManagerAppComponent,
    children: [
      { path: ":id", component: MainContentComponent },
      { path: "", component: MainContentComponent }
    ]
  },
  { path: "**", redirectTo: "" }
];
البته اگر تا اینجا برنامه را اجرا کنید، با نزدیک کردن اشاره‌گر ماوس به نام هر کاربر، آدرسی مانند https://localhost:5001/contactmanager/1 در status bar مرورگر ظاهر خواهد شد، اما با کلیک بر روی آن، اتفاقی رخ نمی‌دهد.
این مشکل دو علت دارد:
الف) چون ContactManagerModule را به صورت lazy load تعریف کرده‌ایم، دیگر نباید در لیست imports فایل AppModule ظاهر شود. بنابراین فایل app.module.ts را گشوده و سپس تعریف ContactManagerModule را هم از قسمت imports بالای صفحه و هم از قسمت imports ماژول حذف کنید؛ چون نیازی به آن نیست.
ب) برای مدیریت خواندن id کاربر، فایل main-content\main-content.component.ts را گشوده و به صورت زیر تکمیل می‌کنیم:
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";

import { User } from "../../models/user";
import { UserService } from "../../services/user.service";

@Component({
  selector: "app-main-content",
  templateUrl: "./main-content.component.html",
  styleUrls: ["./main-content.component.css"]
})
export class MainContentComponent implements OnInit {

  user: User;
  constructor(private route: ActivatedRoute, private userService: UserService) { }

  ngOnInit() {
    this.route.params.subscribe(params => {
      this.user = null;

      const id = params["id"];
      if (!id) {
        return;
      }

      this.userService.getUserIncludeNotes(id)
        .subscribe(data => this.user = data);
    });
  }
}
در اینجا به کمک سرویس ActivatedRoute و گوش فرادادن به تغییرات params آن در ngOnInit، مقدار id مسیر دریافت می‌شود. سپس بر اساس این id، با کمک سرویس کاربران، اطلاعات این تک کاربر از سرور دریافت و به خاصیت عمومی user نسبت داده خواهد شد.
اکنون می‌توان از اطلاعات این user دریافتی، در قالب این کامپوننت و یا همان فایل main-content.component.html استفاده کرد:
<div *ngIf="!user">
  <mat-spinner></mat-spinner>
</div>
<div *ngIf="user">
  <mat-card>
    <mat-card-header>
      <mat-icon mat-card-avatar svgIcon="{{user.avatar}}"></mat-icon>
      <mat-card-title>
        <h2>{{ user.name }}</h2>
      </mat-card-title>
      <mat-card-subtitle>
        Birthday {{ user.birthDate | date:'d LLLL' }}
      </mat-card-subtitle>
    </mat-card-header>
    <mat-card-content>
      <mat-tab-group>
        <mat-tab label="Bio">
          <p>
            {{user.bio}}
          </p>
        </mat-tab>
        <!-- <mat-tab label="Notes"></mat-tab> -->
      </mat-tab-group>
    </mat-card-content>
  </mat-card>
</div>
در اینجا از کامپوننت mat-spinner برای نمایش حالت منتظر بمانید استفاده کرده‌ایم. اگر user نال باشد، این spinner نمایش داده می‌شود و برعکس.


همچنین mat-card را هم بر اساس مثال مستندات آن، ابتدا کپی و سپس سفارشی سازی کرده‌ایم (اگر دقت کنید، هر کامپوننت آن سه برگه‌ی overview، سپس API و در آخر Example را به همراه دارد). این روشی است که همواره می‌توان با کامپوننت‌های این مجموعه انجام داد. ابتدا مثالی را در مستندات آن پیدا می‌کنیم که مناسب کار ما باشد. سپس سورس آن‌را از همانجا کپی و در برنامه قرار می‌دهیم و در آخر آن‌را بر اساس اطلاعات خود سفارشی سازی می‌کنیم.



نمایش جزئیات اولین کاربر در حین بارگذاری اولیه‌ی برنامه

تا اینجای کار اگر برنامه را از ابتدا بارگذاری کنیم، mat-spinner قسمت نمایش جزئیات تماس‌ها ظاهر می‌شود و همانطور باقی می‌ماند، با اینکه هنوز موردی انتخاب نشده‌است. برای رفع آن به کامپوننت sidnav مراجعه کرده و در لحظه‌ی بارگذاری اطلاعات، اولین مورد را به صورت دستی نمایش می‌دهیم:
import { Router } from "@angular/router";

@Component()
export class SidenavComponent implements OnInit, OnDestroy {

  users: User[] = [];
  
  constructor(private userService: UserService, private router: Router) {
  }

  ngOnInit() {
    this.userService.getAllUsersIncludeNotes()
      .subscribe(data => {
        this.users = data;
        if (data && data.length > 0 && !this.router.navigated) {
          this.router.navigate(["/contactmanager", data[0].id]);
        }
      });
  }
}
در اینجا ابتدا سرویس Router به سازنده‌ی کلاس تزریق شده‌است و سپس زمانیکه کار دریافت اطلاعات تماس‌ها پایان یافت و this.router.navigated نبود (یعنی پیشتر هدایت به آدرسی صورت نگرفته بود؛ برای مثال کاربر آدرس id داری را ریفرش نکرده بود)، اولین مورد را توسط متد this.router.navigate فعال می‌کنیم که سبب تغییر آدرس صفحه از https://localhost:5001/contactmanager به https://localhost:5001/contactmanager/1 و باعث نمایش جزئیات آن می‌شود.

البته روش دیگر مدیریت این حالت، حذف کدهای فوق و تبدیل کدهای کامپوننت main-content به صورت زیر است:
let id = params['id'];
if (!id) id = 1;
در اینجا اگر id انتخاب نشده باشد، یعنی اولین بار نمایش برنامه است و خودمان id مساوی 1 را برای آن در نظر می‌گیریم.


بستن خودکار sidenav در حالت نمایش موبایل

اگر اندازه‌ی صفحه‌ی نمایشی را کوچکتر کنیم، قسمت sidenav در حالت over نمایان خواهد شد. در این حالت اگر آیتم‌های آن‌را انتخاب کنیم، هرچند آن‌ها نمایش داده می‌شوند، اما زیر این sidenav مخفی باقی خواهند ماند:


بنابراین در جهت بهبود کاربری این قسمت بهتر است با کلیک کاربر بر روی sidenav و گزینه‌های آن، این قسمت بسته شده و ناحیه‌ی زیر آن نمایش داده شود.
در کدهای قالب sidenav، یک template reference variable برای آن به نام sidenav درنظر گرفته شده‌است:
<mat-sidenav #sidenav
برای دسترسی به آن در کدهای کامپوننت خواهیم داشت:
import { MatSidenav } from "@angular/material";

@Component()
export class SidenavComponent implements OnInit, OnDestroy {

  @ViewChild(MatSidenav) sidenav: MatSidenav;
اکنون که به این ViewChild دسترسی داریم، می‌توانیم در حالت نمایشی موبایل، متد close آن‌را فراخوانی کنیم:
  ngOnInit() {
    this.router.events.subscribe(() => {
      if (this.isScreenSmall) {
        this.sidenav.close();
      }
    });
  }
در اینجا با مشترک this.router.events شدن، متوجه‌ی کلیک کاربر و نمایش صفحه‌ی جزئیات آن می‌شویم. در قسمت سوم این مجموعه نیز خاصیت isScreenSmall را بر اساس ObservableMedia مقدار دهی کردیم. بنابراین اگر کاربر بر روی گزینه‌ای کلیک کرده بود و همچنین اندازه‌ی صفحه در حالت موبایل قرار داشت، sidenav را خواهیم بست تا بتوان محتوای زیر آن‌را مشاهده کرد:



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MaterialAngularClient-03.zip
برای اجرای آن:
الف) ابتدا به پوشه‌ی src\MaterialAngularClient وارد شده و فایل‌های restore.bat و ng-build-dev.bat را اجرا کنید.
ب) سپس به پوشه‌ی src\MaterialAspNetCoreBackend\MaterialAspNetCoreBackend.WebApp وارد شده و فایل‌های restore.bat و dotnet_run.bat را اجرا کنید.
اکنون برنامه در آدرس https://localhost:5001 قابل دسترسی است.
مطالب
پیاده سازی برنامه‌های چند مستاجری در ASP.NET Core

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

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

 در معماری Multi-Tenancy، چندین کاربر می‌توانند از یک نمونه (Single Instance) از اپلیکیشن نرم‌افزاری استفاده کنند. یعنی این نمونه روی سرور اجرا می‌شود و به چندین کاربر سرویس می‌دهد. هر کاربر را یک Tenant می‌نامیم. می‌توان به Tenantها امکان تغییر و شخصی‌سازی بخشی از اپلیکیشن را داد؛ مثلا امکان تغییر رنگ رابط کاربری و یا قوانین کسب‌وکار، اما آنها نمی‌توانند کدهای اپلیکیشن را شخصی‌سازی کنند.

بدون داشتن دانش کافی، پیاده سازی معماری multi tenant می‌تواند تبدیل یه یک چالش بزرگ شود. مخصوصا در نسخه‌ی قبلی ASP.NET که یکپارچه نبودن فریم ورک‌های مختلف می‌توانست باعث ایجاد چندین پیاده سازی مختلف در برنامه شود. موضوع وقتی پیچیده‌تر می‌شد که شما قصد داشتید در یک برنامه چندین فریم ورک مختلف مثل SignalR, MVC, Web API را مورد استفاده قرار دهید.

خوشبختانه اوضاع با وجود OWIN بهتر شده و ما در این مطلب قصد استفاده از یک تولکیت را به نام SaasKit، برای پیاده سازی این معماری در ASP.NET Core داریم. هدف از این toolkit، ساده‌تر کردن هر چه بیشتر ساخت برنامه‌های SaaS (Software as a Service) هست. با استفاده از OWIN ما قادریم که بدون در نظر گرفتن فریم ورک مورد استفاده، رفتار مورد نظر خودمان را مستقیما در یک چرخه درخواست HTTP پیاده سازی کنیم و البته به لطف طراحی خاص ASP.NET Core 1.0 و استفاده از میان افزار‌هایی مشابه OWIN در برنامه، کار ما با SaasKit باز هم راحت‌تر خواهد بود.

شروع کار 

یک پروژه ASP.NET Core جدید را ایجاد کنید و سپس ارجاعی را به فضای نام SaasKit.Multitenancy  (موجود در Nuget) بدهید. 
PM> Install-Package SaasKit.Multitenancy
بعد از اینکار ما باید به SaasKit اطلاع دهیم که چطور مستاجر‌های ما را شناسایی کند.

شناسایی مستاجر (tenant) 

اولین جنبه در معماری multi-tenant، شناسایی مستاجر بر اساس اطلاعات درخواست جاری می‌باشد که می‌تواند از hostname ، کاربر جاری یا یک HTTP header باشد.
ابتدا به تعریف کلاس مستاجر می‌پردازیم: 
    public class AppTenant
    {
        public string Name { get; set; }
        public string[] Hostnames { get; set; }
    }
سپس از طریق پیاده سازی اینترفیس ITenantResolver  و نوشتن یک tenant resolver به SaasKit اطلاع می‌دهیم که چطور مستاجر جاری را بر اساس اطلاعات درخواست جاری شناسایی کند و در صورتیکه موفق به شناسایی شود، یک وهله از نوع <TenantContext<TTenant را بازگشت خواهد داد. 
    public class AppTenantResolver : ITenantResolver<AppTenant>
    {
        IEnumerable<AppTenant> tenants = new List<AppTenant>(new[]
        {
            new AppTenant {
                Name = "Tenant 1",
                Hostnames = new[] { "localhost:6000", "localhost:6001" }
            },
            new AppTenant {
                Name = "Tenant 2",
                Hostnames = new[] { "localhost:6002" }
            }
        });
        public async Task<TenantContext<AppTenant>> ResolveAsync(HttpContext context)
        {
            TenantContext<AppTenant> tenantContext = null;
            var tenant = tenants.FirstOrDefault(t =>
                t.Hostnames.Any(h => h.Equals(context.Request.Host.Value.ToLower())));
            if (tenant != null)
            {
                tenantContext = new TenantContext<AppTenant>(tenant);
            }
            return tenantContext;
        }
    }
در نظر داشته باشید که اینجا ما اطلاعات مستاجر را از روی hostname استخراج کردیم؛ اما از آنجا که شما به شیء HttpContext دسترسی کاملی دارید، می‌توانید از هر چیزی که مایل باشید استفاده کنید؛ مثل URL، اطلاعات کاربر، هدر‌های HTTP و غیره. در اینجا فعلا مشخصات مستاجر‌های خودمان را در کد نوشتیم. اما شما می‌توانید در برنامه خودتان این اطلاعات را از فایل تنظیمات برنامه و یا یک بانک اطلاعاتی دریافت کنید.
 

سیم کشی کردن 

بعد از پیاده سازی این اینترفیس نوبت به سیم کشی‌های SaasKit میرسد. من در اینجا سعی کردم که مثل الگوی برنامه‌های ASP.NET Core عمل کنم. ابتدا نیاز داریم که وابستگی‌های SaasKit را ثبت کنیم. فایل startups.cs  را باز کنید و کدهای زیر را در متد ConfigureServices اضافه نمایید: 
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMultitenancy<AppTenant, AppTenantResolver>();
    }
سپس باید میان افزار SaasKit را ثبت کنیم. کدهای زیر را به متد Configure اضافه کنید:
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        // after .UseStaticFiles()
        app.UseMultitenancy<AppTenant>();
        // before .UseMvc()
    }


دریافت مستاجر جاری 

حالا هر جا که نیاز به وهله‌ای از شیء مستاجر جاری داشتید، می‌توانید به روش زیر عمل کنید: 
    public class HomeController : Controller
    {
        private AppTenant tenant;
        public HomeController(AppTenant tenant)
        {
            this.tenant = tenant;
        }
    }
به عنوان مثال قصد داریم نام مستاجر را در عنوان سایت نمایش دهیم. برای اینکار ما از قابلیت جدید MVC Core یعنی تزریق سرویس‌ها به View استفاده خواهیم کرد.  در فایل Layout.cshtml_ تکه کد زیر را به بالای صفحه اضافه کنید:
    @inject AppTenant Tenant;
این کد، AppTenant را برای ما در تمامی View‌ها از طریق شی Tenant قابل دسترس می‌کند. حالا می‌توانیم در View خود از جزییات مستاجر به شکل زیر استفاده کنیم: 
    <a asp-controller="Home" asp-action="Index">@Tenant.Name</a>


اجرای نمونه مثال 

فایل project.json را باز کنید و مقدار web را به شکل زیر مقدار دهی کنید: (در اینجا برای سایت خود 3 آدرس را نگاشت کردیم) 
    "commands": {
      "web": "Microsoft.AspNet.Server.Kestrel --server.urls=http://localhost:6000;http://localhost:6001;http://localhost:6002",
    },
سپس کنسول را در محل ریشه پروژه باز نموده و دستور زیر را اجرا کنید: 
    dotnet run
حال اگر در مرورگر خود آدرس http://localhost:6000 را وارد کنیم، مستاجر 1 را خواهیم دید:


و اگر آدرس http://localhost:6002 را وارد کنیم، مستاجر 2 را مشاهده می‌کنیم:


قابل پیکربندی کردن مستاجر ها 

از آنجائیکه نوشتن مشخصات مستاجر‌ها در کد زیاد جالب نیست، برای همین تصمیم داریم که این مشخصات را با استفاده از قابلیت‌های ASP.NET Core از فایل appsettings.json دریافت کنیم. تنظیمات مستاجر‌ها را مطابق اطلاعات زیر به این فایل اضافه کنید:

    "Multitenancy": {
      "Tenants": [
        {
          "Name": "Tenant 1",
          "Hostnames": [
            "localhost:6000",
            "localhost:6001"
          ]
        },
        {
          "Name": "Tenant 2",
          "Hostnames": [
            "localhost:6002"
          ]
        }
      ]
    }
سپس کلاسی را که بیانگر تنظیمات چند مستاجری باشد، می‌نویسیم: 
    public class MultitenancyOptions
    {
        public Collection<AppTenant> Tenants { get; set; }
    }
حالا نیاز داریم که به برنامه اعلام کنیم تا تنظیمات مورد نیاز خود را از فایل appsettings.json بخواند. کد زیر را به ConfigureServices اضافه کنید: 
    services.Configure<MultitenancyOptions>(Configuration.GetSection("Multitenancy"));
سپس کدهای resolver خود را جهت دریافت اطلاعات از MultitenancyOptions مطابق زیر تغییر می‌دهیم: 
    public class AppTenantResolver : ITenantResolver<AppTenant>
    {
        private readonly IEnumerable<AppTenant> tenants;
        public AppTenantResolver(IOptions<MultitenancyOptions> options)
        {
            this.tenants = options.Value.Tenants;
        }
        public async Task<TenantContext<AppTenant>> ResolveAsync(HttpContext context)
        {
            TenantContext<AppTenant> tenantContext = null;
            var tenant = tenants.FirstOrDefault(t => 
                t.Hostnames.Any(h => h.Equals(context.Request.Host.Value.ToLower())));
            if (tenant != null)
            {
                tenantContext = new TenantContext<AppTenant>(tenant);
            }
            return Task.FromResult(tenantContext);
        }
    }
برنامه را یکبار re-build کرده و اجرا کنید . 


در آخر 

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

_ سورس مثال بالا در گیت هاب قابل دریافت می‌باشد.

_ منبع: اینجا  

مطالب
الگوی طراحی Builder همراه با اصول Interface Segregation
الگوی طراحی builder، برای ساختن اشیاء بسیار مفید است؛ اما پروسه ساختن اشیاء آن بسیار پیچده هست و به صورت معمول، این پروسه شامل چندین قسمت می‌شود.
در این مثال ما مشکلات ساختن شیء Person را مورد بررسی قرار می‌دهیم و این شیء از اشیایی کوچکتر مانند Name ، Surname و یا Primary Contact و غیره نیز تشکیل شده است.
class Person : IPerson
{

    private string Name { get; }
    private string Surname { get; }
    private IContact PrimaryContact { get; set; }
    private IList<IContact> AllContacts { get; }

    public Person(string name, string surname, IContact primaryContact)
    {
        if (string.IsNullOrEmpty(name))
            throw new ArgumentException(nameof(name));
        if (string.IsNullOrEmpty(surname))
            throw new ArgumentException(nameof(surname));

        this.Name = name;
        this.Surname = surname;
        this.AllContacts = new List<IContact>();

        this.SetPrimaryContact(primaryContact);
    }

    public void SetPrimaryContact(IContact contact)
    {
        this.AddContact(contact);
        this.PrimaryContact = contact;
    }

    public void AddContact(IContact contact)
    {
        if (contact == null)
            throw new ArgumentNullException(nameof(contact));

        this.AllContacts.Add(contact);
    }
}
همان طور که مشاهده می‌کنید، مقدار دهی شیء IContact  پیچیده‌تر از Name و Surname هست و روش اضافه کردن Contact‌ها نیز بسیار پیچیده است؛ زیرا آنها به دو گروه PrimaryContact و Contacts تقسیم شده‌اند.
 شی Person شامل تعدای Contact مانند تلفن، ایمیل و یا هر چیزی دیگری میتواند باشد. 
در این مثال ما دو نوع Contact داریم که به صورت زیر پیاده سازی شده‌اند: 
interface IContact
{
}

class PhoneNumber : IContact
{

    private string AreaCode { get; }
    private string Number { get; }

    public PhoneNumber(string areaCode, string number)
    {

        if (string.IsNullOrEmpty(areaCode))
            throw new ArgumentException(nameof(areaCode));
        if (string.IsNullOrEmpty(number))
            throw new ArgumentException(nameof(number));

        this.AreaCode = areaCode;
        this.Number = number;
    }
}

class EmailAddress : IContact
{
    private string Address { get; }

    public EmailAddress(string address)
    {
        if (string.IsNullOrEmpty(address))
            throw new ArgumentException(nameof(address));   

        this.Address = address;
    }
}
به صورت کلی سه راه برای ساختن اشیاء وجود دارد:
1) استفاده از سازنده کلاس Person و سپس استفاده از متدهای AddContact و  SetPrimaryContact برای ساختن شیء، به صورت کامل.
2) استفاده از Abstract Factory برای ساختن Person و سپس استفاده از متدهای AddContact و  SetPrimaryContact برای ساختن شیء به صورت کامل.
3) استفاده از Builder برای ساختن شیء به صورت کامل و یکجا همراه با contact‌‌های آن.

 طراحی PersonBuilder : 
interface IPerson
{
    void SetPrimaryContact(IContact primaryContact);
    void AddContact(IContact contact);
}

interface IPersonBuilder
{
    void SetName(string name);
    void SetSurname(string surname);
    void SetPrimaryContact(IContact primaryContact);
    void AddContact(IContact contact);
    IPerson Build();
}
همانطور که مشاهده می‌کنید، یک اینترفیس معمولی از این الگوی طراحی هست که شامل متدهایی است که برای ساختن شیء مورد استفاده قرار میگیرند و در ادامه نحوه پیاده سازی این اینترفیس بیان شده‌است:
class PersonBuilder: IPersonBuilder
{
    private string Name { get; set; }
    private string Surname { get; set; }
    private IContact PrimaryContact { get; set; }
    private IList<IContact> OtherContacts { get; } = new List<IContact>();

    public void SetName(string name)
    {
        if (string.IsNullOrEmpty(name))
            throw new ArgumentException(nameof(name));
        this.Name = name;
    }

    public void SetSurname(string surname)
    {
        if (string.IsNullOrEmpty(surname))
            throw new ArgumentException(nameof(surname));
        this.Surname = surname;
    }

    public void SetPrimaryContact(IContact primaryContact)
    {
        if (primaryContact == null)
            throw new ArgumentNullException(nameof(primaryContact));
        this.PrimaryContact = primaryContact;
    }

    public void AddContact(IContact contact)
    {
        if (contact == null)
            throw new ArgumentNullException(nameof(contact));
        this.OtherContacts.Add(contact);
    }

    public IPerson Build()
    {        
        IPerson person = new Person(this.Name, this.Surname, this.PrimaryContact);

        foreach (IContact contact in this.OtherContacts)
            person.AddContact(contact);

        return person;
    }
}
خوب، اولین مشکلی که در این پیاده سازی مشهود است، مربوط به متد Build هست. اگر مقدار‌های سازنده کلاس Person را به صورت null ارسال کنیم، باعث خطا میشود و این خطا به این خاطر نیست که ما مقدار Null را به کلاس PersonBuilder ارسال کرده‌ایم؛ زیرا ما تمام متد‌های Set را با استفاده NullGurd مورد حفاظت قرار داده‌ایم. مشکل اصلی از وضعیت داخلی شیء PersonBuilder  هست. اگر متد‌های Set را فراخوانی نکنیم، تمام فیلد‌های خصوصی، مقدار null میگیرند و یکی از راه‌های رفع این مشکل این است که پارامتر‌ها را از طریق سازنده PersonBuilder مقدار دهی کنیم. ولی کمی بعدتر متوجه خواهیم شد که این پیاده سازی مانند کلاس person هست و در نتیجه این روش بی استفاده است.

راه حل: استفاده از  Interface Segregation principle در PersonBuilder :
اصل ISP  می‌گوید: "کلاینت‌ها نباید وابسته به متدهایی باشند که آنها را پیاده سازی نمی‌کنند." برای رسیدن به این امر در مثال بالا، باید آن واسط را به واسط‌های کوچکتری تقسیم کرد. این تقسیم بندی باید بر اساس استفاده کنندگان از واسط‌ها صورت گیرد.
برای اینکه شیء Person را بسازیم، متوجه خواهید شد بعضی از داده‌ها الزامی و بعضی دیگر اختیاری هستند؛ مانند PrimaryContact که از داده‌های ضروری شیء Person است. ولی AllContacts می‌تواند به صورت اختیاری تعریف شود و در  پیاده سازی PersonBuilder بالا، کلاینت متوجه نخواهد شد کدام متد اختیاری یا اجباری  هست و در نتیجه ممکن است فراموش کند متد SetPrimaryContact را فراخوانی کند و همین مساله باعث می‌شود تا نرم افزار با خطا مواجه شود.
راه حل: به کد زیر توجه فرمایید: 
class PersonBuilder
{

    private PersonBuilder() { }

    public static IExpectSurnamePersonBuilder WithName(string name)
    {
        ...
    }
}
همانطور که مشاهده می‌فرمایید، سازنده کلاس به صورت خصوصی تعریف شده‌است. درنتیجه بیرون از کلاس نمی‌توان از آن وهله ساخت و ‌‌آن‌را مورد استفاده قرار داد و تنها راه وهله سازی از کلاس PersonBuilder از طریق متد WithName خواهد بود. ثانیا این متد PersonBuilder را برنمی‌گرداند؛ بلکه شیء‌ایی را برمیگ‌رداند که منتظر فراهم کردن مقدار Surname  است و با استفاده از این روش می‌توانیم پروسه فراخوانی متد‌ها را مشخص کنیم.
درنتیجه پروسه ساختن شیء، به چندین قسمت تقسیم شده که به صورت زیر میباشد:
1) فراهم کردن مقدار Surname
2) فراهم کردن مقدار Name
3) فراهم کردن مقدار PrimaryContact
4) فراهم کردن مقدار سایر Contact‌های شخص
5) ساختن شیء Person

پس به ازای هر کدام از عملیات‌ها، یک اینترفیس خواهیم داشت: 
interface IExpectSurnamePersonBuilder
{
    IExpectPrimaryContactPersonBuilder WithSurname(string surname);
}

interface IExpectPrimaryContactPersonBuilder
{
    IExpectOtherContactsPersonBuilder WithPrimaryContact(IContact contact);
}

interface IExpectOtherContactsPersonBuilder
{
    IExpectOtherContactsPersonBuilder WithOtherContact(IContact contact);
    IPersonBuilder WithNoMoreContacts();
}

interface IPersonBuilder
{
    IPerson Build();
}
حالا نوبت به پیاده سازی PersonBuilder بر اساس اصول ISP است :
class PersonBuilder :
    IExpectSurnamePersonBuilder,
    IExpectPrimaryContactPersonBuilder,
    IExpectOtherContactsPersonBuilder,
    IPersonBuilder
{

    private string Name { get; }
    private string Surname { get; set; }
    private IContact PrimaryContact { get; set; }
    private Person Person { get; set; }

    private PersonBuilder(string name)
    {
        if (string.IsNullOrEmpty(name))
            throw new ArgumentException(nameof(name));
        this.Name = name;
    }

    public static IExpectSurnamePersonBuilder WithName(string name)
    {
        return new PersonBuilder(name);
    }

    public IExpectPrimaryContactPersonBuilder WithSurname(string surname)
    {
        if (string.IsNullOrEmpty(surname))
            throw new ArgumentException(nameof(surname));
        this.Surname = surname;
        return this;
    }

    public IExpectOtherContactsPersonBuilder WithPrimaryContact(IContact contact)
    {
        if (contact == null)
            throw new ArgumentNullException(nameof(contact));
        this.Person = new Person(this.Name, this.Surname, contact);
        return this;
    }

    public IExpectOtherContactsPersonBuilder WithOtherContact(IContact contact)
    {
        if (contact == null)
            throw new ArgumentNullException(nameof(contact));
        this.Person.AddContact(contact);
        return this;
    }

    public IPersonBuilder WithNoMoreContacts()
    {
        return this;
    }

    public IPerson Build()
    {
        return this.Person;
    }

}
این طراحی به کلاینت کمک خواهد کرد اشیایی را با وضعیت پایدار ایجاد کند و نرم افزاری تولید کند که دارای کمترین خطا باشد.
و اگر کلاینت بخواهد وهله‌ای را از کلاس PersonBuilder بسازد، به صورت زیر خواهد بود:
IPerson person =
    PersonBuilder
    .WithName("Ali")
    .WithSurname("Karimi")
    .WithPrimaryContact(new EmailAddress("admin@gmail.com"))
    .WithOtherContact(new EmailAddress("Test1@work.com"))
    .WithOtherContact(new EmailAddress("Test2@home.com"))
    .WithNoMoreContacts()
    .Build();
اصول طراحی ISP باعث می‌شوند، کد خواناتر شود و همین خوانایی سبب می‌گردد نگهداری و توسعه نرم افزار راحت‌تر شود.

چکیده:
ساختن اشیا در زبان‌های object oriented کار بسیار ساده‌ای است و همین سادگی، خطاهای جبران ناپذیری را به نرم افزار تحمیل میکنند و باعث ایجاد اشیایی ناپایدار در سیستم می‌شود. در اولین گام، الگوی طراحی Builder را به صورت ساده مورد بررسی قرار دادیم و در نهایت این طراحی را تا جای پیش بردیم که بتوانیم اشیایی پایدار را بسازیم. ولی این طراحی هنوز با مشکلاتی رو به رو هست؛ مانند نقض کردن قانون  command query separation  که این مشکل را در مقاله‌ی بعدی برطرف خواهیم کرد.
مطالب
Design Pattern: Factory

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

الگوهای طراحی از لحاظ سناریو، به سه گروه عمده تقسیم می‌شوند:

1- تکوینی: هر چقدر تعداد کلاسها در یک پروژه زیاد شود، به مراتب تعداد اشیاء ساخته شده از آن نیز افزوده شده و پیچیدگی و درگیری نیز افزایش می‌یابد. راه حل‌هایی از این دست، تمرکز بر روی مرکزیت دادن به کلاسها با استفاده از رابط‌ها و کپسوله نمودن (پنهان سازی) اشیاء دارد. 

2- ساختاری: گاهی در پروژه‌ها پیش می‌آید که می‌خواهیم ارتباط بین دو کلاس را تغییر دهیم. از این رو امکان از هم پاشی اجزایِ دیگر پروژه پیش می‌آید. راه حلهای ساختاری، سعی در حفظ انسجام پروژه در برابر این دست از تغییرات را دارند.

3- رفتاری: گاهی بنا به مصلحت و نیاز مشتری، رفتار یک کلاس می‌بایستی تغییر نماید. مثلا چنانچه کلاسی برای ارائه صورتحساب داریم و در آن میزان مالیات 30% لحاظ شده است، حال این درصد باید به عددی دیگر تغییر کند و یا پایگاه داده به جای مشاهده‌ی تعدادِ معدودی گره از درخت، حال می‌بایست تمام گره‌ها را ارائه نماید.


الگوی فکتوری:

الگوی فکتوری در دستهء اول قرار می‌گیرد. من در اینجا به نمونه‌ای از مشکلاتی که این الگو حل می‌نماید، اشاره می‌کنم:

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


یک راه این است که با کلیک روی دکمه‌ی چاپ، نوع مشتری تشخیص داده شود و به ازای نوع مشتری، یک شیء از کلاس مشخص شده برای همان نوع ساخته شود.

 

 

            // Get Customer Type from Customer click on Print Button
            int customerType = 0;

            // Create Object without instantiation
            object obj;


            //Instantiate obj according to customer Type
            if (customerType == 1)
            {
                obj = new Customer1();
            }
            else if (customerType == 2)
            {
                obj = new Customer2();
            }
            // Problem:
            //          1: Scattered New Keywords
            //          2: Client side is aware of Customer Type

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

راه حل: این مشکل با استفاده از الگوی فکتوری قابل حل است. با استناد به الگوی فکتوری، کلاینت تنها به کلاس فکتوری و یک اینترفیس دسترسی دارد و کلاسهای فکتوری و اینترفیس، حق دسترسی به کلاسهای اصلی برنامه را دارند.

گام نخست: در ابتدا یک class library  به نام Interface ساخته و در آن یک کلاس با نام ICustomer  می سازیم   که متد Report() را معرفی می‌نماید.

  //Interface

namespace Interface
{
    public interface ICustomer
    {
        void Report();
    }
}

گام دوم: یک class library  به نام MainClass  ساخته و با Add Reference کلاس Interface را اضافه نموده، در آن دو کلاس با نام Customer1, Customer2 می‌سازیم و using Interface را Import می‌نماییم. هر دو کلاس از ICustomer  ارث می‌برند و  سپس متد Report() را در هر دو کلاس Implement می‌نماییم.

// Customer1
using System;
using Interface;

namespace MainClass
{
    public class Customer1 : ICustomer
    {
        public void Report()
        {           
            Console.WriteLine("این گزارش مخصوص مشتری نوع اول است");           
        }
    }
}

//Customer2
using System;
using Interface;

namespace MainClass
{
   public class Customer2 : ICustomer
    {
        public void Report()
        {           
            Console.WriteLine("این گزارش مخصوص مشتری نوع دوم است");           
        }
    }
}

گام سوم: یک class library  به نام FactoryClass  ساخته و با Add Reference کلاس Interface, MainClass را اضافه نموده، در آن یک کلاس با نام clsFactory  می سازیم و using Interface, using MainClass را Import می‌نماییم. پس از آن یک متد با نام getCustomerType ساخته که ورودی آن نوع مشتری از نوع int است و خروجی آن از نوع Interface-ICustomer و بر اساس کد نوع مشتری object را از کلاس Customer1 و یا Customer2 می‌سازیم و آن را return می نماییم.

//Factory
using System;
using Interface;
using MainClass;

namespace FactoryClass
{
    public class clsFactory
    {
        static public ICustomer getCustomerType(int intCustomerType)
        {
            ICustomer objCust;
            if (intCustomerType == 1)
            {
                objCust = new Customer1();
            }
            else if (intCustomerType == 2)
            {
                objCust = new Customer2();
            }
            else
            {
                return null;
            }
            return objCust;
        }
    }
}

گام چهارم (آخر): در قسمت UI   Client، کد نوع مشتری را از کاربر دریافت کرده و با Add Reference کلاس Interface, FactoryClass را اضافه نموده (دقت نمایید هیچ دسترسی به کلاس‌های اصلی وجود ندارد)، و using Interface,  using FactoryClass را Import می‌نماییم. از clsFactory تابع  getCustomerType را فراخوانی نموده (به آن کد نوع مشتری را پاس می‌دهیم) و خروجی آن را که از نوع اینترفیس است به یک object از نوع ICustomer  نسبت می‌دهیم. سپس از این object  متد Report را فراخوانی می‌نماییم. همانطور که از شکل و کدها مشخص است، هیچ رابطه ای بین UI(Client) و کلاسهای اصلی برقرار نیست.

//UI (Client)
using System;
using FactoryClass;
using Interface;

namespace DesignPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            int intCustomerType = 0;
            ICustomer objCust;
            Console.WriteLine("نوع مشتری را وارد نمایید");           
            intCustomerType = Convert.ToInt16(Console.ReadLine());
            objCust = clsFactory.getCustomerType(intCustomerType);
            objCust.Report();
            Console.ReadLine();
        }
    }
}

مطالب
اصول طراحی شی گرا SOLID - #بخش پنجم اصل DIP

اصل 5)  D – DIP– Dependency Inversion principle 
مقایسه با دنیای واقعی:
همان مثال کامپیوتر را دوباره در نظر بگیرید.این کامپیوتر دارای قطعات مختلفی مانند RAM ، هارد دیسک، CD ROM و ... است که هر کدام به صورت مستقل به مادربرد متصل شده اند. این به این معنی است که اگر قسمتی از کار بیفتد میتوان آن را با یک قطعه‌ی جدید به آسانی تعویض کرد . حالا فقط تصور کنید که تمامی قطعات شدیداً به یکدیگر متصل شده اند آنوقت دیگر نمیتوانستیم قطعه ای را از مادربرد برداریم و به همین خاطر اگر مثلا RAM از کار بیفتد ، یاید یک مادربرد جدید خریداری کنید که برای شما گران تمام می‌شود.
به مثال زیر توجه کنید :
public class CustomerBAL
{
    public void Insert(Customer c)
    {
        try
        {
            //Insert logic
        }
        catch (Exception e)
        {
            FileLogger f = new FileLogger();
            f.LogError(e);
        }
    }
}

public class FileLogger
{
    public void LogError(Exception e)
    {
        //Log Error in a physical file
    }
}
در کد بالا کلاسCustomerBAL مستقیما به کلاس FileLogger وابسته است که استثناء‌های رخ داده را بر روی یک فایل فیزیکی لاگ میکند. حالا فرض کنید که چند روز بعد مدیریت تصمیم میگیرد که از این به بعد استثناء‌ها بر روی یک Event  Viewer لاگ شوند. اکنون چه میکنید؟ با تغییر کدها ممکن است با خطاهای زیادی روبرو شوید(درصورت تعداد بالای کلاسهایی که از کلاس FileLogger استفاده میکنند و فقط تعداد محدودی از آنها نیاز دارند که بر روی Event Viewer لاگ کنند.)
DIP  به ما میگوید : " ماژول‌های سطح بالا نباید به ماژولهای سطح پایین وابسته باشند، هر دو باید به انتزاعات وابسته باشند. انتزاعات نباید وابسته به جزئیات باشند، بلکه جزئیات باید وابسته به انتزاعات باشند. ".
در طراحی ساخت یافته، ماژولهای سطح بالا به ماژولهای سطح پایین وابسته بودند. این مسئله دو مشکل ایجاد می‌کرد:
1-  ماژول‌های سطح بالا (سیاست گذار) به ماژول‌های سطح پایین (مجری) وابسته هستند. در نتیجه هر تغییری در ماژول‌های سطح پایین ممکن است باعث اشکال در ماژول‌های سطح بالا گردد.
2-  استفاده مجدد از ماژول‌های سطح بالا در جاهای دیگر مشکل است، زیرا وابستگی مستقیم به ماژول‌های سطح پایین دارند.
راه حل با توجه به اصل DIP :
public interface ILogger
{
    void LogError(Exception e);
}

public class FileLogger:ILogger
{
    public void LogError(Exception e)
    {
        //Log Error in a physical file
    }
}
public class EventViewerLogger : ILogger
{
    public void LogError(Exception e)
    {
        //Log Error in a Event Viewer
    }
}
public class CustomerBAL
{
    private ILogger _objLogger;
    public CustomerBAL(ILogger objLogger)
    {
        _objLogger = objLogger;
    }

    public void Insert(Customer c)
    {
        try
        {
            //Insert logic
        }
        catch (Exception e)
        {            
            _objLogger.LogError(e);
        }
    }
}
در اینجا وابستگی‌های کلاس CustomerBAL از طریق سازنده آن در اختیارش قرار گرفته است. یک اینترفیس ILogger تعریف شده‌است به همراه دو پیاده سازی مختلف از آن مانند FileLogger و EventViewerLogger. 
یکی از انواع فراخوانی آن نیز می‌تواند به شکل زیر باشد:
var customerBAL = new CustomerBAL (new EventViewerLogger());
customerBAL.LogError();
اطلاعات بیشتر در دوره آموزشی "بررسی مفاهیم معکوس سازی وابستگی‌ها و ابزارهای مرتبط با آن  ".       
مطالب
الگوی طراحی Factory Method به همراه مثال

الگوی طراحی Factory Method به همراه مثال

عناوین :

·   تعریف Factory Method
·   دیاگرام UML
·   شرکت کنندگان در UML
·   مثالی از Factory Pattern در #C 


تعریف الگوی Factory Method :

این الگو پیچیدگی ایجاد اشیاء برای استفاده کننده را پنهان می‌کند. ما با این الگو میتوانیم بدون اینکه کلاس دقیق یک شیئ را مشخص کنیم آن را ایجاد و از آن استفاده کنیم. کلاینت ( استفاده کننده ) معمولا شیئ واقعی را ایجاد نمی‌کند بلکه با یک واسط و یا کلاس انتزاعی (Abstract) در ارتباط است و کل مسئولیت ایجاد کلاس واقعی را به Factory Method می‌سپارد. کلاس Factory Method می‌تواند استاتیک باشد . کلاینت معمولا اطلاعاتی را به متدی استاتیک از این کلاس می‌فرستد و این متد بر اساس آن اطلاعات تصمیم می‌گیرید که کدام یک از پیاده سازی‌ها را برای کلاینت برگرداند.

از مزایای این الگو این است که اگر در نحوه ایجاد اشیاء تغییری رخ دهد هیچ نیازی به تغییر در کد کلاینت‌ها نخواهد بود. در این الگو اصل DIP از اصول پنجگانه SOLID به خوبی رعایت می‌شود چون که مسئولیت ایجاد زیرکلاس‌ها از دوش کلاینت برداشته می‌شود.


دیاگرام UML :

در شکل زیر دیاگرام UML الگوی Factory Method را مشاهده می‌کنید.

        

شرکت کنندگان در این الگو به شرح زیل هستند :

- Iproduct یک واسط است که هر کلاینت  از آن استفاده می‌کند. در اینجا کلاینت استفاده کننده نهایی است مثلا می‌تواند متد main یا هر متدی در کلاسی خارج از این الگو باشد. ما می‌توانیم پیاده سازی‌های مختلفی بر حسب نیاز از واسط Iproduct ایجاد کنیم.

- ConcreteProduct یک پیاده سازی از واسط Iproduct است ، برای این کار بایستی کلاس پیاده سازی (ConcreteProduct) از این واسط (IProduct) مشتق شود.

- Icreator واسطیست که Factory Method را تعریف می‌کند. پیاده ساز این واسط بر اساس اطلاعاتی دریافتی کلاس صحیح را ایجاد می‌کند. این اطلاعات از طریق پارامتر برایش ارسال می‌شوند.همانطور که گفتیم این عملیات بر عهده پیاده ساز این واسط است و ما در این نمودار این وظیفه را فقط بر عهده ConcreteCreator گذاشته ایم که از واسط Icreator مشتق شده است.


پیاده سازی UMLفوق به صورت زیر است:

در ابتدا کلاس واسط IProduct تعریف شده است.

interface IProduct
{
       //  در اینجا  برحسب نیاز فیلدها  و یا امضای متد‌ها قرار می‌گیرند 
}

در این مرحله ما پند پیاده سازی از IProduct انجام می‌دهیم.

class ConcreteProductA : IProduct
{
      // A پیاده سازی 
}
 
class ConcreteProductB : IProduct
{
      // B پیاده سازی 
}
در این مرحله کلاس انتزاعی Creator تعریف می‌شود.
abstract class Creator
{
          // این متد بر اساس نوع ورودی انتخاب مناسب را انجام و باز می‌گرداند
           public abstract IProduct FactoryMethod(string type);
}
در این مرحله ما با ارث بری از Creator متد Abstract آن را به شیوه خودمان پیاده سازی می‌کنیم.
class ConcreteCreator : Creator
{
     public override IProduct FactoryMethod(string type)
    {
            switch (type)
           {
                case "A": return new ConcreteProductA();
                case "B": return new ConcreteProductB();
                default: throw new ArgumentException("Invalid type", "type");
           }
     }
}
مثالی از Factory Pattern در #C :

برای روشن‌تر شدن موضوع ، یک مثال کاملتر ارائه داده می‌شود. در شکل زیر طراحی این برنامه نشان داده شده است.

کد برنامه به شرح زیل است :

using System;

namespace FactoryMethodPatternRealWordConsolApp
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            VehicleFactory factory = new ConcreteVehicleFactory();

            IFactory scooter = factory.GetVehicle("Scooter");
            scooter.Drive(10);

            IFactory bike = factory.GetVehicle("Bike");
            bike.Drive(20);

            Console.ReadKey();

        }
    }

    public interface IFactory
    {
        void Drive(int miles);
    }

    public class Scooter : IFactory
    {
        public void Drive(int miles)
        {
            Console.WriteLine("Drive the Scooter : " + miles.ToString() + "km");
        }
    }

    public class Bike : IFactory
    {
        public void Drive(int miles)
        {
            Console.WriteLine("Drive the Bike : " + miles.ToString() + "km");
        }
    }

    public abstract class VehicleFactory
    {
        public abstract IFactory GetVehicle(string Vehicle);

    }

    public class ConcreteVehicleFactory : VehicleFactory
    {
        public override IFactory GetVehicle(string Vehicle)
        {
            switch (Vehicle)
            {
                case "Scooter":
                    return new Scooter();
                case "Bike":
                    return new Bike();
                default:
                    throw new ApplicationException(string.Format("Vehicle '{0}' cannot be created", Vehicle));
            }
        }
    }
}
خروجی اجرای برنامه فوق به شکل زیر است :






فایل این برنامه ضمیمه شده است، از لینک مقابل دانلود کنید FactoryMethodPatternRealWordConsolApp.zip

در مقالات بعدی مثال‌های کاربردی‌تر و جامع‌تری از این الگو و الگو‌های مرتبط ارائه خواهم کرد... 
مطالب
الگوی استراتژی - Strategy Pattern
الگوی استراتژی (Strategy)  اجازه می­دهد که یک الگوریتم در یک کلاس بسته بندی شود و در زمان اجرا برای تغییر رفتار یک شیئ تعویض شود.
برای مثال فرض کنید که ما در حال طراحی یک برنامه مسیریابی برای یک شبکه هستیم. همانطوریکه می‌دانیم برای مسیر یابی الگوریتم‌های مختلفی وجود دارد که هر کدام دارای مزایا و معایبی هستند. و با توجه به وضعیت موجود شبکه یا عملی که قرار است انجام پذیرد باید الگوریتمی را که دارای بالاترین کارائی است انتخاب کنیم. همچنین این برنامه باید امکانی را به کاربر بدهد که کارائی الگوریتم‌های مختلف را در یک شبکه فرضی بررسی کنید. حالا طراحی پیشنهادی شما برای این مسئله چست؟

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

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

الگوی استراتژی گزینه مناسبی برای مسائلی است که می‌توانند از چندین الگوریتم مختلف به مقصود خود برسند.

نمودار UML الگوی استراتژی به صورت زیر است : 


اجازه بدهید، شیوه کار این الگو را با مثال مربوط به مرتب سازی بررسی کنیم. فرض کنید که ما تصمیم گرفتیم که از سه الگویتم زیر برای مرتب سازی استفاده کنیم.

1 - الگوریتم مرتب سازی Shell Sort
2 - الگوریتم مرتب سازی Quick Sort
3 - الگوریتم مرتب سازی Merge Sort

ما برای مرتب سازی در این برنامه دارای سه استراتژی هستیم. که هر کدام را به عنوان یک کلاس جداگانه در نظر می‌گیریم (همان کلاس‌های ConcreteStrategy ). برای اینکه کلاس Client بتواند به سادگی یک از استراتژی‌ها را انتخاب کنید بهتر است که تمام کلاس‌های استراتزی دارای اینترفیس مشترک باشند. برای این کار می‌توانیم یک کلاس abstract تعریف کنیم و ویژگیهای مشترک کلاس‌های استراتژی را در آن قرار دهیم و کلاس‌های استراتژی آنها را به ارث ببرند(همان کلاس Strategy ) و پیاده سازی کنند.


در زیل کلاس Abstract که کل کلاس‌های استراتژی از آن ارث می‌برند را مشاهده می‌کنید :

abstract class SortStrategy
{
        public abstract void Sort(ArrayList list);
}
کلاس مربوط به QuickSort
class QuickSort : SortStrategy
{
        public override void Sort(ArrayList list)
        {
          // الگوریتم مربوطه   
        }
}

کلاس مربوط به ShellSort

class ShellSort : SortStrategy
{
        public override void Sort(ArrayList list)
        {
          // الگوریتم مربوطه
        }
}
کلاس مربوط به MergeSort
 class MergeSort : SortStrategy
{
        public override void Sort(ArrayList list)
        {
          // الگوریتم مربوطه
        }
}
و در آخر کلاس Context که یکی از استراتژی‌ها را برای مرتب کردن به کار می‌برد :
 class SortedList
{
        private ArrayList list = new ArrayList();
        private SortStrategy sortstrategy;

        public void SetSortStrategy(SortStrategy sortstrategy)
        {
            this.sortstrategy = sortstrategy;
        }
        public void Add(string name)
        {
            list.Add(name);
        }
        public void Sort()
        {
            sortstrategy.Sort(list);
        }
}