اندازهی قلم متن
تخمین مدت زمان مطالعهی مطلب:
سه دقیقه
قسمت سوم آشنایی با Refactoring در حقیقت به تکمیل قسمت قبل که در مورد «استخراج متدها» بود اختصاص دارد و به مبحث «استخراج یک یا چند کلاس از متدها» یا Extract Method Object اختصاص دارد.
زمانیکه کار «استخراج متدها» را شروع میکنیم، پس از مدتی به علت بالا رفتن تعداد متدهای کلاس جاری، به آنچنان شکل و شمایل خوشایند و زیبایی دست پیدا نخواهیم کرد. همچنین اینبار بجای متدی طولانی، با کلاسی طولانی سروکار خواهیم داشت. در این حالت بهتر است از متدهای استخراج شده مرتبط، یک یا چند کلاس جدید تهیه کنیم. به همین جهت به آن Extract Method Object میگویند.
بنابراین مرحلهی اول کار با یک قطعه کد با کیفیت پایین، استخراج متدهایی کوچکتر و مشخصتر، از متدهای طولانی آن است. مرحله بعد، کپسوله کردن این متدها در کلاسهای مجزا و مرتبط با آنها میباشد (logic segregation). بر این اساس که یکی از اصول ابتدایی شیء گرایی این مورد است: هر کلاس باید یک کار را انجام دهد (Single Responsibility Principle).
بنابراین اینبار از نتیجهی حاصل از مرحلهی قبل شروع میکنیم و عملیات Refactoring را ادامه خواهیم داد:
using System.Collections.Generic;
namespace Refactoring.Day2.ExtractMethod.After
{
public class Receipt
{
private IList<decimal> _discounts;
private IList<decimal> _itemTotals;
public decimal CalculateGrandTotal()
{
_discounts = new List<decimal> { 0.1m };
_itemTotals = new List<decimal> { 100m, 200m };
decimal subTotal = CalculateSubTotal();
subTotal = CalculateDiscounts(subTotal);
subTotal = CalculateTax(subTotal);
return subTotal;
}
private decimal CalculateTax(decimal subTotal)
{
decimal tax = subTotal * 0.065m;
subTotal += tax;
return subTotal;
}
private decimal CalculateDiscounts(decimal subTotal)
{
if (_discounts.Count > 0)
{
foreach (decimal discount in _discounts)
subTotal -= discount;
}
return subTotal;
}
private decimal CalculateSubTotal()
{
decimal subTotal = 0m;
foreach (decimal itemTotal in _itemTotals)
subTotal += itemTotal;
return subTotal;
}
}
}
این مثال، همان نمونهی کامل شدهی کد نهایی قسمت قبل است. چند اصلاح هم در آن انجام شده است تا قابل استفاده و مفهومتر شود. عموما متغیرهای خصوصی یک کلاس را به صورت فیلد تعریف میکنند؛ نه خاصیتهای set و get دار. همچنین مثال قبل نیاز به مقدار دهی این فیلدها را هم داشت که در اینجا انجام شده.
اکنون میخواهیم وضعیت این کلاس را بهبود ببخشیم و آنرا از این حالت بسته خارج کنیم:
using System.Collections.Generic;
namespace Refactoring.Day3.ExtractMethodObject.After
{
public class Receipt
{
public IList<decimal> Discounts { get; set; }
public decimal Tax { get; set; }
public IList<decimal> ItemTotals { get; set; }
public decimal CalculateGrandTotal()
{
return new ReceiptCalculator(this).CalculateGrandTotal();
}
}
}
using System.Collections.Generic;
namespace Refactoring.Day3.ExtractMethodObject.After
{
public class ReceiptCalculator
{
Receipt _receipt;
public ReceiptCalculator(Receipt receipt)
{
_receipt = receipt;
}
public decimal CalculateGrandTotal()
{
decimal subTotal = CalculateSubTotal();
subTotal = CalculateDiscounts(subTotal);
subTotal = CalculateTax(subTotal);
return subTotal;
}
private decimal CalculateTax(decimal subTotal)
{
decimal tax = subTotal * _receipt.Tax;
subTotal += tax;
return subTotal;
}
private decimal CalculateDiscounts(decimal subTotal)
{
if (_receipt.Discounts.Count > 0)
{
foreach (decimal discount in _receipt.Discounts)
subTotal -= discount;
}
return subTotal;
}
private decimal CalculateSubTotal()
{
decimal subTotal = 0m;
foreach (decimal itemTotal in _receipt.ItemTotals)
subTotal += itemTotal;
return subTotal;
}
}
}
بهبودهای حاصل شده نسبت به نگارش قبلی آن:
در این مثال کل عملیات محاسباتی به یک کلاس دیگر منتقل شده است. کلاس ReceiptCalculator شیءایی از نوع Receipt را در سازنده خود دریافت کرده و سپس محاسبات لازم را بر روی آن انجام میدهد. همچنین فیلدهای محلی آن تبدیل به خواصی عمومی و قابل تغییر شدهاند. در نگارش قبلی، تخفیفها و مالیات و نحوهی محاسبات به صورت محلی و در همان کلاس تعریف شده بودند. به عبارت دیگر با کدی سروکار داشتیم که قابلیت استفاده مجدد نداشت. نمیتوانست نوعهای مختلفی از Receipt را بپذیرد. نمیشد از آن در برنامهای دیگر هم استفاده کرد. تازه شروع کرده بودیم به جدا سازی منطقهای قسمتهای مختلف محاسبات یک متد اولیه طولانی. همچنین اکنون کلاس ReceiptCalculator تنها عهده دار انجام یک عملیات مشخص است.
البته اگر به کلاس ReceiptCalculator قسمت سوم و کلاس Receipt قسمت دوم دقت کنیم، شاید آنچنان تفاوتی را نتوان حس کرد. اما واقعیت این است که کلاس Receipt قسمت دوم، تنها یک پیش نمایش مختصری از صدها متد موجود در آن است.