เปลี่ยนเป็น MVVM กันเถอะ

1. เกรินนำ

การเขียนโปรแกรมประยุกต์ ด้วยการปฏิบัติ และการออกแบบสถาปัตยกรรมที่ดี พิสูจน์แล้วว่า เป็นเรื่องง่ายโดยเปรียบเทียบกับที่พวกเราต้องไปเขียนโปรแกรมประยุกต์ใหม่ แต่เวลาส่วนใหญ่ของเรากำลังทำงานกับโปรแกรมที่มีอยู่ ที่การเปลี่ยนแปลงนั้นไม่ใช่เรื่องง่ายที่จะทำ ถ้าเราเคยได้รับโอกาสที่จะต้องเขียนโปรแกรมที่มีอยู่แล้ว เราจะต้องหลีกเลี่ยงการทำซ้ำความผิดพลาดเหมือนกับคนอื่น เหตุผลของความผิดนั้นอาจจะเนื่องจาก ระยะเวลา, ข้อจำกัดทางเทคโนโลยี, ขอบเขตและอื่นๆ ซึ่งมันจะดีกว่าถ้าเราสามารถ refactor codebases ที่มีอยู่ ให้ดีขึ้น โดยที่ไม่มีหรือมีความเสี่ยงต่ำที่สุด

MVVM ได้รับการพิสูจน์แล้ว เพราะว่า Design Pattern นี้ ถูกใช้อย่างมากใน WPF / Silverlight Application แต่มันอาจจะเป็นไปได้ว่า เรามี codebase จำนวนมาก ที่ไม่ได้ใช้ประโยชน์จาก MVVM Design Pattern บทความนี้ สำหรับผู้ที่มี Application อยู่แล้ว และต้องการที่จะใช้ประโยชน์จาก MVVM มากกว่าการเริ่มต้นเขียน application ใหม่ทั้งหมด ในบทความผมจะเสนอขั้นตอนไปทีละนิดจนกระทั้งเป็น MVVM ได้อย่างไร มีสองส่วนสำคัญในบทความ ส่วนแรกเราจะศึกษา วิวัฒนาการของ MVVM ส่วนที่สองเราจะดูขั้นตอนที่ทำให้ codebase ของเรากลายเป็น MVVM ทุกๆขั้นตอนอาจจะมีความเสี่ยงบ้าง แต่ผมได้พยายามลดความเสี่ยงให้ไม่มี หรือมีน้อยที่สุดแล้ว

2. วิวัฒนาการของ MVVM

มาพิจารณาที่ MVVM จากระดับบนก่อน โดยผมจะนำเสนอเป็นแบบ step by step เพื่อที่จะทำความเข้าใจไปพร้อมๆกับคุณไปเรื่อยๆ การอธิบายจะขึ้นอยู่กับความซับซ้อนของสถาปัตยกรรมจากง่ายไปยังซับซ้อนมาก ไม่ได้มาจากลำดับทางประวัติศาสตร์

เริ่มต้นที่ Observer Design Patterns นี่น่าจะเป็นหลัก pattern ที่ออกแบบมาง่ายที่สุดแล้ว สำหรับการแยกส่วน การนำเสนอข้อมูล(view) ออกจาก ตัวข้อมูล(model) ใน Observer Design Pattern เรามีสอง class ที่แตกต่างกัน class หนึ่ง สำหรับข้อมูล(subject/model) และ อีก class หนึ่งสำหรับส่วนนำเสนอ (observer/view) ที่ class Subject มีอินสแตนซ์ทั้งหมดของผู้สังเกตุการณ์ และส่งการแจ้งเตือนทั้งหมดของผู้สังเกตการณ์เมื่อมีการ เปลี่ยนแปลงข้อมูลใดๆ ที่แสดงข้างล่างนี้คือ diagram สถาปัตยกรรมของ Observer Design Patterns

ขั้นตอนต่อไป เป็นการแนะนำอีก pattern ที่มี layer กลางที่เรียกกันว่าตัวควบคุม(Controller) เข้ามาขั้นอยู่ในระหว่างข้อมูล(Model)และ ส่วนการนำเสนอ(View) โดยวัตถุประสงค์หลักของ layer ตัวควบคุมนี้ก็คือ การจัดการติดต่อสื่อสารระหว่างทั้งสองส่วนนำเสนอ และตัวข้อมูล นี่เป็นแนวคิดหลักของ pattern ที่มีนามว่า Model View Controller Pattern หรือ MVC Patterns นั่นเอง

ที่แสดงข้างล่างนี้คือ diagram สถาปัตยกรรมของ MVC Patterns

วิธีการนี้จะมีข้อดีและข้อเสียบางอย่าง ข้อเสียที่สำคัญคือ ส่วนนำเสนอ(View) ทั้งหมดของเราไม่ได้เป็นอิสระออกจากข้อมูล(Model) ของเรา ดังนั้นจึงได้เกิด patterns ที่ชื่อว่า Model View Presenter (MVP) Patterns ขึ้นเพื่อจัดการกับปัญหานี้ได้ ใน MVP Pattenrs ได้ตัดความสัมพันธ์ระหว่าง View และ Model ได้โดยสิ้นเชิง ที่แสดงข้างล่างนี้คือ diagram สถาปัตยกรรมของ MVP Patterns

       สถาปัตยกรรมของ MVVM จะคล้ายๆกับสถาปัตยกรรม MVP หรือบางทีมันอาจจะเป็นเพียงรูปแบบการจัดเรียงเฉพาะของ MVP ในแบบ MVVM เท่านั้นเองก็ได้ ใน MVVM มี Presentator ที่จะรู้จักกับ ViewModel, Model ติดต่อสื่อสารกับ ViewModel โดยการแจ้งเตือน(Notification), ViewModel ติดต่อสื่อสารกับ View ด้วยกลไกของ Data Binding และ Command Binding ที่แสดงข้างล่างนี้คือ diagram สถาปัตยกรรมของ MVVM Patterns

 

ตอนนี้เรามาดู MVVM กันอีกเล็กน้อยก่อนที่จะลงไปในรายละเอียด ประโยชน์ที่เราได้รับมากที่สุดของสถาปัตยกรรม MVVM คืออะไร? ประโยชน์อันแรกเลยก็คือ การนำเสนอ(View)ของเราทั้งหมด ไม่รู้จัก Model ของเราเลย เราจึงไม่จำเป็นต้องเขียน code ใดๆที่เกี่ยวกับ การนำเสนอใดๆเลยใน ViewModel และทุกการสื่อสารอยู่บนมูลฐานของกลไก Data Binding และ Command Binding นั่นหมายความว่า เราสามารถเขียน Unit Test เพื่อทดสอบมันได้ง่ายๆ เราสามารถเปลี่ยนส่วนติดต่อผู้ใช้ใดๆ หรือแม้กระทั่งการเปลี่ยนแปลงรูปแบบข้อมูลได้อย่างง่ายๆ นี่คือไดอะแกรมแสดงรายละเอียดของรูปแบบการประยุกต์ใช้ของ MVVM

แผนภาพนี้จะอธิบายถึงวิธีการที่เราสามารถใช้ประโยชน์จาก MVVM สิ่งที่สำคัญที่สุดใน pattenr นี้คือการออกแบบ ViewModel ให้เหมาะสม WPF มีการออกแบบที่แยกโดยอิสระมาก(very orthogonal design) ซึ่งหมายความว่าเราสามารถปรับแต่งหรือปรับปรุงส่วนต่างๆของ library ได้โดยไม่ต้องมีผลกระทบต่อส่วนอื่นเลย โดยส่วนใหญ่ถูกนำมาใช้ใหม่ WPF จะขึ้นอยู่กับองค์ประกอบมากกว่าการสืบทอด ดังนั้นเราจึงสามารถใช้ประโยชน์ได้สูงสุดแม้ในขณะ run time เพราะพวกเราสามารถเปลี่ยนแปลงการทำงานขององค์ประกอบ ในขณะ run time ได้ง่ายๆ เนื่องจากส่วนประกอบไม่ได้รับการถ่ายทอด ในแผนภาพนี้เราจะเห็นส่วนประกอบสำคัญของ library WPF ที่เราพัฒนาปรับปรุงคุณภาพหรือปรับแต่งในการใช้งาน WPF มากที่สุด

3. มาเปลี่ยนเป็น MVVM กันเถอะ

เราจะมาเริ่มตัวอย่างง่ายๆ สมมุติเรามี class เก็บ information เกี่ยวกับการจ่าย ของเราอยู่ คือ

    public class PaymentInfo
    {
        public int PaymentNo { get; set; }

        public double Payment { get; set; }

        public double Principle { get; set; }

        public double Interest { get; set; }

        public double Balance { get; set; }
    }

มาดูในส่วนของ code behide กัน

เรามีตัวแปรเพื่อเก็บ information ที่รับจาก user interface อยู่กลุ่มหนึ่ง คือ

        private double principle;
        private double interestRate;
        private int duration;
        private double payment;

นี่เป็น code ส่วนหนึ่งที่เอาค่าจาก user interface ไปเก็บไว้ในตัวแปร

            if (txtPrincipleAmount.Text.Length > 0)
            {
                principle = Convert.ToDouble(txtPrincipleAmount.Text);
            }
            else
            {
                MessageBox.Show("Please enter principle amount",
                "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            if (txtInterestRate.Text.Length > 0)
            {
                interestRate = Convert.ToDouble(txtInterestRate.Text);
                interestRate /= 100;
                interestRate /= 12;
            }
            else
            {
                MessageBox.Show("Please enter interest",
                "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            if (txtDuration.Text.Length > 0)
            {
                duration = Convert.ToInt32(txtDuration.Text);
            }
            else
            {
                MessageBox.Show("Please enter duration",
                "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

เรามี method logic อยู่ส่วนหนึ่งเพื่องานนี้

   
//การคำนวณยอดเงินคงเหลือที่ชำระเงินโดยเฉพาะ
        private double CalculateBalance(int month)
        {
            double interestTerm = Math.Pow((1 + interestRate), month);
            double totalInterest = principle * interestTerm;
            double totalPaid = payment * (interestTerm - 1) / interestRate;
            return totalInterest - totalPaid;
        }
//ส่วนการคำนวณดอกเบี้ยของการชำระเงินรายการใด ๆ

        private double CalculateInterestPart(int month)
        {
            double interestTerm = Math.Pow((1 + interestRate), (month - 1));
            double totalInterest = principle * interestTerm;
            double totalPaid = payment * (interestTerm - 1) / interestRate;
            return (totalInterest - totalPaid) * interestRate;
        }

//การคำนวณส่วนหนึ่งที่สำคัญของการชำระเงินรายการใด ๆ

        private double CalculatePrinciple(int month)
        {
            return payment - CalculateInterestPart(month);
        }

 และเรามีการคำนวณอะไรเล็กน้อยแล้วแสดงผลไว้ใน DataGrid

// ตารางการคำนวณค่าตัดจำหน่ายที่สมบูรณ์และกรอกข้อมูลใส่ data grid
        private void CalculatePayment()
        {
            int totalpayments = duration * 12;

            Title = "Amortization Schedule for " + Convert.ToString(totalpayments) + " Payments";

            // คำนวณดอกเบี้ย
            double interestTerm = Math.Pow((1 + interestRate), totalpayments);

            // คำนวณการชำระเงิน
            payment = (principle * interestRate) / (1 - (1 / interestTerm));

            payments.Clear();

            for (int iIndex = 1; iIndex <= totalpayments; ++iIndex)
            {
                PaymentInfo paymentInfo = new PaymentInfo();
                paymentInfo.PaymentNo = iIndex;
                paymentInfo.Balance = CalculateBalance(iIndex);
                paymentInfo.Payment = payment;
                paymentInfo.Interest = CalculateInterestPart(iIndex);
                paymentInfo.Principle = CalculatePrinciple(iIndex);

                payments.Add(paymentInfo);
            }

            lstAmortization.ItemsSource = payments;
        }

จะเห็นว่า code ส่วนของ logic เราอยู่ในส่วนการนำเสนอทั้งหมด ไม่มีหนทางใดเลยที่เราจะเขียนตัวทดสอบโดยที่จะไม่ไปกระทบส่วนการนำเสนอผู้ใช้

XAML projects ของเรานั้น ง่ายมาก นี่คือรหัส XAML ที่สมบูรณ์แล้วของโปรแกรมเรา

<Window x:Class="MVVM.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="400" Width="600">
    <Grid Background="Beige">
        <Grid.RowDefinitions>
            <RowDefinition Height="2*"/>
            <RowDefinition Height="2*"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <TextBlock Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" FontSize="18" FontWeight="Bold"
                       HorizontalAlignment="Center"
                       VerticalAlignment="Center">Loan Amortization</TextBlock>
            <TextBlock Grid.Column="0" Grid.Row="1" VerticalAlignment="Center" Margin="5">Principle Amount</TextBlock>
            <TextBlock Grid.Column="0" Grid.Row="2" VerticalAlignment="Center" Margin="5">Interest Rate</TextBlock>
            <TextBlock Grid.Column="0" Grid.Row="3" VerticalAlignment="Center" Margin="5">Duration</TextBlock>
            <TextBox Grid.Column="1" Grid.Row="1" Margin="5" VerticalAlignment="Center" Name="txtPrincipleAmount"/>
            <TextBox Grid.Column="1" Grid.Row="2" Margin="5" VerticalAlignment="Center" Name="txtInterestRate"/>
            <TextBox Grid.Column="1" Grid.Row="3" Margin="5" VerticalAlignment="Center" Name="txtDuration"/>
        </Grid>
        <DataGrid Grid.Row="1" Name="lstAmortization" Margin="5">
        </DataGrid>
        <Grid Grid.Row="2">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Button Grid.Column="0" Name="btnCalculate" Width="75" Height="45" Click="btnCalculate_Click">Calculate</Button>
            <Button Grid.Column="1" Name="btnExit" Width="75" Height="45" Click="btnExit_Click">Exit</Button>
        </Grid>
    </Grid>
</Window>

ต่อไปนี้เป็นขั้นตอนไปสู่ MVVM

3.1. ขั้นที่ 1: ใช้ Properties

เปลี่ยนตัวแปรทั้งหมดของเราไปเป็น Properties ได้ code ตามนี้

        public double Principle { get; set; }
        public double InterestRate { get; set; }
        public int Duration { get; set; }
        public double Payment { get; set; }

3.2. ขั้นที่ 2: ใช้ interface INotifyPropertyChanged และ Dependency Property

เปลี่ยน properties ไปเป็น dependency properties ได้ code ตามนี้

        public static readonly DependencyProperty PrincipleProperty = DependencyProperty.Register("Principle", typeof(double), typeof(MainWindow));

        public double Principle
        {
            get { return (double)GetValue(PrincipleProperty); }
            set { SetValue(PrincipleProperty, value); }
        }
        public static readonly DependencyProperty InterestRateProperty = DependencyProperty.Register("InterestRate", typeof(double), typeof(MainWindow));

        public double InterestRate
        {
            get { return (double)GetValue(InterestRateProperty); }
            set { SetValue(InterestRateProperty, value); }
        }
        public static readonly DependencyProperty DurationProperty = DependencyProperty.Register("Duration", typeof(int), typeof(MainWindow));

        public int Duration
        {
            get { return (int)GetValue(DurationProperty); }
            set { SetValue(DurationProperty, value); }
        }
        public static readonly DependencyProperty PaymentProperty = DependencyProperty.Register("Payment", typeof(double), typeof(MainWindow));

        public double Payment
        {
            get { return (double)GetValue(PaymentProperty); }
            set { SetValue(PaymentProperty, value); }
        }

ยังไม่มีความเปลี่ยนแปลงใน XAML file

3.3. ขั้นที่ 3: ใช้ Data Binding

ขั้นตอนต่อไปคือการใช้ data binding ขั้นตอนนี้จะช่วยลด codebase จำนวนมาก เมื่อผ่านขั้นตอนนี้ก็ไม่ควรจะมี code แบบนี้

  
        txtName.Text = firstName;

ตัว validation field จะได้ code แบบนี้

            if (Principle < 0)
            {
                MessageBox.Show("Please enter principle amount",
                "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            if (InterestRate < 0)
            {
                MessageBox.Show("Please enter interest", "Error",
                MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            if (Duration < 0)
            {
                MessageBox.Show("Please enter duration",
                "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

ตอนนี้เราต้องปรับปรุง XAML ของเราและใช้ data binding

        <TextBox Grid.Column="1" Grid.Row="1" Margin="5" VerticalAlignment="Center" Text="{Binding Principle}" />
        <TextBox Grid.Column="1" Grid.Row="2" Margin="5" VerticalAlignment="Center" Text="{Binding InterestRate}"/>
        <TextBox Grid.Column="1" Grid.Row="3" Margin="5" VerticalAlignment="Center" Text="{Binding Duration}"/>

3.4. ขั้นที่ 4: เปลี่ยน code เป็น Event Handler

ใน event hanlder ให้ handler ได้แบบนี้

        private void btnCalculate_Click(object sender, RoutedEventArgs e)
        {
            CalculateAmortization();
        }

        private void CalculateAmortization()
        {
            ValidateInput();
            CalculatePayment();
        }

3.5. ขั้นที่ 5: ใช้ interface ICommand

implement แล้วได้ code หน้าตาเป็นแบบนี้

        public class MyCommand : ICommand
        {
            public Action Function
            { get; set; }

            public MyCommand()
            {
            }

            public MyCommand(Action function)
            {
                Function = function;
            }

            public bool CanExecute(object parameter)
            {
                if (Function != null)
                {
                    return true;
                }

                return false;
            }

            public void Execute(object parameter)
            {
                if (Function != null) { Function(); }
            }
            public event EventHandler CanExecuteChanged
            {
                add { CommandManager.RequerySuggested += value; }
                remove { CommandManager.RequerySuggested -= value; }
            }
        }

ขั้นตอนนี้ไม่มีการเปลี่ยนแปลงใน XAML file

3.6. ขั้นที่ 6: เพิ่ม properties ICommand

เพิ่ม properties ICommand ลงไป

        public ICommand ExitCommand { get; set; }
        public ICommand CalculateAmortizationCommand { get; set; }

3.7. ขั้นที่ 7: วิธีการกำหนดให้ กับคุณสมบัติของ ICommand Type

กำหนด command ให้ ICommand

            ExitCommand = new MyCommand(Close);
            CalculateAmortizationCommand = new MyCommand(CalculateAmortization);

3.8. ขั้นที่ 8: ใช้ Command Binding

แก้ XAML file เพื่อใช้ Command Binding

        <Button Grid.Column="0" Name="btnCalculate" Width="75" Height="45" Command="{Binding CalculateAmortizationCommand}" >Calculate</Button>
        <Button Grid.Column="1" Name="btnExit" Width="75" Height="45" Command="{Binding ExitCommand}" >Exit</Button>

3.9. ขั้นที่ 9: กำหนด Class ViewModel

ให้ class ViewModel handle เหตุการณ์ close

        public event EventHandler RequestClose;

        private void CloseWindow()
        {
            EventHandler handler = this.RequestClose;

            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }

เราตั้งค่าตัวจัดการเหตุการณ์นี้จาก class window (view) ของเรา

        public partial class MainWindow : Window
        {
            private MyViewModel vm = new MyViewModel();

            public MainWindow()
            {
                InitializeComponent();

                vm.RequestClose += delegate
                {
                    Close();
                };

                DataContext = vm;
            }
        }

นี่คือ code ที่สมบูรณ์แล้วของ class ViewModel ของเรา

    public class MyViewModel : DependencyObject
    {
        public static DependencyProperty PrincipleProperty =
        DependencyProperty.Register("Principle", typeof(double), typeof(MainWindow));

        public double Principle
        {
            get { return (double)GetValue(PrincipleProperty); }
            set { SetValue(PrincipleProperty, value); }
        }

        public static DependencyProperty InterestRateProperty =
        DependencyProperty.Register("InterestRate", typeof(double), typeof(MainWindow));

        public double InterestRate
        {
            get { return (double)GetValue(InterestRateProperty); }
            set { SetValue(InterestRateProperty, value); }
        }

        public static DependencyProperty DurationProperty =
        DependencyProperty.Register("Duration", typeof(int), typeof(MainWindow));

        public int Duration
        {
            get { return (int)GetValue(DurationProperty); }
            set { SetValue(DurationProperty, value); }
        }

        public static DependencyProperty PaymentProperty = DependencyProperty.Register("Payment", typeof(double), typeof(MainWindow));

        public double Payment
        {
            get { return (double)GetValue(PaymentProperty); }
            set { SetValue(PaymentProperty, value); }
        }
        public static DependencyProperty PaymentProperty = DependencyProperty.Register("Payment", typeof(double), typeof(MainWindow));

        public double Payment
        {
            get { return (double)GetValue(PaymentProperty); }
            set { SetValue(PaymentProperty, value); }
        }
        public MyViewModel()
        {
            CalculateAmortizationCommand = new MyCommand(CalculateAmortization);
            ExitCommand = new MyCommand(CloseWindow);

            Payments = new ObservableCollection<PaymentInfo>();
        }
        private void CloseWindow()
        {
            EventHandler handler = this.RequestClose;

            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }

        private void CalculateAmortization()
        {
            ValidateInput();
            CalculatePayment();
        }
        // Validate Input
        private void ValidateInput()
        {
            if (Principle < 0)
            {
                MessageBox.Show("Please enter principle amount",
                "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            if (InterestRate < 0)
            {
                MessageBox.Show("Please enter interest",
                "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            if (Duration < 0)
            {
                MessageBox.Show("Please enter duration",
                "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }
        }

        // Calculate the complete amortization schedule and fill the list control
        private void CalculatePayment()
        {
            int totalpayments = Duration * 12;

            double monthlyInterest = CalculateMonthlyInterest(InterestRate);

            // calculate interest term
            double interestTerm = Math.Pow((1 + monthlyInterest), totalpayments);

            // calculate payment
            Payment = (Principle * monthlyInterest) / (1 - (1 / interestTerm));

            Payments.Clear();

            for (int iIndex = 1; iIndex <= totalpayments; ++iIndex)
            {
                PaymentInfo paymentInfo = new PaymentInfo();
                paymentInfo.PaymentNo = iIndex;
                paymentInfo.Balance = CalculateBalance(iIndex);
                paymentInfo.Payment = Payment;
                paymentInfo.Interest = CalculateInterestPart(iIndex);
                paymentInfo.Principle = CalculatePrinciple(iIndex);

                Payments.Add(paymentInfo);
            }

            //lstAmortization.ItemsSource = payments;
        }

        // Calculate the remaining balance at particular payment
        private double CalculateBalance(int month)
        {
            double monthlyInterest = CalculateMonthlyInterest(InterestRate);

            double interestTerm = Math.Pow((1 + monthlyInterest), month);
            double totalInterest = Principle * interestTerm;
            double totalPaid = Payment * (interestTerm - 1) / monthlyInterest;
            return totalInterest - totalPaid;
        }

        // Calculate the Interest part of any particular payment
        private double CalculateInterestPart(int month)
        {
            double monthlyInterest = CalculateMonthlyInterest(InterestRate);

            double interestTerm = Math.Pow((1 + monthlyInterest), (month - 1));
            double totalInterest = Principle * interestTerm;
            double totalPaid = Payment * (interestTerm - 1) / monthlyInterest;
            return (totalInterest - totalPaid) * monthlyInterest;
        }

        // Calculate the principle part of any particular payment
        private double CalculatePrinciple(int month)
        {
            return Payment - CalculateInterestPart(month);
        }

        // Calculate the monthly interest rate
        private double CalculateMonthlyInterest(double InterestRate)
        {
            double monthlyInterest = InterestRate;
            monthlyInterest /= 100;
            monthlyInterest /= 12;

            return monthlyInterest;
        }
    }

3.10. ขั้นที่ 10: กำหนด Class ViewModelBase

กำหนด base class ของ ViewModel เพื่อลด code และ reuse

    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event EventHandler RequestClose;

        public ICommand ExitCommand
        { get; set; }

        public void Close()
        {
            EventHandler handler = this.RequestClose;

            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }

        public void RaisePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;

            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

กลับไปแก้ ViewModel ของเราอีกหน่อยครับเป็นแบบนี้

    public class MyViewModel : ViewModelBase
    {
        public double principle;
        public double interestRate;
        public int duration;
        public double payment;

        public double Principle
        {
            get { return principle; }
            set
            {
                principle = value;
                RaisePropertyChanged("Principle");
            }
        }

        public double InterestRate
        {
            get { return interestRate; }
            set
            {
                interestRate = value;
                RaisePropertyChanged("InterestRate");
            }
        }

        public int Duration
        {
            get { return duration; }
            set
            {
                duration = value;
                RaisePropertyChanged("Duration");
            }
        }

        public double Payment
        {
            get { return payment; }
            set
            {
                payment = value;
                RaisePropertyChanged("Payment");
            }
        }
    }

เสร็จแล้วครับผม

ขอบคุณบทความนี้ครับ http://www.codeproject.com/KB/aspnet/Switching_to_MVVM.aspx

ขอบคุณครับ 😉

Advertisements

#net, #design, #design-patterns, #pattenrs, #wpf