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

autor: Microsoft

Pobierz kod

W tej czwartej iteracji korzystamy z kilku wzorców projektowania 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 (C#)

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. Po każdej iteracji stopniowo ulepszamy aplikację. Celem tego podejścia iteracji wielokrotnej jest umożliwienie zrozumienia przyczyny każdej zmiany.

  • Iteracja #1 — tworzenie aplikacji. W pierwszej iteracji utworzymy menedżera kontaktów w najprostszy możliwy sposób. Dodamy obsługę podstawowych operacji bazy danych: tworzenie, odczytywanie, 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ą ASP.NET widoku MVC i 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 połącz aplikację. W tej czwartej iteracji korzystamy z kilku wzorców projektowania 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 dodanie testów jednostkowych. Wyśmiewamy nasze klasy modelu danych i kompilujemy testy jednostkowe dla naszych kontrolerów i logiki walidacji.

  • Iteracja nr 6 — korzystanie z programowania opartego na testach. W tej szóstej iteracji dodamy nową funkcjonalność do naszej aplikacji, 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 reakcji i wydajność naszej aplikacji, dodając obsługę Ajax.

Ta iteracja

W tej czwartej iteracji aplikacji Contact Manager refaktoryzujemy aplikację, aby aplikacja była bardziej luźno połączona. 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 kontrolera. To jest zły pomysł. Zawsze, 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 kontrolera, walidacji i logiki bazy danych jest ogromnym naruszeniem zasady odpowiedzialności pojedynczej.

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 zmodyfikowanie sposobu implementacji funkcji aplikacji. Aplikacje są rzadko statyczne. Mają tendencję do wzrostu i mutacji 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 weryfikacji 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. Można na przykład przełączać technologie dostępu do danych bez modyfikowania logiki weryfikacji lub kontrolera.

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

Uwaga

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

Korzystanie ze wzorca projektowania oprogramowania repozytorium

Pierwsza zmiana polega na wykorzystaniu wzorca projektowania oprogramowania nazywanego wzorcem repozytorium. Użyjemy wzorca repozytorium, aby odizolować kod dostępu do danych z pozostałej części naszej aplikacji.

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

  1. Tworzenie interfejsu
  2. Tworzenie betonowej klasy, która implementuje 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.cs

using System;
using System.Collections.Generic;

namespace ContactManager.Models
{
    public interface IContactRepository
    {
        Contact CreateContact(Contact contactToCreate);
        void DeleteContact(Contact contactToDelete);
        Contact EditContact(Contact contactToUpdate);
        Contact GetContact(int id);
        IEnumerable<Contact> ListContacts();

    }
}

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

Lista 2 — Models\EntityContactManagerRepository.cs

using System.Collections.Generic;
using System.Linq;

namespace ContactManager.Models
{
    public class EntityContactManagerRepository : ContactManager.Models.IContactManagerRepository
    {
        private ContactManagerDBEntities _entities = new ContactManagerDBEntities();

        public Contact GetContact(int id)
        {
            return (from c in _entities.ContactSet
                    where c.Id == id
                    select c).FirstOrDefault();
        }

        public IEnumerable ListContacts()
        {
            return _entities.ContactSet.ToList();
        }

        public Contact CreateContact(Contact contactToCreate)
        {
            _entities.AddToContactSet(contactToCreate);
            _entities.SaveChanges();
            return contactToCreate;
        }

        public Contact EditContact(Contact contactToEdit)
        {
            var originalContact = GetContact(contactToEdit.Id);
            _entities.ApplyPropertyChanges(originalContact.EntityKey.EntitySetName, contactToEdit);
            _entities.SaveChanges();
            return contactToEdit;
        }

        public void DeleteContact(Contact contactToDelete)
        {
            var originalContact = GetContact(contactToDelete.Id);
            _entities.DeleteObject(originalContact);
            _entities.SaveChanges();
        }

    }
}

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.

W ten sposób możemy zaimplementować interfejs z nową klasą bez konieczności modyfikowania pozostałej części naszej aplikacji. Na przykład w przyszłości możemy zaimplementować klasę DataServicesContactManagerRepository, która implementuje interfejs IContactManagerRepository. Klasa DataServicesContactManagerRepository może używać usług 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 żadnej z pozostałych części naszego kodu. Na przykład możemy przełączyć się z klasy EntityContactManagerRepository do klasy 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. Możesz na przykład najpierw utworzyć klasę EntityContactManagerRepository, a następnie użyć interfejsu extract, aby automatycznie wygenerować interfejs IContactManagerRepository.

Korzystanie ze wzorca projektowania oprogramowania iniekcyjnego 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.cs

using System.Text.RegularExpressions;
using System.Web.Mvc;
using ContactManager.Models;

namespace ContactManager.Controllers
{
    public class ContactController : Controller
    {
        private IContactManagerRepository _repository;

        public ContactController()
            : this(new EntityContactManagerRepository())
        {}

        public ContactController(IContactManagerRepository repository)
        {
            _repository = repository;
        }

        protected void ValidateContact(Contact contactToValidate)
        {
            if (contactToValidate.FirstName.Trim().Length == 0)
                ModelState.AddModelError("FirstName", "First name is required.");
            if (contactToValidate.LastName.Trim().Length == 0)
                ModelState.AddModelError("LastName", "Last name is required.");
            if (contactToValidate.Phone.Length > 0 && !Regex.IsMatch(contactToValidate.Phone, @"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
                ModelState.AddModelError("Phone", "Invalid phone number.");
            if (contactToValidate.Email.Length > 0 && !Regex.IsMatch(contactToValidate.Email, @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
                ModelState.AddModelError("Email", "Invalid email address.");
        }

        public ActionResult Index()
        {
            return View(_repository.ListContacts());
        }

        public ActionResult Create()
        {
            return View();
        } 

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create([Bind(Exclude = "Id")] Contact contactToCreate)
        {
            // Validation logic
            ValidateContact(contactToCreate);
            if (!ModelState.IsValid)
                return View();

            // Database logic
            try
            {
                _repository.CreateContact(contactToCreate);
                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }

        public ActionResult Edit(int id)
        {
            return View(_repository.GetContact(id));
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Edit(Contact contactToEdit)
        {
            // Validation logic
            ValidateContact(contactToEdit);
            if (!ModelState.IsValid)
                return View();

            // Database logic
            try
            {
                _repository.EditContact(contactToEdit);
                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }

        public ActionResult Delete(int id)
        {
            return View(_repository.GetContact(id));
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Delete(Contact contactToDelete)
        {
            try
            {
                _repository.DeleteContact(contactToDelete);
                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }

    }
}

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 Kontroler kontaktów używa iniekcji zależności konstruktora.

Jedyną i jedyną pozycją, która jest używana przez klasę EntityContactManagerRepository, jest pierwszy konstruktor. 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, po prostu 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 Contact 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 trzeba odwoływać się do konkretnej klasy w kodzie.

Tworzenie warstwy usługi

Być może zauważyliśmy, że nasza logika walidacji nadal jest mieszana z naszą logiką kontrolera w zmodyfikowanej klasie kontrolera na liście 3. Z tego samego powodu dobrym pomysłem jest odizolowanie logiki dostępu do danych, dlatego dobrym pomysłem jest odizolowanie logiki walidacji.

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

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

Lista 4 — Models\ContactManagerService.cs

using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Web.Mvc;
using ContactManager.Models.Validation;

namespace ContactManager.Models
{
    public class ContactManagerService : IContactManagerService
    {
        private IValidationDictionary _validationDictionary;
        private IContactManagerRepository _repository;

        public ContactManagerService(IValidationDictionary validationDictionary) 
            : this(validationDictionary, new EntityContactManagerRepository())
        {}

        public ContactManagerService(IValidationDictionary validationDictionary, IContactManagerRepository repository)
        {
            _validationDictionary = validationDictionary;
            _repository = repository;
        }

        public bool ValidateContact(Contact contactToValidate)
        {
            if (contactToValidate.FirstName.Trim().Length == 0)
                _validationDictionary.AddError("FirstName", "First name is required.");
            if (contactToValidate.LastName.Trim().Length == 0)
                _validationDictionary.AddError("LastName", "Last name is required.");
            if (contactToValidate.Phone.Length > 0 && !Regex.IsMatch(contactToValidate.Phone, @"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
                _validationDictionary.AddError("Phone", "Invalid phone number.");
            if (contactToValidate.Email.Length > 0 && !Regex.IsMatch(contactToValidate.Email, @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
                _validationDictionary.AddError("Email", "Invalid email address.");
            return _validationDictionary.IsValid;
        }

        #region IContactManagerService Members

        public bool CreateContact(Contact contactToCreate)
        {
            // Validation logic
            if (!ValidateContact(contactToCreate))
                return false;

            // Database logic
            try
            {
                _repository.CreateContact(contactToCreate);
            }
            catch
            {
                return false;
            }
            return true;
        }

        public bool EditContact(Contact contactToEdit)
        {
            // Validation logic
            if (!ValidateContact(contactToEdit))
                return false;

            // Database logic
            try
            {
                _repository.EditContact(contactToEdit);
            }
            catch
            {
                return false;
            }
            return true;
        }

        public bool DeleteContact(Contact contactToDelete)
        {
            try
            {
                _repository.DeleteContact(contactToDelete);
            }
            catch
            {
                return false;
            }
            return true;
        }

        public Contact GetContact(int id)
        {
            return _repository.GetContact(id);
        }

        public IEnumerable<Contact> ListContacts()
        {
            return _repository.ListContacts();
        }

        #endregion
    }
}

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

Należy również zauważyć, że element ContactManagerService implementuje interfejs IContactManagerService. Zawsze należy dążyć do programowania pod kątem 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 zaprogramowana względem interfejsu IContactManagerService.

Interfejs IContactManagerService znajduje się na liście 5.

Lista 5 — Models\IContactManagerService.cs

using System.Collections.Generic;

namespace ContactManager.Models
{
    public interface IContactManagerService
    {
        bool CreateContact(Contact contactToCreate);
        bool DeleteContact(Contact contactToDelete);
        bool EditContact(Contact contactToEdit);
        Contact GetContact(int id);
        IEnumerable ListContacts();
    }
}

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 jak najbardziej odizolowana od innych warstw.

Lista 6 — Controllers\ContactController.cs

using System.Web.Mvc;
using ContactManager.Models;

namespace ContactManager.Controllers
{
    public class ContactController : Controller
    {
        private IContactManagerService _service;

        public ContactController()
        {
            _service = new ContactManagerService(new ModelStateWrapper(this.ModelState));

        }

        public ContactController(IContactManagerService service)
        {
            _service = service;
        }
        
        public ActionResult Index()
        {
            return View(_service.ListContacts());
        }

        public ActionResult Create()
        {
            return View();
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create([Bind(Exclude = "Id")] Contact contactToCreate)
        {
            if (_service.CreateContact(contactToCreate))
                return RedirectToAction("Index");
            return View();
        }

        public ActionResult Edit(int id)
        {
            return View(_service.GetContact(id));
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Edit(Contact contactToEdit)
        {
            if (_service.EditContact(contactToEdit))
                return RedirectToAction("Index");
            return View();
        }

        public ActionResult Delete(int id)
        {
            return View(_service.GetContact(id));
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Delete(Contact contactToDelete)
        {
            if (_service.DeleteContact(contactToDelete))
                return RedirectToAction("Index");
            return View();
        }

    }
}

Nasza aplikacja nie działa już w przypadku zasady o pojedynczej odpowiedzialności (SRP). Kontroler kontaktów na liście 6 został pozbawiony 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.

Korzystanie ze 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 oddzielnym 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 walidacji z powrotem do warstwy kontrolera. Jak umożliwić warstwie usługi komunikowanie komunikatów o błędach walidacji bez sprzęgania kontrolera i warstwy usługi? Możemy skorzystać ze wzorca projektowego oprogramowania o nazwie Wzorzec dekoratora.

Kontroler używa klasy ModelStateDictionary o nazwie ModelStateState do reprezentowania błędów walidacji. W związku z tym może 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ług z aplikacją WPF zamiast ASP.NET aplikacji 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 Listing 7. Klasa ModelStateWrapper implementuje interfejs w liście 8.

Lista 7 — Models\Validation\ModelStateWrapper.cs

using System.Web.Mvc;

namespace ContactManager.Models.Validation
{
    public class ModelStateWrapper : IValidationDictionary
    {
        private ModelStateDictionary _modelState;

        public ModelStateWrapper(ModelStateDictionary modelState)
        {
            _modelState = modelState;
        }

        public void AddError(string key, string errorMessage)
        {
            _modelState.AddModelError(key, errorMessage);
        }

        public bool IsValid
        {
            get { return _modelState.IsValid; }
        }
    }
}

Lista 8 — Models\Validation\IValidationDictionary.cs

namespace ContactManager.Models.Validation
{
    public interface IValidationDictionary
    {
        void AddError(string key, string errorMessage);
        bool IsValid {get;}
    }
}

Jeśli przyjrzysz się bliżej 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(this.ModelState));

Podsumowanie

W tej iteracji nie dodaliśmy żadnych nowych funkcji do aplikacji Contact Manager. Celem tej iteracji było refaktoryzacja aplikacji Contact Manager, co ułatwia 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ę walidacji od logiki kontrolera. Utworzyliśmy oddzielną warstwę usługi, która zawiera cały kod weryfikacji. 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 z wzorca dekoratora, aby odizolować element ModelState od warstwy usług. W 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 projektowego wstrzykiwania zależności sprawia również, że nasz kod jest bardziej testowalny. W następnej iteracji dodajemy testy jednostkowe do naszego projektu.