It’s always your fault

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

its_all_my_fault_tshirts

ما باید مسئولیت کدی را که تولید می کنیم تازمانیکه آن کد اجرا می شود(تا زمانی که از آن برنامه استفاده می شود) بر عهده بگیریم.این کار باعث ایجاد حس اعتماد و بدست آوردن اعتبار می شود.هیچ شخصی با دور زدن مشکل اعتماد و اعتبار بدست نمی آورد.

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

چند روز پیش موردی برایم پیش آمد که واقعا جالب بود.مدتی است که به شدت deadlockهای روی سیستم بالا رفته از این رو به فکر راه حل افتادیم.یکی از دوستانم سنگ تمام گذاشت و گفت مشکل از sql server است و بهتر است از cassandra  استفاده کنیم.این جا است که باید کمی عمیق تر به جمله It’s always your fault فکر کرد.این جا است که حتی باید کمی عمیق تر فکر کرد.

درست است که بوجود آمدن باگ ممکن است دلایل زیادی داشته باشد.از محیط توسعه گرفته تا تحلیل و از کم خوابی برنامه نویس گرفته تا غذای چربی که دیشب خورده ولی هر چه باشد به عنوان یک وظیفه تا زمانی که ثابت نشده باگ از کد ما نیست،باید در درون کد خودمان دنبال باگ بگردیم.

برداشتی از codinghorror + تجربه های شخصی

اصول Refactoring

تا به اینجای کار با یک مثال ساده با Refactoring به صورت عملی آشنا شدیم ولی قصدم از این مثال آموزش کل Refactoring نبوده است زیرا این مقوله را نمی توان با یک مثال ساده و کوچک به خوبی درک کرد.ولی این مثال به ما کمک کرد که بتوانیم ریتم کلی Refactoring را بشناسیم.

در این پست بد نیست کمی عمیق تر به اصول کلی Refactoring بپردازیم و آن را بررسی کنیم.Refactoring را از دو جنبه کلی می توان بررسی کرد.

  1. (Refactoring)تغییر ساختار داخلی نرم افزار به منظور ساده کردن فهم کد و همچنین کاستن از هزینه تغییرات بدون اینکه رفتار نرم افزار تغییر کند.
  2. (Refactor)تغییر ساختار نرم افزار به وسیله Refactoringهای کوچک بدون اینکه رقتار نرم افزار تحت تاثیر قرار بگیرد.

موضوعی که باید به آن دقت کرد این است که هدف Refactoring درک ساده کد و تغییرات کم هزینه تر است.علاوه بر آن Refactoring باعث تغییر رفتار نمی شود و کاربران و حتی برنامه نویسان دیگر از این تغییرات چیزی نخواهند فهمید.

اگر بخواهم مثالی بزنم بهینه سازی performance را نمی توان  Refactoring نامید زیرا هدفش با Refactoring متفاوت است.در بهینه سازی performance ممکن است کد پیچیده تر شود این در حالتی است که هدف Refactoring ساده تر کردن کد است.

موضوع بعدی این است که اصلاً چرا ما از Refactoring استفاده می کنیم.

  • باعث بهبود کارایی سیستم می شود.
  • باعث فهم آسانتر کد می شود.
  • به پیدا کردن باگها کمک می کند.
  • باعث سریعتر شدن کد می شود.

در پست های آینده درباره هر کدام از موضوعات بالا به تفکیک صحبت خواهیم کرد.

Refactoring قسمت ششم

اگر تا به اینجا مطالب Refactoring را دنبال نکرده اید بهتر است از اینجا شروع کنید.

همانطور که مشاهده کرده اید ما سه نوع متفاوت از Movie داریم و برای محاسبه شارژ از یک switch case استفاده کرده ایم.برای ریفکتور کردن این کلاس اولین راهی که به ذهن می رسد استفاده از یک کلاس پایه به اسم Movie و ایجاد ۳ زیر کلاس به ازای هر نوع از Movie است.

Refactoring

راه بالا خوب است ولی جوابگوی مسئله ما نیست.زیرا ممکن است نوع یک فیلم (در زمان اجرا)تغییر کند بنابراین این راه پاسخ گو نیست.راه دیگر برای این مسئله وجود دارد و آن هم استفاده از State pattern  است.این الگوی برنامه نویسی امکان تغییر همزمان نحوه محاسبه شارژ  بر اساس نوع فیلم را به ما میدهد.

State Pattern

در این حالت به جای ایجاد کلاس های مختلف به ازای هر نوع Movie،کلاسهای مختلف بر اساس نوع محاسبه شارژخواهیم ساخت.پس زمانی که درحال ساختن یک وهله از Movie هستیم در سازنده کلاس priceCode را پاس می دهیم.به وسیله priceCode کد مشخص می شود state مربوط به Movie چه stateی است.بنابراین در هر جایی که نیاز بود نوع Movie تغییر کند کافی است priceCode را تغییر دهیم و این باعث می شود کلیه عملیات محاسبه شارژ نیز تغییر کند.

 public class Movie
    {
        public enum PriceCodes
        {
            Regular = 0,
            NewRelease = 1,
            Childrens = 2
        }

        private Price _price;

        public Movie(string title, PriceCodes priceCode)
        {
            Title = title;
            PriceCode = priceCode;
        }

        public PriceCodes PriceCode
        {
            get { return _price.GetPriceCode(); }
            set
            {
                switch (value)
                {
                    case PriceCodes.Regular:
                    {
                        _price = new RegularPrice();
                        break;
                    }
                    case PriceCodes.NewRelease:
                    {
                        _price = new NewReleasePrice();
                        break;
                    }
                    case PriceCodes.Childrens:
                    {
                        _price = new ChildrensPrice();
                        break;
                    }
                }
            }
        }

        public string Title { get; private set; }

        public double GetCharge(int daysRented)
        {
            return _price.GetCharge(daysRented);
        }

        public int GetFrequentRenterPoints(int daysRented)
        {
            return _price.GetFrequentRenterPoints(daysRented);
        }
    }

همانطور که دربالا می بینید در setter مربوط به PriceCode نوع price را مشخص کرده ایم.علاوه بر این در متد GetCharge و همچنین GetFrequentRenterPoints دیگر خبری از if و یا switch case نیست و کارهای مربوطه را به کلاس های مشتق شده از Price انتقال داد ه ایم.

کد کلاس Price را در زیر مشاهده می کنید:

  public abstract class Price
    {
        public abstract Movie.PriceCodes GetPriceCode();

        public abstract double GetCharge(int daysRented);

        public virtual int GetFrequentRenterPoints(int daysRented)
        {
            return 1;
        }
    }

کلاس Price یک کلاس abstract است که توسط stateهای مختلف پیاده سازی می شود.برای مثال نگاهی به NewReleasePrice خواهیم انداخت:

public class NewReleasePrice : Price
    {
        public override Movie.PriceCodes GetPriceCode()
        {
            return Movie.PriceCodes.NewRelease;
        }

        public override double GetCharge(int daysRented)
        {
            return daysRented * 3;
        }

        public override int GetFrequentRenterPoints(int daysRented)
        {
            return (daysRented > 1) ? 2 : 1;
        }
    }

پس تا به اینجای کار مشاهده کردید که با استفاده از State Pattern توانسیتم نیازهایی مبنی بر تغییر نوع یک فیلم را در زمان اجرا پاسخ دهیم.در حال حاضر روی یک پروژه ساده در حال بهینه سازی هستیم از این رو امکان دارد تمام مزایای Refactoring قابل مشاهده نباشد ولی موضوعی که اهمیت دارد ریتم Refactoring است.یعنی تست ، تغییر کوچک و سپس تست.این ریتم باعث حرکت سریعتر و امن تر می شود.

کدهای این بخش را می توانید از اینجا دانلود کنید.

دیتابیس های Column-oriented

دیتابیس column-oriented چیست؟ قبل از هر چیز بهتر است نگاهی به دیتابیس های سنتی بیاندازیم.دیتابیس های سنتی داده ها را سطر سطر ذخیره می کنند.هر سطر نشان دهنده یک دسته از داده های مشترک است به عنوان مثال مشخص کننده اطلاعات یک فرد.

row-oriented approach

به عنوان مثال اگر بخواهیم داده های ذخیره شده را نمایش دهیم مانند جدول زیر خواهد بود:

name          sex       age
==============================
Alex          male      26
Bettina       female    22
Clara         female    23
Dieter        male      28
Emil          male      29
Frederike     female    27

همچنین داده ها نیز در رم به شکل زیر ذخیره خواهند شد:

address 00 |  A l e x \0 m a l e \0 26
address 11 |  B e t i n a \0 f e m a l e \0 22
address 26 |  C l a r a \0 f e m a l e \0 23
address 40 |  D i e t e r \0 m a l e \0 28
address 53 |  E m i l \0 m a l e \0 29
address 64 |  F r e d e r i k e \0 f e m a l e \0 27

حال فرض کنید بخواهیم آدرس یکی از رکوردها  یا یکی از فیلد ها را حدس بزنیم.در این صورت به صورت مستقیم قادر به این کار نیستیم و نیاز داریم تا تمامی داده ها را جستجو کنیم.به دلیل اینکه که طول متغییر هر رکورد مانع از حدس زدن آدرس آن رکورد می شود.فرض کنید که نیاز است تا متوسط سن افراد را محاسبه کنیم در این صورت نیاز داریم تا آدرس های ۱۰, ۲۵, ۳۹, ۵۲ و ۶۳ را بخوانیم، میبینید که مجبور هستیم آدرس های مختلفی را بخوانیم پس هزینه خواندن نیز بالا می رود زیرا که دادهای ما در پشت سر هم ذخیره نشده اند.

یک دیدگاه متفاوت column-oriented است که اگر با روش قبل مقایسه کنید متوجه می شوید برای نگهداری داده های هر کدام از name،sexو age ها یک آرایه مجزا داریم.column-oriented approachحال داده ها در حافظه به شکل زیر ذخیره می شوند:

address 00 |  A l e x \0 B e t i n a \0 C l a r a \0 D i e t e r \0 E m i l \0 F r e d e r i k e \0
address 40 |  m a l e \0 f e m a l e \0 f e m a l e \0 m a l e \0 m a l e \0 f e m a l e \0
address 77 |  26 22 23 28 29 27

هم اکنون نیز نمی توانیم مکان یکی از nameها را حدس بزنیم.برای مثال برای بازیابی Dieter می توانیم به راحتی ایندکس سوم آرایه را بخوانیم و همچنین او را نیز می توانیم به راحتی بازیابی کنیم:۷۷ + ۳= ۸۰ => 28

اگر جنس داده های ما integer, float, boolean باشد به علت اینکه طول آن ها ثابت است به راحتی می توانیم مکان آن را حدس بزنیم.زیرا می دانیم ایندکس چندم است و همچنین می دانیم که طول آن چقدر است  علاوه بر آن ، برای محاسبه متوسط سن نیز به راحتی می توانیم از خانه ۷۷–۸۲ را بازیابی کنیم.

بنابراین استفاده از دیتابیس های Column-oriented کارایی برنامه را به شدت بالا می برد زیرا هزینه خواندن به شدت پایین خواهد آمد.

State Pattern

در الگوی State رفتار یک کلاس به وسیله یک State عوض می شود.در State Pattern ما یک کلاس پایه داریم  که Stateها متفاوتی از آن به ارث برده و یک کلاس استفاده کننده داریم که با توجه به این Stateها رفتارش تغییر می کند.

state

اگر به دیاگرام بالا توجه کنید می بینید که شی context دارای یک state است.خود کلاس state یک کلاس abstract است که دو کلاس از آن به ارث برده و متد Handle را پیاده سازی می کنند.

برای درک بهتر مثالی می زنم:

   public interface IState
    {
        void DoAction(Context context);
    }

در بالا ما یک interface به نام IState داریم که دارای متد DoAction است.دو State این interface را پیاده سازی می کنند.

public class StartState : IState
    {

        public override String ToString()
        {
            return "Start State";
        }

        public void DoAction(Context context)
        {
            Console.WriteLine("Player is in start state");
            context.State = this;
        }
    }
    public class StopState : IState
    {

        public override String ToString()
        {
            return "Stop State";
        }

        public void DoAction(Context context)
        {
            Console.WriteLine("Player is in stop state");
            context.State = this;
        }
    }

اگر دقت کنید خواهید دید که متد DoAction یک شی Context را به عنوان پارامتر دریافت می کند.سپس State آن را تغییر می دهد.در زیر کلاس Context را نیز مشاهده می کنید.

    public class Context
    {

        public Context()
        {
        }
        public IState State { get; set; }
    }

حال اگر کد زیر را اجرا کنید خواهید دید که می توانید در زمان اجرا به وسیله تغییر State رفتار کلاس Context را تغییر دهید.

            Context context = new Context();
            StartState startState = new StartState();
            startState.DoAction(context);
            Console.WriteLine(context.State);

            StopState stopState = new StopState();
            stopState.DoAction(context);
            Console.WriteLine(context.State);

خروجی کد بالا را در زیر مشاهده می کنید:

Player is in start state
Start State
Player is in stop state
Stop State

نگاهی به معماری Katana (بررسی Applications)

در قسمت های قبلی عنوان شد که قرار نیست Owin مدل جدیدی در سبک برنامه نویسی باشد و دلیل وجودی آن مستقل کردن برنامه از وب سرور خواهد بود.برای مثال زمانی که در حال توسعه یک برنامه Web API هستیم،چه از Owin استفاده کنیم و چه استفاده نکنیم،تنها جایی که کدهای مربوط به OWIN دیده خواهند شد کلاس startup است.

کلاس startup جایی است که برنامه نویسان به وسیله UseXxمی توانند middleware های خود را روی owin pipeline اضافه کنند.این کار می تواندشبیه به تعریف یک HTTP modules در دنیای System.Web باشد.owin

زمانی که برنامه ما به وسیله یک contract با وب سرور صحبت می کند دیگر به آن وب سرور وابسته نیست و هر وب سروری که بتواند با آن زبان صحبت کند می تواند جایگزین شود.

لایه انتزاعی که بین برنامه و وب سرور برقرار می شود باعث می شود در صورتی که نیاز بود هاست ما نیز تغییر کند برنامه با کمترین تغییر مواجه شود.

همانطور که قبلا نیز ذکر شد ایده اصلی OWIN از rack در دنیای روبی گرفته شده است:

Rack: a Ruby Webserver Interface

Rack provides a minimal interface between webservers supporting Ruby and Ruby frameworks.

۱۰ فرمان برای برنامه‌ نویس‌های بی‌ ادعا

ده موردی که در متن زیر خواهید خواند از وبلاگ codinghorror برگرفته شده است.البته استاد نصیری عزیز این مطلب را به اشتراک گذاشته بودند که نظر من را به خودش جلب کرد.موضوع جالب برایم این است که Jeff Atwood این مطلب را از کتابی به اسم The Psychology of Computer Programming برگرفته است که کتابی قدیمی است.ولی همچنان می توان مطالب آن را به کار برد.

۱۰ فرمان

  1. بفهمید و قبول کنید ، شما هم اشتباه میکنید
    نکته پیدا کردن کردن سریع خطاها قبل از مرحله تولید برنامه است.
  2. شما در کد نیستید(کد شما خود شما نیست)
    بیاد داشته باشید، دلیل کلی بازبینی (Review) پیدا کردن مشکلات است که پیدا هم میشوند.اگر هم پیدا نشد خودتان را اذیت نکنید.
  3. مهم نیست چقدر دانش هنرهای رزمی دارید همیشه کسی هست که بیشتر از شما میداند
    اگر شما بخواهید، چنین افرادی فن ها و حرکت های جدیدی به شما یادخواهند.دنبال یادگیری دانش از افراد دیگر باشید و آنهارا قبول کنید، خصوصا وقتی که فکر میکنید نیازی ندارید.
  4. بدون همفکری کد را بازنویسی نکنید
    مرز باریکی بین اصلاح و بازنویسی کد وجود دارد.تفاوت ها را شناسایی  و تغییر سبک ها را در چهارچوب Code Review دنبال کنید اما نه به عنوان یک امر جامع.
  5. با افرادی که کمتر از شما میدانند با احترام ، فروتنی و شکیبایی رفتار کنید
    تقریباً همه افراد غیرحرفه ای که دائماً با توسعه دهندگان برخورد دارند عقیده دارند ، در شرایط خوب هنرپیشه نقش اول و در شرایط بد بهانه گیرهای خوبی هستند.ابن کلیشه را با عصبانیت و بی صبری تقویت نکنید.(میدان دادن به افراد غیر حرفه ای می تواند به آن ها خیلی کمک کند)
  6. تنها حقیقت ثابت دنیا تغییر است
    با آغوش باز آن را بپذیرید و قبول کنید.به چشم یک نیاز،بستر و ابزار برای یک چالش جدید به تغییرات نگاه کنید ، نه به عنوان یک عامل سختی که باید با آن مبارزه کنید.
  7. تنها قدرت واقعی از دانش سرچشمه میگیرد نه از مقام وجایگاه
    دانش قدرت ایجادمیکند و قدرت آبستن احترام است، پس اگربه دنبال احترام در محیطی بی ادعا هستید ، دانش ترویج کنید.
  8. برای اعتقاداتتان بجنگید اما با افتخار شکست را بپذیرید
    درک کنید که گاهی اوقات ،حتی اگر اصلاحاتی هم ایجاد کرده باشید.امکان رد شدن ایده ها وجود دارد. اما بیاد داشته باشید هیچگاه نباید انتقام بگیربد یا با رفتارهای اشتباه ایده قلبی خود را بی ارزش کنید.
  9. “the guy in the room” نباشید
    این اصطلاح به برنامه نویس نابغه ای اشاره دارد که با کسی صحبت نمیکند و گوشه گیر است.(برنامه نویس معروف فیلم های هالیوودی)
    مثل یه برنامه نویسی  که در اتاقی تاریک  کار میکند و تنها هنگام خرید کولا دیده میشود نباشید. چنین فردی دست نیافتنی است و هیچ جایی در محیط  مشارکتی و کارگروهی ندارد.
  10. به جای انسانها از کدها انتقاد کنید، با برنامه نویس مهربان باشید نه با کد
    تا حد ممکن ،نظراتی مثبت و  درجهت بهبود کد بدهید.نظراتی مرتبط با استاندارد ها، مشخصات برنامه و افزایش عملکرد و…(البته خیلی از موارد به شخصه دیده ام که انتقاد به کد معنای انتقاد به افراد است.به طور مثال به محض این که می گوییم یک جایی از کد مشکل دارد،به برنامه نویسش بر میخورد.این موضوع برای خود من هم پیش آمده.ولی اگر بتوانیم این فرهنگ را در یک تیم برنامه نویسی نهادینه کنیم،کیفیت کدها بسیار بالاتر خواهد رفت.)

منبع

نگاهی به معماری Katana (بررسی Middleware)

در قسمت های قبلی با استاندارد  Owin و همچنین پیاده سازی آن توسط مایکروسافت،یعنی Katana آشنا شدید.سعی کردم که معماری هاست و سرور را  به طور مجزا توضیح دهم.در این قسمت با یکی از زیباترین اجزای katana یعنی همان Middleware آشنا خواهیم شد.

گفتیم که وظیفه سرور باز کردن سوکت است تا زمانی که یک request از سمت کلاینت به دستش رسید ،بتواند آن را به Owin pipeline ارسال کند.در واقع اجزای  pipeline را می توان middleware نامید.یک middleware در پایه ترین حالت نیاز به پیاده سازی

Func<IDictionary<string, object>, Task> 

دارد.

در کل می توان middlewareها را مثل یک زنجیر تصور کرد که یک درخواست http بین حلقه های این زنجیر حرکت می کند.

البته راههای دیگری نیز برای پیاده سازی یک middleware وجود دارد.به عنوان مثال به راحتی می توانیم از کلاس OwinMiddleware ارث برده و یک middleware جدید ایجاد کنیم.

public class LoggerMiddleware : OwinMiddleware
{
    private readonly ILog _logger;

    public LoggerMiddleware(OwinMiddleware next, ILog logger) : base(next)
    {
        _logger = logger;
    }

    public override async Task Invoke(IOwinContext context)
    {
        _logger.LogInfo("Middleware begin");
        await this.Next.Invoke(context);
        _logger.LogInfo("Middleware end");
    }
}

کد بالا یک لاگر بسیار ساده است.همانطور که مشاهده می کنید در constructor کلاس LoggerMiddleware یک وهله از OwinMiddleware بعدی دریافت می کنیم.منظور از OwinMiddleware بعدی یعنی بعد از این کلاس قرار است چه middlewareی اجرا شود.

OwinPipline

در زمان اجرا  متد invokeی که override شده فراخوانی شده ،سپس توسط this.Next.Invoke شی context به middleware بعدی انتقال داده می شود.در شی context همان دیکشنری وجود دارد که از طریق آن می توانیم به پارامتراهای موجود در یک request و response دسترسی پیدا کنیم.

بعد از اینکه یک middleware را نوشتیم باید آن را به برنامه معرفی کنیم.این کار در کلاس startup انجام می دهیم.

public class Startup
{
   public void Configuration(IAppBuilder app)
   {
      app.Use<LoggerMiddleware>(new TraceLogger());

   }
}

همانطور که می بینید به راحتی می توانیم یک middleware را به owin pipeline اضافه کنیم.

فقط توجه به این نکته ضروری است که middleware ها به ترتیتی که در کلاس startup نوشته شده اند،فراخوانی می شوند.

Refactoring قسمت پنجم

اگر تا به اینجا مطالب Refactoring را دنبال نکرده اید بهتر است از اینجا شروع کنید.

زمانی که ما از switch  case  استفاده می کنیم باید به نکاتی توجه کنیم.یکی از آن ها این است که اگر از switch در کلاسی استفاده می کنید که بر پایه کلاس دیگر است این کار اشتباه است.یعنی اگر از اطلاعات کلاس دیگری استفاده می کند.به عنوان مثال در کلاس Rental ما switchی بر پایه نوع Movie استفاده می کنیم و در صورتی که یک تایپ جدید به Movieها اضافه شود در این صورت علاوه بر کلاس Movie کلاس Rental نیز تغییر خواهد کرد.

پس بهتر است به جای اینکه ما در getChargeی که در Rental است switchی برپایه نوع Movie داشته باشیم،آن را به کلاس Movie انتقال دهیم.

علاوه بر getCharge،متد GetFrequentRenterPoints نیز شرایط بالا را داراست،از این رو این متد را نیز به Movie انتقال خواهیم دارد.

  public double GetCharge(int daysRented)
        {
            double result = 0;
            switch (PriceCode)
            {
                case PriceCodes.Regular:
                    result += 2;
                    if (daysRented > 2)
                        result += (daysRented - 2) * 1.5;
                    break;
                case PriceCodes.NewRelease:
                    result += daysRented * 3;
                    break;
                case PriceCodes.Childrens:
                    result += 1.5;
                    if (daysRented > 3) result += (daysRented - 3) * 1.5;
                    break;
            }
            return result;
        }
        public int GetFrequentRenterPoints(int daysRented)
        {
            if ((PriceCode == PriceCodes.NewRelease) && daysRented > 1)
                return 2;
            return 1;
        }

بعد از تغییرات کلاس Rental به شکل زیر خواهد شد:


    public class Rental
    {

        public Rental(Movie movie, int daysRented)
        {
            Movie = movie;
            DaysRented = daysRented;
        }
        public int DaysRented { get; private set; }
        public Movie Movie { get; private set; }
        public double GetCharge()
        {
            return Movie.GetCharge(DaysRented);
        }
        public int GetFrequentRenterPoints()
        {
            return Movie.GetFrequentRenterPoints(DaysRented);
        }

    }

در این صورت اگر کلاس Movie تغییر کند دیگر نیاز نیست کلاس Rental را تغییر دهیم.

معنای انتزاع

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

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

درک مفهوم صحیح انتزاع ما را به شناسایی بهتر مشکلات دامین هدایت می کند.

abstraction

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

این کار ما را به سمت ،از خارج نگاه کردن به یک شی هدایت می کند.نگاه از بیرون به یگ شی، contractی را ایجاد می کند که سایر اشیا به آن وابسته هستند و علاوه بر آن مشخص کننده عمل داخل خود شی هستنند.

در واقع contract مشخص کننده مسئولیت یک شی است.

مثالی از انتزاع

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

موضوع دیگری که مطرح می شود این است که هر گیاه ممکن است برنامه رشد خود را داشته باشد .به طور مثال به مقدار نور مشخص و دمای مشخصی برای رشد نیاز داشته باشد.

تا به اینجا ما با دو Domain problem سروکار داریم یکی مانیتور کردن یک سری از عوامل موثر و دیگری تعیین برنامه غذایی با توجه به نوع گیاه. Temperature Sensor

حال فرض کنید که نیاز است برای دما ما به وسیله یک سنسور که در مکانی مناسب قرار  دارد،یک قسمت از سیستم مانیتورینگ را توسعه دهیم در این حالت ابتدا ما یک سری وظیفه را در نظر میگیریم.برای مثال قرار است که زمانی که از سنسور ،دمای محیط پرسیده شد به ما اطلاع بدهد.مورد بعدی که باید به آن توجه کرد این است که هر زمان که دما تغییر کرد بتواند آن را جایی ثبت کند.علاوه بر آن ما نیاز داریم تا یک آستانه نیز برای آن تعریف کنیم به عنوان مثال اگر دما از مقداری بالاتر یا پایین تر نیز آمد به ما اطلاع بدهد.Active Temperature Sensor

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

علاوه بر سنسور قرار شد که برای برنامه رشد هر گیاه نیز قسمتی را توسعه دهیم.از این رو نام این قسمت را می توان  Growing Planگذاشت.این قسمت نیز مانند سنسور برای انجام اعمال خود نیاز به یک سری اطلاعات و علاوه بر آن یک سری وظیفه دارد.

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

Related Candidate Abstractions: Crop, Conditions, Plan Controll

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

به عبارتی واضح تر abstraction یعنی دور ریختن اطلاعاتی که از یک شی به دردمان نمی خورد و نگه داشتن اطلاعات اصلی که شخصیت یک شی را مشخص می کند.