Bagikan melalui


Iterasi #4 – Membuat aplikasi digabungkan secara longgar (VB)

oleh Microsoft

Unduh Kode

Dalam iterasi keempat ini, kami memanfaatkan beberapa pola desain perangkat lunak untuk mempermudah pemeliharaan dan modifikasi aplikasi Contact Manager. Misalnya, kami merefaktor aplikasi kami untuk menggunakan pola Repositori dan pola Injeksi Dependensi.

Membangun Manajemen Kontak ASP.NET Aplikasi MVC (VB)

Dalam rangkaian tutorial ini, kami membangun seluruh aplikasi Manajemen Kontak dari awal hingga akhir. Aplikasi Contact Manager memungkinkan Anda menyimpan informasi kontak - nama, nomor telepon, dan alamat email - untuk daftar orang.

Kami membangun aplikasi melalui beberapa iterasi. Dengan setiap iterasi, kami secara bertahap meningkatkan aplikasi. Tujuan dari pendekatan perulangan ganda ini adalah untuk memungkinkan Anda memahami alasan setiap perubahan.

  • Iterasi #1 - Buat aplikasi. Pada iterasi pertama, kami membuat Contact Manager dengan cara yang paling sederhana. Kami menambahkan dukungan untuk operasi database dasar: Buat, Baca, Perbarui, dan Hapus (CRUD).

  • Iterasi #2 - Buat aplikasi terlihat bagus. Dalam perulangan ini, kami meningkatkan tampilan aplikasi dengan memodifikasi halaman master tampilan ASP.NET MVC default dan lembar gaya bertingkat.

  • Iterasi #3 - Tambahkan validasi formulir. Dalam iterasi ketiga, kami menambahkan validasi formulir dasar. Kami mencegah orang mengirimkan formulir tanpa melengkapi bidang formulir yang diperlukan. Kami juga memvalidasi alamat email dan nomor telepon.

  • Iterasi #4 - Buat aplikasi digabungkan secara longgar. Dalam iterasi keempat ini, kami memanfaatkan beberapa pola desain perangkat lunak untuk mempermudah pemeliharaan dan modifikasi aplikasi Contact Manager. Misalnya, kami merefaktor aplikasi kami untuk menggunakan pola Repositori dan pola Injeksi Dependensi.

  • Iterasi #5 - Membuat pengujian unit. Dalam iterasi kelima, kami membuat aplikasi kami lebih mudah dirawat dan dimodifikasi dengan menambahkan pengujian unit. Kami meniru kelas model data kami dan membangun pengujian unit untuk pengontrol dan logika validasi kami.

  • Iterasi #6 - Gunakan pengembangan berbasis pengujian. Dalam iterasi keenam ini, kami menambahkan fungsionalitas baru ke aplikasi kami dengan menulis pengujian unit terlebih dahulu dan menulis kode terhadap pengujian unit. Dalam perulangan ini, kami menambahkan grup kontak.

  • Iterasi #7 - Tambahkan fungsionalitas Ajax. Dalam iterasi ketujuh, kami meningkatkan responsivitas dan performa aplikasi kami dengan menambahkan dukungan untuk Ajax.

Perulangan ini

Dalam iterasi keempat aplikasi Contact Manager ini, kami merefaktor aplikasi untuk membuat aplikasi lebih longgar digabungkan. Ketika aplikasi digabungkan secara longgar, Anda dapat memodifikasi kode di salah satu bagian aplikasi tanpa perlu memodifikasi kode di bagian lain aplikasi. Aplikasi yang digabungkan secara longgar lebih tahan terhadap perubahan.

Saat ini, semua logika akses dan validasi data yang digunakan oleh aplikasi Contact Manager terkandung dalam kelas pengontrol. Ini ide yang buruk. Setiap kali Anda perlu memodifikasi satu bagian aplikasi, Anda berisiko memasukkan bug ke bagian lain dari aplikasi Anda. Misalnya, jika Anda memodifikasi logika validasi, Anda berisiko memperkenalkan bug baru ke dalam akses data atau logika pengontrol Anda.

Catatan

(SRP), kelas tidak boleh memiliki lebih dari satu alasan untuk berubah. Pencampuran pengontrol, validasi, dan logika database adalah pelanggaran besar-besaran terhadap Prinsip Tanggung Jawab Tunggal.

Ada beberapa alasan bahwa Anda mungkin perlu memodifikasi aplikasi Anda. Anda mungkin perlu menambahkan fitur baru ke aplikasi, Anda mungkin perlu memperbaiki bug di aplikasi Anda, atau Anda mungkin perlu memodifikasi bagaimana fitur aplikasi Anda diterapkan. Aplikasi jarang statis. Mereka cenderung tumbuh dan bermutasi dari waktu ke waktu.

Bayangkan, misalnya, bahwa Anda memutuskan untuk mengubah cara Anda menerapkan lapisan akses data Anda. Saat ini, aplikasi Contact Manager menggunakan Microsoft Entity Framework untuk mengakses database. Namun, Anda mungkin memutuskan untuk bermigrasi ke teknologi akses data baru atau alternatif seperti ADO.NET Data Services atau NHibernate. Namun, karena kode akses data tidak diisolasi dari kode validasi dan pengontrol, tidak ada cara untuk memodifikasi kode akses data di aplikasi Anda tanpa memodifikasi kode lain yang tidak terkait langsung dengan akses data.

Ketika aplikasi digabungkan secara longgar, di sisi lain, Anda dapat membuat perubahan pada satu bagian aplikasi tanpa menyentuh bagian lain dari aplikasi. Misalnya, Anda dapat beralih teknologi akses data tanpa memodifikasi validasi atau logika pengontrol Anda.

Dalam iterasi ini, kami memanfaatkan beberapa pola desain perangkat lunak yang memungkinkan kami merefaktor aplikasi Contact Manager kami ke dalam aplikasi yang lebih digabungkan secara longgar. Setelah selesai, Contact Manager tidak akan melakukan apa pun yang tidak dilakukan sebelumnya. Namun, kami akan dapat mengubah aplikasi dengan lebih mudah di masa depan.

Catatan

Pemfaktoran ulang adalah proses penulisan ulang aplikasi sewaktu-waktu sehingga tidak kehilangan fungsionalitas yang ada.

Menggunakan Pola Desain Perangkat Lunak Repositori

Perubahan pertama kami adalah memanfaatkan pola desain perangkat lunak yang disebut pola Repositori. Kami akan menggunakan pola Repositori untuk mengisolasi kode akses data kami dari aplikasi kami lainnya.

Menerapkan pola Repositori mengharuskan kami menyelesaikan dua langkah berikut:

  1. Membuat antarmuka
  2. Membuat kelas konkret yang mengimplementasikan antarmuka

Pertama, kita perlu membuat antarmuka yang menjelaskan semua metode akses data yang perlu kita lakukan. Antarmuka IContactManagerRepository terkandung dalam Daftar 1. Antarmuka ini menjelaskan lima metode: CreateContact(), DeleteContact(), EditContact(), GetContact, dan ListContacts().

Daftar 1 - Models\IContactManagerRepository.vb

Public Interface IContactManagerRepository
Function CreateContact(ByVal contactToCreate As Contact) As Contact
Sub DeleteContact(ByVal contactToDelete As Contact)
Function EditContact(ByVal contactToUpdate As Contact) As Contact
Function GetContact(ByVal id As Integer) As Contact
Function ListContacts() As IEnumerable(Of Contact)
End Interface

Selanjutnya, kita perlu membuat kelas konkret yang mengimplementasikan antarmuka IContactManagerRepository. Karena kita menggunakan Microsoft Entity Framework untuk mengakses database, kita akan membuat kelas baru bernama EntityContactManagerRepository. Kelas ini terkandung dalam Daftar 2.

Daftar 2 - Models\EntityContactManagerRepository.vb

Public Class EntityContactManagerRepository
Implements IContactManagerRepository

Private _entities As New ContactManagerDBEntities()

Public Function GetContact(ByVal id As Integer) As Contact Implements IContactManagerRepository.GetContact
    Return (From c In _entities.ContactSet _
            Where c.Id = id _
            Select c).FirstOrDefault()
End Function


Public Function ListContacts() As IEnumerable(Of Contact) Implements IContactManagerRepository.ListContacts
    Return _entities.ContactSet.ToList()
End Function


Public Function CreateContact(ByVal contactToCreate As Contact) As Contact Implements IContactManagerRepository.CreateContact
    _entities.AddToContactSet(contactToCreate)
    _entities.SaveChanges()
    Return contactToCreate
End Function


Public Function EditContact(ByVal contactToEdit As Contact) As Contact Implements IContactManagerRepository.EditContact
    Dim originalContact = GetContact(contactToEdit.Id)
    _entities.ApplyPropertyChanges(originalContact.EntityKey.EntitySetName, contactToEdit)
    _entities.SaveChanges()
    Return contactToEdit
End Function


Public Sub DeleteContact(ByVal contactToDelete As Contact) Implements IContactManagerRepository.DeleteContact
    Dim originalContact = GetContact(contactToDelete.Id)
    _entities.DeleteObject(originalContact)
    _entities.SaveChanges()
End Sub

End Class

Perhatikan bahwa kelas EntityContactManagerRepository mengimplementasikan antarmuka IContactManagerRepository. Kelas mengimplementasikan kelima metode yang dijelaskan oleh antarmuka tersebut.

Anda mungkin bertanya-tanya mengapa kita perlu repot-repot dengan antarmuka. Mengapa kita perlu membuat antarmuka dan kelas yang mengimplementasikannya?

Dengan satu pengecualian, sisa aplikasi kami akan berinteraksi dengan antarmuka dan bukan kelas konkret. Alih-alih memanggil metode yang diekspos oleh kelas EntityContactManagerRepository, kita akan memanggil metode yang diekspos oleh antarmuka IContactManagerRepository.

Dengan begitu, kita dapat mengimplementasikan antarmuka dengan kelas baru tanpa perlu memodifikasi sisa aplikasi kita. Misalnya, pada tanggal mendatang, kami mungkin ingin mengimplementasikan kelas DataServicesContactManagerRepository yang mengimplementasikan antarmuka IContactManagerRepository. Kelas DataServicesContactManagerRepository mungkin menggunakan ADO.NET Data Services untuk mengakses database alih-alih Microsoft Entity Framework.

Jika kode aplikasi kami diprogram terhadap antarmuka IContactManagerRepository alih-alih kelas EntityContactManagerRepository konkret, maka kita dapat beralih kelas konkret tanpa memodifikasi salah satu kode kami lainnya. Misalnya, kita dapat beralih dari kelas EntityContactManagerRepository ke kelas DataServicesContactManagerRepository tanpa memodifikasi akses data atau logika validasi kami.

Pemrograman terhadap antarmuka (abstraksi) alih-alih kelas konkret membuat aplikasi kami lebih tahan terhadap perubahan.

Catatan

Anda dapat dengan cepat membuat antarmuka dari kelas konkret dalam Visual Studio dengan memilih opsi menu Refaktor, Ekstrak Antarmuka. Misalnya, Anda dapat membuat kelas EntityContactManagerRepository terlebih dahulu lalu menggunakan Extract Interface untuk menghasilkan antarmuka IContactManagerRepository secara otomatis.

Menggunakan Pola Desain Perangkat Lunak Injeksi Dependensi

Sekarang setelah kami memigrasikan kode akses data kami ke kelas Repositori terpisah, kami perlu memodifikasi pengontrol Kontak kami untuk menggunakan kelas ini. Kami akan memanfaatkan pola desain perangkat lunak yang disebut Injeksi Dependensi untuk menggunakan kelas Repositori di pengontrol kami.

Pengontrol Kontak yang dimodifikasi terkandung dalam Daftar 3.

Daftar 3 - Controllers\ContactController.vb

Public Class ContactController
    Inherits System.Web.Mvc.Controller

    Private _repository As IContactManagerRepository 

    Sub New()
        Me.New(new EntityContactManagerRepository())
    End Sub

    Sub New(repository As IContactManagerRepository)
        _repository = repository
    End Sub

    Protected Sub ValidateContact(contactToValidate As Contact)
        If contactToValidate.FirstName.Trim().Length = 0 Then
            ModelState.AddModelError("FirstName", "First name is required.")
        End If
        If contactToValidate.LastName.Trim().Length = 0 Then
            ModelState.AddModelError("LastName", "Last name is required.")
        End If
        If (contactToValidate.Phone.Length > 0 AndAlso Not Regex.IsMatch(contactToValidate.Phone, "((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
            ModelState.AddModelError("Phone", "Invalid phone number.")
        End If        
        If (contactToValidate.Email.Length > 0 AndAlso  Not Regex.IsMatch(contactToValidate.Email, "^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
            ModelState.AddModelError("Email", "Invalid email address.")
        End If
    End Sub

    Function Index() As ActionResult
        Return View(_repository.ListContacts())
    End Function

    Function Create() As ActionResult
        Return View()
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Create(<Bind(Exclude:="Id")> ByVal contactToCreate As Contact) As ActionResult
        ' Validation logic
        ValidateContact(contactToCreate)
        If Not ModelState.IsValid Then
            Return View()
        End If

        ' Database logic
        Try
            _repository.CreateContact(contactToCreate)
            Return RedirectToAction("Index")
        Catch
            Return View()
        End Try
    End Function

    Function Edit(ByVal id As Integer) As ActionResult
        Return View(_repository.GetContact(id))
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Edit(ByVal contactToEdit As Contact) As ActionResult
        ' Validation logic
        ValidateContact(contactToEdit)
        If Not ModelState.IsValid Then
            Return View()
        End If

        ' Database logic
        Try
            _repository.EditContact(contactToEdit)
            Return RedirectToAction("Index")
        Catch
            Return View()
        End Try
    End Function

    Function Delete(ByVal id As Integer) As ActionResult
        Return View(_repository.GetContact(id))
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Delete(ByVal contactToDelete As Contact) As ActionResult
        Try
            _repository.DeleteContact(contactToDelete)
            Return RedirectToAction("Index")
        Catch
            Return View()
        End Try
    End Function

End Class

Perhatikan bahwa pengontrol Kontak di Daftar 3 memiliki dua konstruktor. Konstruktor pertama meneruskan instans konkret antarmuka IContactManagerRepository ke konstruktor kedua. Kelas Pengontrol kontak menggunakan Injeksi Dependensi Konstruktor.

Satu-satunya tempat yang digunakan kelas EntityContactManagerRepository berada di konstruktor pertama. Sisa kelas menggunakan antarmuka IContactManagerRepository alih-alih kelas EntityContactManagerRepository konkret.

Ini memudahkan untuk beralih implementasi kelas IContactManagerRepository di masa depan. Jika Anda ingin menggunakan kelas DataServicesContactRepository alih-alih kelas EntityContactManagerRepository, cukup ubah konstruktor pertama.

Injeksi Dependensi Konstruktor juga membuat kelas Pengontrol kontak sangat dapat diuji. Dalam pengujian unit, Anda dapat membuat instans pengontrol Kontak dengan melewati implementasi tiruan dari kelas IContactManagerRepository. Fitur Injeksi Dependensi ini akan sangat penting bagi kami dalam iterasi berikutnya ketika kami membangun pengujian unit untuk aplikasi Contact Manager.

Catatan

Jika Anda ingin sepenuhnya memisahkan kelas Pengontrol kontak dari implementasi tertentu dari antarmuka IContactManagerRepository maka Anda dapat memanfaatkan kerangka kerja yang mendukung Injeksi Dependensi seperti StructureMap atau Microsoft Entity Framework (MEF). Dengan memanfaatkan kerangka kerja Injeksi Dependensi, Anda tidak perlu merujuk ke kelas konkret dalam kode Anda.

Membuat Lapisan Layanan

Anda mungkin telah memperhatikan bahwa logika validasi kami masih bercampur dengan logika pengontrol kami di kelas pengontrol yang dimodifikasi di Listing 3. Untuk alasan yang sama, ada baiknya mengisolasi logika akses data kami, ada baiknya untuk mengisolasi logika validasi kami.

Untuk memperbaiki masalah ini, kita dapat membuat lapisan layanan terpisah. Lapisan layanan adalah lapisan terpisah yang dapat kita sisipkan antara kelas pengontrol dan repositori kami. Lapisan layanan berisi logika bisnis kami termasuk semua logika validasi kami.

ContactManagerService terkandung dalam Daftar 4. Ini berisi logika validasi dari kelas Pengontrol kontak.

Daftar 4 - Models\ContactManagerService.vb

Public Class ContactManagerService
Implements IContactManagerService

Private _validationDictionary As IValidationDictionary
Private _repository As IContactManagerRepository


Public Sub New(ByVal validationDictionary As IValidationDictionary)
    Me.New(validationDictionary, New EntityContactManagerRepository())
End Sub


Public Sub New(ByVal validationDictionary As IValidationDictionary, ByVal repository As IContactManagerRepository)
    _validationDictionary = validationDictionary
    _repository = repository
End Sub


Public Function ValidateContact(ByVal contactToValidate As Contact) As Boolean
    If contactToValidate.FirstName.Trim().Length = 0 Then
        _validationDictionary.AddError("FirstName", "First name is required.")
    End If
    If contactToValidate.LastName.Trim().Length = 0 Then
        _validationDictionary.AddError("LastName", "Last name is required.")
    End If
    If contactToValidate.Phone.Length > 0 AndAlso (Not Regex.IsMatch(contactToValidate.Phone, "((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}")) Then
        _validationDictionary.AddError("Phone", "Invalid phone number.")
    End If
    If contactToValidate.Email.Length > 0 AndAlso (Not Regex.IsMatch(contactToValidate.Email, "^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$")) Then
        _validationDictionary.AddError("Email", "Invalid email address.")
    End If
    Return _validationDictionary.IsValid
End Function


#Region "IContactManagerService Members"

Public Function CreateContact(ByVal contactToCreate As Contact) As Boolean Implements IContactManagerService.CreateContact
    ' Validation logic
    If Not ValidateContact(contactToCreate) Then
        Return False
    End If

    ' Database logic
    Try
        _repository.CreateContact(contactToCreate)
    Catch
        Return False
    End Try
    Return True
End Function

Public Function EditContact(ByVal contactToEdit As Contact) As Boolean Implements IContactManagerService.EditContact
    ' Validation logic
    If Not ValidateContact(contactToEdit) Then
        Return False
    End If

    ' Database logic
    Try
        _repository.EditContact(contactToEdit)
    Catch
        Return False
    End Try
    Return True
End Function

Public Function DeleteContact(ByVal contactToDelete As Contact) As Boolean Implements IContactManagerService.DeleteContact
    Try
        _repository.DeleteContact(contactToDelete)
    Catch
        Return False
    End Try
    Return True
End Function

Public Function GetContact(ByVal id As Integer) As Contact Implements IContactManagerService.GetContact
    Return _repository.GetContact(id)
End Function

Public Function ListContacts() As IEnumerable(Of Contact) Implements IContactManagerService.ListContacts
    Return _repository.ListContacts()
End Function

#End Region
End Class

Perhatikan bahwa konstruktor untuk ContactManagerService memerlukan ValidationDictionary. Lapisan layanan berkomunikasi dengan lapisan pengontrol melalui ValidationDictionary ini. Kami membahas ValidationDictionary secara rinci di bagian berikut ketika kita membahas pola Dekorator.

Perhatikan, selanjutnya, bahwa ContactManagerService mengimplementasikan antarmuka IContactManagerService. Anda harus selalu berusaha untuk memprogram terhadap antarmuka alih-alih kelas konkret. Kelas lain dalam aplikasi Contact Manager tidak berinteraksi dengan kelas ContactManagerService secara langsung. Sebaliknya, dengan satu pengecualian, sisa aplikasi Contact Manager diprogram terhadap antarmuka IContactManagerService.

Antarmuka IContactManagerService terkandung dalam Listing 5.

Daftar 5 - Models\IContactManagerService.vb

Public Interface IContactManagerService
Function CreateContact(ByVal contactToCreate As Contact) As Boolean
Function DeleteContact(ByVal contactToDelete As Contact) As Boolean
Function EditContact(ByVal contactToEdit As Contact) As Boolean
Function GetContact(ByVal id As Integer) As Contact
Function ListContacts() As IEnumerable(Of Contact)
End Interface

Kelas Pengontrol kontak yang dimodifikasi terkandung dalam Daftar 6. Perhatikan bahwa pengontrol Kontak tidak lagi berinteraksi dengan repositori ContactManager. Sebagai gantinya, pengontrol Kontak berinteraksi dengan layanan ContactManager. Setiap lapisan diisolasi sebanyak mungkin dari lapisan lain.

Daftar 6 - Controllers\ContactController.vb

Public Class ContactController
    Inherits System.Web.Mvc.Controller

    Private _service As IContactManagerService 

    Sub New()
        _service = new ContactManagerService(New ModelStateWrapper(ModelState))
    End Sub

    Sub New(service As IContactManagerService)
        _service = service
    End Sub

    Function Index() As ActionResult
        Return View(_service.ListContacts())
    End Function

    Function Create() As ActionResult
        Return View()
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Create(<Bind(Exclude:="Id")> ByVal contactToCreate As Contact) As ActionResult
        If _service.CreateContact(contactToCreate) Then
            Return RedirectToAction("Index")        
        End If
        Return View()
    End Function

    Function Edit(ByVal id As Integer) As ActionResult
        Return View(_service.GetContact(id))
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Edit(ByVal contactToEdit As Contact) As ActionResult
        If _service.EditContact(contactToEdit) Then
            Return RedirectToAction("Index")        
        End If
        Return View()
    End Function

    Function Delete(ByVal id As Integer) As ActionResult
        Return View(_service.GetContact(id))
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Delete(ByVal contactToDelete As Contact) As ActionResult
        If _service.DeleteContact(contactToDelete) Then
            return RedirectToAction("Index")
        End If
        Return View()
    End Function

End Class

Aplikasi kami tidak lagi menjalankan prinsip Tanggung Jawab Tunggal (SRP). Pengontrol Kontak di Daftar 6 telah dilucuti dari setiap tanggung jawab selain mengontrol alur eksekusi aplikasi. Semua logika validasi telah dihapus dari pengontrol Kontak dan didorong ke lapisan layanan. Semua logika database telah didorong ke lapisan repositori.

Menggunakan Pola Dekorator

Kami ingin dapat sepenuhnya memisahkan lapisan layanan kami dari lapisan pengontrol kami. Pada prinsipnya, kita harus dapat mengkompilasi lapisan layanan kita dalam rakitan terpisah dari lapisan pengontrol kita tanpa perlu menambahkan referensi ke aplikasi MVC kita.

Namun, lapisan layanan kami harus dapat meneruskan pesan kesalahan validasi kembali ke lapisan pengontrol. Bagaimana kami dapat mengaktifkan lapisan layanan untuk mengkomunikasikan pesan kesalahan validasi tanpa mengkoplorasi pengontrol dan lapisan layanan? Kita dapat memanfaatkan pola desain perangkat lunak bernama pola Dekorator.

Pengontrol menggunakan ModelStateDictionary bernama ModelState untuk mewakili kesalahan validasi. Oleh karena itu, Anda mungkin tergoda untuk meneruskan ModelState dari lapisan pengontrol ke lapisan layanan. Namun, menggunakan ModelState di lapisan layanan akan membuat lapisan layanan Anda bergantung pada fitur kerangka kerja MVC ASP.NET. Ini akan buruk karena, suatu hari nanti, Anda mungkin ingin menggunakan lapisan layanan dengan aplikasi WPF alih-alih aplikasi MVC ASP.NET. Dalam hal ini, Anda tidak ingin mereferensikan kerangka kerja MVC ASP.NET untuk menggunakan kelas ModelStateDictionary.

Pola Dekorator memungkinkan Anda membungkus kelas yang ada di kelas baru untuk mengimplementasikan antarmuka. Proyek Contact Manager kami mencakup kelas ModelStateWrapper yang terkandung dalam Listing 7. Kelas ModelStateWrapper mengimplementasikan antarmuka di Listing 8.

Daftar 7 - Models\Validation\ModelStateWrapper.vb

Public Class ModelStateWrapper
Implements IValidationDictionary

Private _modelState As ModelStateDictionary

Public Sub New(ByVal modelState As ModelStateDictionary)
    _modelState = modelState
End Sub

Public Sub AddError(ByVal key As String, ByVal errorMessage As String) Implements IValidationDictionary.AddError
    _modelState.AddModelError(key, errorMessage)
End Sub

Public ReadOnly Property IsValid() As Boolean Implements IValidationDictionary.IsValid
    Get
        Return _modelState.IsValid
    End Get
End Property

End Class

Daftar 8 - Models\Validation\IValidationDictionary.vb

Public Interface IValidationDictionary

Sub AddError(ByVal key As String, ByVal errorMessage As String)
ReadOnly Property IsValid() As Boolean

End Interface

Jika Anda melihat dari dekat Listing 5 maka Anda akan melihat bahwa lapisan layanan ContactManager menggunakan antarmuka IValidationDictionary secara eksklusif. Layanan ContactManager tidak bergantung pada kelas ModelStateDictionary. Saat pengontrol Kontak membuat layanan ContactManager, pengontrol membungkus ModelState-nya seperti ini:

_service = new ContactManagerService(New ModelStateWrapper(ModelState))

Ringkasan

Dalam iterasi ini, kami tidak menambahkan fungsionalitas baru apa pun ke aplikasi Contact Manager. Tujuan dari iterasi ini adalah untuk merefaktor aplikasi Contact Manager sehingga lebih mudah untuk dipertahankan dan dimodifikasi.

Pertama, kami menerapkan pola desain perangkat lunak Repositori. Kami memigrasikan semua kode akses data ke kelas repositori ContactManager terpisah.

Kami juga mengisolasi logika validasi kami dari logika pengontrol kami. Kami membuat lapisan layanan terpisah yang berisi semua kode validasi kami. Lapisan pengontrol berinteraksi dengan lapisan layanan, dan lapisan layanan berinteraksi dengan lapisan repositori.

Ketika kami membuat lapisan layanan, kami memanfaatkan pola Dekorator untuk mengisolasi ModelState dari lapisan layanan kami. Di lapisan layanan kami, kami diprogram terhadap antarmuka IValidationDictionary alih-alih ModelState.

Akhirnya, kami memanfaatkan pola desain perangkat lunak bernama pola Injeksi Dependensi. Pola ini memungkinkan kita untuk memprogram terhadap antarmuka (abstraksi) alih-alih kelas konkret. Menerapkan pola desain Injeksi Dependensi juga membuat kode kami lebih dapat diuji. Dalam iterasi berikutnya, kami menambahkan pengujian unit ke proyek kami.