Type Code یا همان Enumeration ها، نوع داده هایی هستند که تعداد محدودی مقدار دارند که این مقادیر برای مشخص کردن یک حالت خاص به کار می روند. به هنگام استفاده از Type code ها سه وضعیت به وجود می آید؛

  •  Type code مبنای تصمیم گیری نیست
  •  Type code مبنای تصمیم گیری است و پس از انتصاب آن به یک شی، تغییر نمی کند
  •  Type code مبنای تصمیم گیری است و پس از انتصاب آن به یک شی، امکان تغییر آن وجود دارد

با توجه به هریک از این وضعیت ها الگوی خاصی برای بازآرایی [1] در کتاب ارزشمند [2] آورده شده است.

برای روشن شدن بحث از یک مثال ملموس استفاده شده است. انسان ها در جامعه یا ثروتمند هستند و یا فقیر. یک راه کار برای نشان دادن این وضعیت تصویر کردن دامنه این حالت به اعداد است. (مثلا عدد 1 نشان دهنده وضعیت ثروتمند و عدد 2 نشان دهنده وضعیت فقیر). یک راه کار برای آشکار کردن این اطلاعات ضمنی نام دادن به هر یک از این حالات است. راه حلی مانند آنچه در شکل زیر آمده است، با توجه به اینکه دامنه مقادیر ورودی به سازنده این کلاس محدود به اعداد 1و2 نیست، منشا بسیاری از مشکلات است.


public class Person
{
    public static int Wealthy => 1;
    public static int Poor => 2;
    public int Status { get; set; }

    private Person()
    {
    }

    public Person(int status) : this()
    {
        Status = status;
    }
}


پیشنهاد Fowler برای غلبه بر این مشکل (محدود کردن دامنه انتخاب مقدار) به این صورت است. در واقع در این راه حل با بهره گیری از Type system دامنه انتخاب مقدار برای وضعیت مالی محدود شده است.


public class Person
{
    public Status Status { get; set; }
    private Person()
    {

    }
    public Person(Status status) : this()
    {
        Status = status;
    }
}

public class Status
{
    public int StatusNumber { get; }
    public static Status Wealthy = new Status(1);
    public static Status Poor = new Status(2);
    private Status()
    {
    }
    private Status(int statusNumber) : this()
    {
        StatusNumber = statusNumber;
    }
}


اما در زبان #C  با توجه به اینکه Enumeration ها strongly typed هستند، برای وضعیتی که Type code تغییر نمی کند و مبنای تصمیم گیری نیست می توان با کد زیر این وضعیت را مدل کرد.


public class Person
{
    public Status Status { get; set; }

    private Person()
    {
    }

    public Person(Status status) : this()
    {
        Status = status;
    }
}

public enum Status
{
    Wealthy,
    Poor
}

اما در وضعیت دوم، یعنی زمانی که Type code مبنای تصمیم گیری است (یارانه افراد بر اساس وضعیت مالی آنها پرداخت می شود) و امکان تغییر آن وجود ندارد (فرد فقیر ثروتمند نمی شود و برعکس). یک پیاده سازی برای این وضعیت در شکل زیر آمده است. ایراد این پیاده سازی استفاده از Switch روی حالت است. این کار باعث نقض اصل OCP می شود چرا که با اضافه شدن یک حالت دیگر (مثلا حالت متوسط اقصادی) تابع GetSubsidy تغییر می کند.


public class Person
{
    public Status Status { get; set; }
    private Person()
    {
    }
    public Person(Status status) : this()
    {
        Status = status;
    }
    public decimal GetSubsidy()
    {
        switch (Status)
        {
            case Status.Wealthy:
                return 0m;
            case Status.Poor:
                return 100m;
            default:
                throw new ArgumentOutOfRangeException();
        }
    }
}

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


public abstract class Person
{
    public abstract decimal GetSubsidy();
}

public class WealtyPerson : Person
{
    public override decimal GetSubsidy()
    {
        return 0m;
    }
}

public class PoorPerson : Person
{
    public override decimal GetSubsidy()
    {
        return 100m;
    }
}

اما در وضعیت سوم که Type code مبنای تصمیم گیری است و مقدار آن برای شی ای که به آن تعلق دارد تغییر می کند (امکان ثروتمند شدن فقرا و برعکس وجود دارد) استفاده از پیاده سازی قبلی باعث می شود برای هر تغییر حالت، یک شی جدید ایجاد شود. به علاوه با توجه به اینکه شی انسان قاعدتا از نوع Entity است، می بایست mutable باشد (توصیه DDD) که با این پیاده سازی Immutable خواهد بود. در این وضعیت با رفتار وابسته به حالت رو به رو هستیم که معرف Polymorphism در شی گرایی است. برای باز آرایی این حالت از الگوی State و یا Strategy استفاده می شود.  در این حالت تغییر وضعیت مالی با استفاده از Interface از کلاس Person پنهان می شود.


public abstract class Person
{
    private IPersonState _personState;

    private Person()
    {
    }

    protected Person(IPersonState personState) : this()
    {
        _personState = personState;
    }
}

public interface IPersonState
{
    decimal GetSubsidy();
}

public class WealtyPerson : IPersonState
{
    public decimal GetSubsidy()
    {
        return 0m;
    }
}

public class PoorPerson : IPersonState
{
    public decimal GetSubsidy()
    {
        return 100m;
    }
}



[1] - Refactoring

[2] - Refactoring: Improving the Design of Existing Code