Iteracja 4 — Luźne sprzężenie aplikacji (VB)

autor: Microsoft

Pobierz kod

W tej czwartej iteracji korzystamy z kilku wzorców projektowych oprogramowania, aby ułatwić konserwację i modyfikowanie aplikacji Contact Manager. Na przykład refaktoryzujemy naszą aplikację, aby używać wzorca repozytorium i wzorca wstrzykiwania zależności.

Tworzenie aplikacji MVC do zarządzania kontaktami ASP.NET (VB)

W tej serii samouczków utworzymy całą aplikację do zarządzania kontaktami od początku do końca. Aplikacja Contact Manager umożliwia przechowywanie informacji kontaktowych — nazw, numerów telefonów i adresów e-mail — dla listy osób.

Tworzymy aplikację za pośrednictwem wielu iteracji. Wraz z każdą iteracją stopniowo ulepszamy aplikację. Celem tego podejścia iteracji wielokrotnej jest umożliwienie zrozumienia przyczyny każdej zmiany.

  • Iteracja #1 — tworzenie aplikacji. W pierwszej iteracji tworzymy Menedżera kontaktów w najprostszy możliwy sposób. Dodajemy obsługę podstawowych operacji bazy danych: tworzenie, odczyt, aktualizowanie i usuwanie (CRUD).

  • Iteracja #2 — sprawia, że aplikacja wygląda ładnie. W tej iteracji poprawiamy wygląd aplikacji, modyfikując domyślną stronę wzorcową widoku MVC ASP.NET oraz kaskadowy arkusz stylów.

  • Iteracja 3 — dodawanie walidacji formularza. W trzeciej iteracji dodamy podstawową walidację formularza. Uniemożliwiamy użytkownikom przesyłanie formularza bez wypełniania wymaganych pól formularza. Weryfikujemy również adresy e-mail i numery telefonów.

  • Iteracja #4 — luźno sprzężenie aplikacji. W tej czwartej iteracji korzystamy z kilku wzorców projektowych oprogramowania, aby ułatwić konserwację i modyfikowanie aplikacji Contact Manager. Na przykład refaktoryzujemy naszą aplikację, aby używać wzorca repozytorium i wzorca wstrzykiwania zależności.

  • Iteracja 5 — tworzenie testów jednostkowych. W piątej iteracji ułatwiamy konserwację i modyfikowanie aplikacji przez dodawanie testów jednostkowych. Wyśmiewamy nasze klasy modelu danych i tworzymy testy jednostkowe dla naszych kontrolerów i logiki walidacji.

  • Iteracja 6 — używanie programowania opartego na testach. W tej szóstej iteracji do naszej aplikacji dodamy nowe funkcje, pisząc najpierw testy jednostkowe i pisząc kod względem testów jednostkowych. W tej iteracji dodajemy grupy kontaktów.

  • Iteracja #7 — dodawanie funkcji Ajax. W siódmej iteracji poprawiamy czas odpowiedzi i wydajność naszej aplikacji, dodając obsługę Ajax.

Ta iteracja

W tej czwartej iteracji aplikacji Contact Manager refaktoryzujemy aplikację, aby aplikacja była luźniej sprzężona. Gdy aplikacja jest luźno połączona, można zmodyfikować kod w jednej części aplikacji bez konieczności modyfikowania kodu w innych częściach aplikacji. Luźno powiązane aplikacje są bardziej odporne na zmiany.

Obecnie wszystkie logiki dostępu do danych i walidacji używane przez aplikację Contact Manager są zawarte w klasach kontrolerów. To jest zły pomysł. Za każdym razem, gdy musisz zmodyfikować jedną część aplikacji, ryzykujesz wprowadzenie usterek do innej części aplikacji. Jeśli na przykład zmodyfikujesz logikę walidacji, ryzyko wprowadzenia nowych usterek do logiki dostępu do danych lub kontrolera.

Uwaga

(SRP), klasa nigdy nie powinna mieć więcej niż jednego powodu do zmiany. Mieszanie logiki kontrolera, walidacji i bazy danych jest ogromnym naruszeniem zasady o pojedynczej odpowiedzialności.

Istnieje kilka powodów, dla których może być konieczne zmodyfikowanie aplikacji. Może być konieczne dodanie nowej funkcji do aplikacji, może być konieczne naprawienie usterki w aplikacji lub może być konieczne zmodyfikowanie sposobu implementacji funkcji aplikacji. Aplikacje są rzadko statyczne. Mają tendencję do wzrostu imutacji w czasie.

Załóżmy na przykład, że decydujesz się zmienić sposób implementowania warstwy dostępu do danych. W tej chwili aplikacja Contact Manager używa programu Microsoft Entity Framework do uzyskiwania dostępu do bazy danych. Możesz jednak zdecydować się na migrację do nowej lub alternatywnej technologii dostępu do danych, takiej jak ADO.NET Data Services lub NHibernate. Jednak ponieważ kod dostępu do danych nie jest odizolowany od kodu walidacji i kontrolera, nie ma możliwości modyfikowania kodu dostępu do danych w aplikacji bez modyfikowania innego kodu, który nie jest bezpośrednio związany z dostępem do danych.

Gdy aplikacja jest luźno połączona, z drugiej strony możesz wprowadzić zmiany w jednej części aplikacji bez dotykania innych części aplikacji. Na przykład można przełączać technologie dostępu do danych bez modyfikowania logiki walidacji lub kontrolera.

W tej iteracji korzystamy z kilku wzorców projektowych oprogramowania, które umożliwiają refaktoryzację aplikacji Contact Manager do bardziej luźno powiązanej aplikacji. Gdy skończymy, Menedżer kontaktów nie zrobi nic, czego wcześniej nie zrobił. Jednak będziemy mogli łatwiej zmienić aplikację w przyszłości.

Uwaga

Refaktoryzacja to proces ponownego zapisywania aplikacji w taki sposób, że nie traci żadnych istniejących funkcji.

Korzystanie ze wzorca projektowania oprogramowania repozytorium

Pierwszą zmianą jest skorzystanie ze wzorca projektowego oprogramowania nazywanego wzorcem repozytorium. Użyjemy wzorca repozytorium, aby odizolować kod dostępu do danych od pozostałej części naszej aplikacji.

Zaimplementowanie wzorca repozytorium wymaga wykonania następujących dwóch kroków:

  1. Tworzenie interfejsu
  2. Tworzenie konkretnej klasy implementujące interfejs

Najpierw musimy utworzyć interfejs opisujący wszystkie metody dostępu do danych, które należy wykonać. Interfejs IContactManagerRepository znajduje się na liście 1. W tym interfejsie opisano pięć metod: CreateContact(), DeleteContact(), EditContact(), GetContact i ListContacts().

Lista 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

Następnie musimy utworzyć konkretną klasę, która implementuje interfejs IContactManagerRepository. Ponieważ do uzyskiwania dostępu do bazy danych używamy programu Microsoft Entity Framework, utworzymy nową klasę o nazwie EntityContactManagerRepository. Ta klasa jest zawarta w liście 2.

Lista 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

Zwróć uwagę, że klasa EntityContactManagerRepository implementuje interfejs IContactManagerRepository. Klasa implementuje wszystkie pięć metod opisanych przez ten interfejs.

Możesz się zastanawiać, dlaczego musimy przeszkadzać za pomocą interfejsu. Dlaczego musimy utworzyć zarówno interfejs, jak i klasę, która ją implementuje?

Z jednym wyjątkiem pozostała część naszej aplikacji będzie współdziałać z interfejsem, a nie konkretną klasą. Zamiast wywoływać metody uwidocznione przez klasę EntityContactManagerRepository, wywołamy metody uwidocznione przez interfejs IContactManagerRepository.

Dzięki temu możemy zaimplementować interfejs z nową klasą bez konieczności modyfikowania pozostałej części aplikacji. Na przykład w przyszłości możemy zaimplementować klasę DataServicesContactManagerRepository, która implementuje interfejs IContactManagerRepository. Klasa DataServicesContactManagerRepository może używać ADO.NET Data Services w celu uzyskania dostępu do bazy danych zamiast programu Microsoft Entity Framework.

Jeśli nasz kod aplikacji jest zaprogramowany względem interfejsu IContactManagerRepository zamiast konkretnej klasy EntityContactManagerRepository, możemy przełączyć konkretne klasy bez modyfikowania pozostałej części kodu. Na przykład możemy przełączyć się z klasy EntityContactManagerRepository na klasę DataServicesContactManagerRepository bez modyfikowania logiki dostępu do danych lub walidacji.

Programowanie względem interfejsów (abstrakcji) zamiast konkretnych klas sprawia, że nasza aplikacja jest bardziej odporna na zmiany.

Uwaga

Interfejs można szybko utworzyć na podstawie konkretnej klasy w programie Visual Studio, wybierając opcję menu Refaktoryzacja, Wyodrębnij interfejs. Na przykład można najpierw utworzyć klasę EntityContactManagerRepository, a następnie użyć interfejsu Extract, aby automatycznie wygenerować interfejs IContactManagerRepository.

Korzystanie ze wzorca projektowego oprogramowania wstrzykiwania zależności

Teraz, gdy przeprowadziliśmy migrację kodu dostępu do danych do oddzielnej klasy repozytorium, musimy zmodyfikować kontroler kontaktów, aby używał tej klasy. Skorzystamy ze wzorca projektowego oprogramowania o nazwie Wstrzykiwanie zależności, aby użyć klasy Repository w naszym kontrolerze.

Zmodyfikowany kontroler kontaktów znajduje się na liście 3.

Lista 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

Zwróć uwagę, że kontroler kontaktów na liście 3 ma dwa konstruktory. Pierwszy konstruktor przekazuje konkretne wystąpienie interfejsu IContactManagerRepository do drugiego konstruktora. Klasa kontrolera kontaktów używa wstrzykiwania zależności konstruktora.

Jedyną klasą EntityContactManagerRepository jest klasa i tylko w pierwszym konstruktorze. Pozostała część klasy używa interfejsu IContactManagerRepository zamiast konkretnej klasy EntityContactManagerRepository.

Ułatwia to przełączanie implementacji klasy IContactManagerRepository w przyszłości. Jeśli chcesz użyć klasy DataServicesContactRepository zamiast klasy EntityContactManagerRepository, zmodyfikuj pierwszy konstruktor.

Wstrzykiwanie zależności konstruktora sprawia również, że klasa kontrolera Kontakt jest bardzo testowalna. W testach jednostkowych można utworzyć wystąpienie kontrolera kontaktów, przekazując pozorną implementację klasy IContactManagerRepository. Ta funkcja wstrzykiwania zależności będzie dla nas bardzo ważna w następnej iteracji podczas kompilowania testów jednostkowych dla aplikacji Contact Manager.

Uwaga

Jeśli chcesz całkowicie rozdzielić klasę kontrolera Kontakt z konkretnej implementacji interfejsu IContactManagerRepository, możesz skorzystać z platformy obsługującej wstrzykiwanie zależności, takie jak StructureMap lub Microsoft Entity Framework (MEF). Korzystając z struktury wstrzykiwania zależności, nigdy nie musisz odwoływać się do konkretnej klasy w kodzie.

Tworzenie warstwy usługi

Być może zauważysz, że nasza logika walidacji jest nadal mieszana z naszą logiką kontrolera w zmodyfikowanej klasie kontrolera w liście 3. Z tego samego powodu warto odizolować logikę dostępu do danych, dlatego warto odizolować logikę weryfikacji.

Aby rozwiązać ten problem, możemy utworzyć oddzielną warstwę usługi. Warstwa usługi to oddzielna warstwa, którą możemy wstawić między naszymi klasami kontrolera i repozytorium. Warstwa usługi zawiera naszą logikę biznesową, w tym całą logikę walidacji.

Element ContactManagerService znajduje się na liście 4. Zawiera logikę walidacji z klasy Kontroler kontaktów.

Lista 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

Zwróć uwagę, że konstruktor klasy ContactManagerService wymaga elementu ValidationDictionary. Warstwa usługi komunikuje się z warstwą kontrolera za pośrednictwem tego kontrolki ValidationDictionary. Szczegółowo omówimy element ValidationDictionary w poniższej sekcji podczas omawiania wzorca dekoratora.

Zwróć uwagę, że usługa ContactManagerService implementuje interfejs IContactManagerService. Zawsze należy dążyć do programowania w odniesieniu do interfejsów zamiast konkretnych klas. Inne klasy w aplikacji Contact Manager nie współdziałają bezpośrednio z klasą ContactManagerService. Zamiast tego, z jednym wyjątkiem, pozostała część aplikacji Contact Manager jest programowana względem interfejsu IContactManagerService.

Interfejs IContactManagerService znajduje się na liście 5.

Lista 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

Zmodyfikowana klasa kontrolera kontaktów jest zawarta w liście 6. Zwróć uwagę, że kontroler kontaktów nie wchodzi już w interakcję z repozytorium ContactManager. Zamiast tego kontroler kontaktów współdziała z usługą ContactManager. Każda warstwa jest izolowana jak najwięcej od innych warstw.

Lista 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

Nasza aplikacja nie działa już za pomocą zasady odpowiedzialności pojedynczej (SRP). Kontroler kontaktów w liście 6 został pozbawiony każdej odpowiedzialności innej niż kontrolowanie przepływu wykonywania aplikacji. Cała logika walidacji została usunięta z kontrolera kontaktów i wypchnięta do warstwy usługi. Cała logika bazy danych została wypchnięta do warstwy repozytorium.

Używanie wzorca dekoratora

Chcemy mieć możliwość całkowitego oddzielenia warstwy usługi od warstwy kontrolera. W zasadzie powinniśmy mieć możliwość skompilowania warstwy usługi w osobnym zestawie od warstwy kontrolera bez konieczności dodawania odwołania do naszej aplikacji MVC.

Jednak nasza warstwa usługi musi mieć możliwość przekazywania komunikatów o błędach weryfikacji z powrotem do warstwy kontrolera. Jak umożliwić warstwie usługi komunikowanie komunikatów o błędach weryfikacji bez sprzężenia kontrolera i warstwy usługi? Możemy skorzystać ze wzorca projektowego oprogramowania o nazwie deseń dekoratora.

Kontroler używa klasy ModelStateDictionary o nazwie ModelStateState do reprezentowania błędów walidacji. W związku z tym możesz być kuszony, aby przekazać modelState z warstwy kontrolera do warstwy usługi. Jednak użycie elementu ModelState w warstwie usługi sprawi, że warstwa usługi będzie zależna od funkcji platformy ASP.NET MVC. Byłoby to złe, ponieważ pewnego dnia warto użyć warstwy usługi z aplikacją WPF zamiast aplikacji ASP.NET MVC. W takim przypadku nie chcesz odwoływać się do platformy ASP.NET MVC, aby użyć klasy ModelStateDictionary.

Wzorzec dekoratora umożliwia opakowywanie istniejącej klasy w nowej klasie w celu zaimplementowania interfejsu. Nasz projekt Contact Manager zawiera klasę ModelStateWrapper zawartą w liście List 7. Klasa ModelStateWrapper implementuje interfejs w liście 8.

Lista 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

Lista 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

Jeśli przyjrzysz się liście 5, zobaczysz, że warstwa usługi ContactManager używa wyłącznie interfejsu IValidationDictionary. Usługa ContactManager nie jest zależna od klasy ModelStateDictionary. Gdy kontroler kontaktów tworzy usługę ContactManager, kontroler opakowuje jego modelState w następujący sposób:

_service = new ContactManagerService(New ModelStateWrapper(ModelState))

Podsumowanie

W tej iteracji nie dodaliśmy żadnych nowych funkcji do aplikacji Contact Manager. Celem tej iteracji było refaktoryzacja aplikacji Contact Manager, aby ułatwić konserwację i modyfikowanie.

Najpierw zaimplementowaliśmy wzorzec projektowania oprogramowania repozytorium. Przeprowadziliśmy migrację całego kodu dostępu do danych do oddzielnej klasy repozytorium ContactManager.

Odizolowaliśmy również logikę weryfikacji od naszej logiki kontrolera. Utworzyliśmy oddzielną warstwę usługi zawierającą cały nasz kod weryfikacyjny. Warstwa kontrolera współdziała z warstwą usługi, a warstwa usługi współdziała z warstwą repozytorium.

Podczas tworzenia warstwy usługi skorzystaliśmy ze wzorca dekoratora, aby odizolować modelState od warstwy usługi. W naszej warstwie usługi programowaliśmy interfejs IValidationDictionary zamiast ModelState.

Na koniec skorzystaliśmy ze wzorca projektowego oprogramowania o nazwie Wzorzec wstrzykiwania zależności. Ten wzorzec umożliwia programowanie interfejsów (abstrakcji) zamiast konkretnych klas. Implementacja wzorca projektowania iniekcji zależności sprawia również, że nasz kod jest bardziej testowalny. W następnej iteracji dodamy testy jednostkowe do naszego projektu.