Archiwa kategorii: Wzorce projektowe

IFactory = abstrakcja na Func<Owned<T>>

Omawiana w poprzednim poście zależność w najprostszej postaci charakteryzuje się pewnym nieprzyjemnym problemem (nie mówiąc o samym fakcie konieczności korzystania z takiej abstrakcji), który postaram się przedstawić na przykładzie.

public interface IFoo
{
	void Save();
}

public class ClassWithResource : Disposable, IFoo
{
	// ...
}

public class ServiceImpl : Disposable, IService
{
	public ServiceImpl(IOwned<IFoo> foo)
	{
		Foo = foo;
	}

	public IOwned<IFoo> Foo { get; private set; }

	public void Add()
	{
		// ...
		Foo.Value.Save();
		// ...
	}

	protected override void DisposeManaged()
    {
        Foo.Dispose();
    }
}

public class ControllerImpl : Disposable, IController
{
	public ControllerImpl(IOwned<IService> service)
	{
		Service = service;
	}

	public IOwned<IService> Service { get; private set; }

	public void HandleRequest()
	{
		// ...
		Service.Value.Add();
		// ...
	}

	protected override void DisposeManaged()
    {
        Service.Dispose();
    }
}

Jak widać korzystanie z „czystego” IOwned poprzez Constructor Injection powoduje, że uzyskujemy łańcuszek zależności IOwned oraz klas implementujących IDisposable – wygląda to co najmniej okropnie. Rozwiązaniem tego problemu jest jak możliwie najszybsze przerwanie tego łańcucha. Najlepiej według mnie to zrobić poprzez połączenia IOwned z wzorcem Factory. Dzięki takiemu podejściu możemy otrzymać następujący kod:

public interface IFoo
{
	void Save();
}

public class ClassWithResource : Disposable, IFoo
{
	// ...
}

public class ServiceImpl : IService
{
	public ServiceImpl(IFactory<IFoo> fooFactory)
	{
		Foo = fooFactory;
	}

	public IFactory<IFoo> FooFactory { get; private set; }

	public void Add()
	{
		// ...
		using (IOwned<IFoo> foo = FooFactory.Create())
		{
			foo.Value.Save();
		}
		// ...
	}
}

public class ControllerImpl : IController
{
	public ControllerImpl(IService service)
	{
		Service = service;
	}

	public IService Service { get; private set; }

	public void HandleRequest()
	{
		// ...
		Service.Add();
		// ...
	}
}

Poniżej przedstawiam sposób w jaki zaimplementowałem IFactory, tak aby można było go zarejestrować w kontenerach, które obsługują wstrzykiwanie Func.

public interface IFactory<out T>
{
	IOwned<T> Create();
}

public interface IFactory<in T, out TOut>
{
	IOwned<TOut> Create(T input);
}

public class Factory<T> : IFactory<T>
{
	private readonly Func<T, IOwned<T>> ownedFactory;
	private readonly Func<T> creator;

	public Factory(Func<T, IOwned<T>> ownedFactory, Func<T> creator)
	{
		this.ownedFactory = ownedFactory;
		this.creator = creator;
	}

	public Factory(Func<T> creator)
		: this(Owned.Create, creator)
	{
	}

	public IOwned<T> Create()
	{
		T ownedInstance = creator();
		IOwned<T> result = ownedFactory(ownedInstance);
		return result;
	}
}

public class Factory<T, TOut> : IFactory<T, TOut>
{
	private readonly Func<TOut, IOwned<TOut>> ownedFactory;
	private readonly Func<T, TOut> creator;

	public Factory(Func<TOut, IOwned<TOut>> ownedFactory, Func<T, TOut> creator)
	{
		this.ownedFactory = ownedFactory;
		this.creator = creator;
	}

	public Factory(Func<T, TOut> creator)
		: this(Owned.Create, creator)
	{
	}

	public IOwned<TOut> Create(T input)
	{
		TOut ownedInstance = creator(input);
		IOwned<TOut> result = ownedFactory(ownedInstance);
		return result;
	}
}

Kod z testami:
Na GitHub w solucji DiProblem.

Implementowanie IDisposable

Nieraz zdarza się, że musimy samodzielnie napisać jakąś klasę implementującą interfejs IDisposable. Wszyscy na pewno znają wzorzec implementowania Dispose. Jednak moim zdaniem ma on kilka wad:

  • Nie jest bezpieczny wątkowo
  • Daje możliwość wielokrotnego sprzątania zasobów – nie zabezpiecza klienta przed wielokrotnym wywołaniem Dispose
  • Jak chcemy mieć finalizator to musimy go sobie sami napisać i wywołać w nim Dispose(false)
  • Nieszczęsna metoda Dispose(bool disposing) sprowadzającego się do tego, że 90% zasobów zwalniamy w bloku „if (disposing)”, a konstrukcja „if (!disposing)” jest w ogóle pozbawiona jakiekolwiek sensu – ogólnie moim zdaniem ten sposób implementacji jest bardzo nieczytelny. Jedną jego zaletą jest możliwość decydowania o dokładnej kolejności zwalania zasobów

Z tego względu proponuję następującą bazową implementację IDisposable:

    public class Disposable : IDisposable
    {
        private const int DisposedFlag = 1;

        private readonly StackTrace creationStackTrace;

        private int _isDisposed;

        public Disposable(bool withFinalizer = false)
        {
#if DEBUG
            creationStackTrace = new StackTrace();
#else
            if (!withFinalizer)
            {
                GC.SuppressFinalize(this);
            }
#endif
        }

        ~Disposable()
        {
            DisposeUnmanaged();
#if DEBUG
            Debug.Fail(GetType() + " in not disposed" + Environment.NewLine + creationStackTrace);
#endif
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Dispose is implemented correctly")]
        public void Dispose()
        {
            int wasDisposed = Interlocked.Exchange(ref _isDisposed, DisposedFlag);
            if (wasDisposed != DisposedFlag)
            {
                DisposeResources();
            }
        }

        protected virtual void DisposeManaged()
        {
        }

        protected virtual void DisposeUnmanaged()
        {
        }

        protected void RequireNotDisposed()
        {
            if (IsDisposed)
            {
                string typeName = GetType().FullName;
                throw new ObjectDisposedException(typeName);
            }
        }

        public bool IsDisposed
        {
            get
            {
                Thread.MemoryBarrier();
                return _isDisposed == DisposedFlag;
            }
        }

        private void DisposeResources()
        {
            DisposeManaged();
            DisposeUnmanaged();
            GC.SuppressFinalize(this);
        }
    }

Zalety klasy Disposable:

  • Bezpieczna wątkowo
  • Zabezpieczenie przed wielokrotnym zwalnianiem zasobów
  • Posiada pomocniczą metodę RequireNotDisposed, którą można wykorzystać w metodach publicznych klasy
  • W konfiguracji Debug zawsze tworzony jest finalizator, który może być użyty podczas śledzenia, czy czasami nie zapomnieliśmy wywołać Dispose
  • Jak mamy zasoby zarządzane, to przeciążamy metodę DisposeManaged. Z kolei w przypadku zasobów niezarządzanych – DisposeUnmanaged
  • Możliwość sprawdzenia czy obiekt ma już zwolnione zasoby poprzez IsDisposed
  • W razie potrzeby posiadania finalizatora wystarczy wykorzystać konstruktor z parameterem (wiem, że bool jest brzydki – można zrobić enuma)

Na marginesie jakby ktoś był zmuszony dziedziczyć (sic!) po klasie dziedziczącej po Disposable to radzę to robić w taki sposób – link. Dzięki temu nie musimy się martwić, że ktoś zapomni wywołać base.Dispose(disposing) albo base.DisposeManagedResources.

Jeżeli, ktoś nie chce albo nie może wykorzystywać dziedziczenia, to proponuję wykorzystać wzorzec delegacji w połączeniu z następującą klasą:

    public class Disposer : Disposable
    {
        private readonly Action action;

        public Disposer(Action action)
        {
            if (action == null) throw new ArgumentNullException("action");
            this.action = action;
        }

        protected override void DisposeManaged()
        {
            action();
        }
    }

Powyższa konstrukcja oparta o delegat jest poprawna tylko w przypadku zasobów zarządzanych. Nie można wykorzystać podobnego mechanizmu dla DisposeUnmanaged. Obiekt Action jest zarządzany i może dojść do zwolnienia jego zasobów zanim wywoła się finalizator obiektu, który posiada do niego referencję.

Wzorce prezentacji MVC MVP MVVM – różnice

Wstęp

Jednym z problemów podczas rozwijania aplikacji jest odseparowanie kodu związanego z interakcją z użytkownikiem od logiki dziedzinowej. Odseparowanie logiki prezentacji upraszcza projekt oraz zwiększa możliwości testowania dzięki wprowadzeniu dekompozycji logiki aplikacji od logiki prezentacji. W obecnej chwili w świecie IT możemy się spotkać z wieloma architektonicznymi wzorcami prezentacji. Najpopularniejszymi z nich są MVC, MVP i MVVM. Niestety z powodu niewielkich różnic są one często nieprawidłowo rozpoznawane, co doprowadza do tego, że nawet doświadczeni programiści często mylą te wzorce. Z tego powodu postanowiłem sporządzić krótki opis każdego ze wymienionych wzorców (bazując na ogólnie uznanych źródłach) oraz analizę różnic, która mogłaby nam pozwolić na uniknięcie pomyłki w rozróżnianiu tych wzorców.

Krótki opis

W przypadku wszystkich wzorców Model’em jest warstwa dziedzinowa (Domain Model).

Model-View-Controller (MVC) jest najczęściej mylonym wzorcem (przeważanie z wariantem MVP: Supervising Controller). Controller jest wyłącznie odpowiedzialny za odbieranie i reagowanie na interakcje użytkownika. Dane do View są pobieranie bezpośrednio z Model’u. MVC występuje w wariantach Pasive Model oraz Active Model w zależności od tego, czy widok jest synchronizowany poprzez obserwator (Active) czy też poprzez referencję (Pasive). Istotną własnością MVC jest przekazywanie interakcji użytkownika bezpośrednio do kontrolera, a nie pośrednio przez widok.

Model-View-Presenter (MVP) charakteryzuje się przede wszystkim tym, że zdarzenia View są oddelegowane do Presenter’a. Presenter jest odpowiedzialny za reagowanie na interakcje użytkownika i zarządzanie stanem widoku oraz mapowanie danych z Model do View. Ze względu na sposób pobierania danych przez View, możemy wyróżnić dwa warianty MVP: Supervising Controller oraz Passive View.  W Passive View, View jest aktualizowany wyłącznie poprzez Presenter (Flow Synchronization). Natomiast w Supervising Controller, View może również korzystać z data-binding‚u do Model’u. Zaletą Passive View w stosunku do Supervising Controller jest brak zależności pomiędzy modelem i widokiem oraz możliwość przetestowania niemal każdego aspektu GUI. Jednak wadą jest większa pracochłonność implementacji.

Model-View-ViewModel (MVVM, wariant Presentation Model) jest podobny do Passive View, jednak różni się sposobem interakcji między widokiem i kontrolerem (ViewModel). ViewModel jest odpowiedzialny za reagowanie na interakcje użytkownika, zarządzanie stanem widoku oraz konwersję danych na reprezentację przyjazną dla View. View aktualizuje się z ViewModel się za pomocą data-binding’u. Zaletą MVVM jest cecha, że ViewModel nie ma silnej zależności do View.

Cechy wspólne

  1. Model jest warstwą dziedzinową
  2. Reakcja na interakcje użytkownika Controller/Presenter/ViewModel
  3. View zawiera interfejs użytkownika

Różnice

Różnice pomiędzy wzorcami można wykazać na czterech płaszczyznach (poniżej dla ułatwienia uwspólniłem termin Controller dla wszystkich wzorców):

1. Miejsce, do którego trafia interakcja użytkownika

  • Przekazywana bezpośrednio do Controller’a (widok nie przechwytuje interakcji użytkownika) – MVC
  • Oddelegowana z View do Controller’a – MVP, MVVM

2. Przechowywanie stanu widoku/prezentacji (np. wybranego elementu)

  • Brak informacji w Contoller – MVC
  • Przechowywany również w Contoller - MVVM, MVP

3. Synchronizacja View

  • Flow Synchronization przez Contoller – MVC Passive Model, MVP
  • Observer Synchronization do Model’u – MVC Active Model
  • Flow Synchronization przez Contoller i Data Binding do Model’u – MVP Supervising Controller 
  • Data Binding (ew.  Observer Synchronization) do Controller’a – MVVM

4. Źródło danych dla View

  • Model – MVC , MVP Supervising Controller
  • Controller – MVP, MVVM

Komentarz o web vs desktop

We frameworkach dekstopowych kontrolki przechwytują interakcje użytkownika – mają w sobie “wbudowany” Contoller. Dlatego stosuje się w nich głównie wzorce MVP oraz MVVM.
Z kolei w  aplikacjach webowych, gdzie występuje interakcja z serwerem, jest inaczej – interakcja użytkownika polega na wysłaniu przez przeglądarkę requesta. Dlatego w dużej części frameworków webowych (np. Struts, ASP.NET MVC) spotykamy się ze wzrocem MVC, gdzie za obsługę requestów odpowiada Controller, który może zwracać widok, ale nie wie jaki jest obecny widok użytkownika. Oczywiście istnieje np. nietypowy ASP.NET WebForms, który przypomina framework desktopowy (więc można w nim skorzystać z MVP), ale to już inna historia…

Podsumowanie

Przede wszystkich należy pamiętać, że przedstawione architektoniczne wzorce prezentacji można implementować i łączyć na wiele sposobów. W VisualWorks Application Model kontroler ma cechy zarówno ViewModel’a jak i Presenter’a. W MVPVM mamy oddzielnie Presnter i ViewModel jako dwa komponenty. Można też się np. spotkać z pojęciem MVCVM albo MVW (Model-View-Whatever). Dobór odpowiedniej implementacji często nie jest prosty i oczywisty, dlatego zawsze przed wyborem warto skorzystać z doświadczeń innych programistów/architektów, uwzględnić złożoność projektu oraz skorzystać z gotowych frameworków upraszających wdrażanie omawianych wzorców.

Tutaj odnośniki do podobnych ciekawych wpisów: http://www.codeproject.com/Articles/66585/Comparison-of-Architecture-presentation-patterns-Mhttp://channel9.msdn.com/Events/TechEd/NorthAmerica/2011/DPR305