Blog
Blog HelionBlog HelionBlog HelionBlog Helion
  • Artykuły
  • Autorzy
  • Recenzje
  • Konkursy

Wzorzec projektowy strategii

    Blog.helion.pl Artykuły Wzorzec projektowy strategii
    NastępnyPoprzedni

    Wzorzec projektowy strategii

    By Jerzy Piechowiak | Artykuły, Bez kategorii, Programowanie | Brak komentarzy | 3 listopada, 2016 | 2

    Programowanie, wbrew obiegowym opiniom, nie jest aż takie trudne. Wypuszczenie nawet prostych aplikacji mobilnych czy stron nie wymaga dzisiaj dużych nakładów sił. Największym problemem jest napisanie takiego kodu, który będzie łatwo rozszerzalny i będzie miał dla nas sens nawet wtedy, gdy spojrzymy na niego za kilka lat.

    Ładny kod to nie tylko dobra stylistyka, ale także wzorce projektowe. W ostatnim tekście opisałem stosunkowo mało znany wzorzec null object (zobacz wpis). Dziś z kolei chciałbym się zająć dużo bardziej popularnym rozwiązaniem, czyli tzw. wzorcem strategii.

     

    Wzorzec strategii

    Niejednokrotnie, tworząc oprogramowanie, musimy być przygotowani do obsłużenia sytuacji, w której kontekst zmienia się dynamicznie. W pewnych okolicznościach program może działać według jednego schematu, w innych zupełnie inaczej. Można by nawet powiedzieć, że ta sama aplikacja może mieć różne strategie działania. Takie warianty muszą być obsłużone niezależnie. Po uruchomieniu programu nie do końca wiadomo, który z nich zostanie użyty. Może to zależeć np. od parametrów wejściowych wprowadzonych na formatce czy też w linii komend.

     

    Aplikacja musi więc obsługiwać rodzinę różnych algorytmów, które mogą posłużyć do rozwiązywania problemów z określonej grupy. Takie algorytmy powinny działać wymiennie — tzn. że da się je wpiąć w to samo miejsce w kodzie i wywoływać odpowiednio w zależności od danych wejściowych.

     

    Być może opis zabrzmiał odrobinę zawile, ale przykład praktyczny pokaże, że wzorzec strategii rozwiązuje standardowe problemy, na które możemy się natknąć w kodzie źródłowym każdego dnia.

     

    Przykład problemu

    Wyobraźmy sobie, że musimy napisać aplikację, która na bazie danych wprowadzonych przez użytkownika powinna dokonywać wyceny określonego problemu. Na potrzeby przykładu wymyśliłem trzy możliwe warianty wyceny:

     

    • ostrożna — mnożymy szacunkową liczbę godzin razy dwa;
    • normalna — brak zmian;
    • agresywna — dzielimy szacunkową liczbę godzin przez dwa.

     

    Dostępne warianty są opisane za pomocą enumeracji. Użytkownik oprócz wybrania rodzaju wyceny musi jeszcze wprowadzić szacunkową liczbę godzin. Poniżej przykład prostej aplikacji:

     
    public enum EvaluationType
    {
    	Careful = 0,
    	Normal,
    	Agressive
    }
    
    public class TestProgram
    {
    	public double Calculate(EvaluationType evaluationType, double hours)
    	{
    		double evaluation = 0;
    		switch(evaluationType)
    		{
    			case EvaluationType.Careful:
    				evaluation = hours * 2;
    				break;
    			case EvaluationType.Agressive:
    				evaluation = hours * 0.5;
    				break;
    			case EvaluationType.Normal:
    			default:
    				evaluation = hours;
    				break;
    		}
    		return evaluation;
    	}
    }
    

    Program właściwie składa się z tylko jednej klasy, co oczywiście w kwestii potencjalnego rozwoju niesie ze sobą kilka problemów:

    • zastosowanie switcha, który nie jest do końca czytelny;
    • cały kod znajdujący się w jednej klasie — jawne pogwałcenie kilku zasad SOLID (zobacz wpisy z serii);
    • sporo problemów w przypadku potencjalnego rozwoju kodu dla któregokolwiek z algorytmów.

     

    Jak to można naprawić? Oczywiście wprowadzając w życie wzorzec strategii.

     

    Refaktoryzacja do wzorca strategii 

    Podstawowym założeniem programistycznym wzorca strategii jest wprowadzenie abstrakcji (w tym przypadku interfejsu), który będzie definiował metodę wykorzystywaną do obliczeń. Każdy typ wyceny otrzyma swoją dedykowaną klasę, która zaimplementuje wspomniany wyżej interfejs.

     

    Dodatkową atrakcją tego przykładu będzie słownik, który pozwoli na całkowite usunięcie switcha i znaczne uproszczenie klasy testowego programu.

     

    Jedynym elementem, który pozostanie z poprzedniego przykładu, jest enumeracja. Poniżej kod:

     
    public interface IEvaluationStrategy
    {
    	double Evaluate(double hours);
    }
    
    public class CarefulEvaluationStrategy : IEvaluationStrategy
    {
    	public double Evaluate(double hours)
    	{
    		return hours * 2;
    	}
    }
    
    public class NormalEvaluationStrategy : IEvaluationStrategy
    {
    	public double Evaluate(double hours)
    	{
    		return hours;
    	}
    }
    
    public class AggresiveEvaluationStrategy : IEvaluationStrategy
    {
    	public double Evaluate(double hours)
    	{
    		return hours*0.5;
    	}
    }
    
    public class StrategyPatternProgram
    {
    	private readonly Dictionary<EvaluationType, IEvaluationStrategy>
    		strategies = new Dictionary<EvaluationType, IEvaluationStrategy>
    		{
    			{EvaluationType.Careful, new CarefulEvaluationStrategy()},
    			{EvaluationType.Normal, new NormalEvaluationStrategy()},
    			{EvaluationType.Agressive, new AggresiveEvaluationStrategy()}
    		};
    
    	public double Calculate(EvaluationType evaluationType, double hours)
    	{
    		return this.strategies[evaluationType].Evaluate(hours);
    	}
    }
    
    

    Zwieńczeniem przykładu będzie kod aplikacji konsolowej, który uruchomi stare i nowe rozwiązanie:

     
    public class Program
    {
    	public static void Main(string[] args)
    	{
    	    EvaluationType evaluation = EvaluationType.Careful;
    	    double hours = 172.5;
    
    	    // Old way
    	    TestProgram testProgram = new TestProgram();
    	    Console.WriteLine($"Test program evaluation: {testProgram.Calculate(evaluation, hours)}");
    
    	    // Strategy pattern
    	    StrategyPatternProgram testStrategyProgram = new StrategyPatternProgram();
    	    Console.WriteLine($"Test strategy program evaluation: {testStrategyProgram.Calculate(evaluation, hours)}");
    
    	    Console.Read();
    	}
    }
    

     

    Oczywiście wprowadzenie wzorca strategii w tak prostym przykładzie znacznie wydłużyło cały kod, ale jednocześnie stał się on dużo bardziej elastyczny i czytelny. W rzeczywistych przypadkach wzorzec strategii powinien być oczywiście wprowadzony z głową. Jak to się często mówi — nie powinniśmy strzelać do muchy z armaty, a także nie obawiać się refaktoryzacji. Inna sprawa to pisanie kodu w taki sposób, by w ogóle nadawał się do refaktoryzacji, ale to już temat na inny wpis…

     

    Jerzy Piechowiak

    Altcontroldelete.pl

    c#, Jerzy Piechowiak, programowanie, SOLID, wzorce projektowe
    Avatar

    Jerzy Piechowiak

    Więcej postów od Jerzy Piechowiak

    Podobne posty

    • Wzorce projektowe — null object

      By Jerzy Piechowiak | Brak komentarzy

      „Object reference not set to an instance of an object” — taki, pojawiający się w najmniej oczekiwanym momencie komunikat to zmora każdego programisty C#. Przyczyna jest zawsze taka sama, a jest nią wyjątek NullReferenceException.

    • Wzorzec projektowy — metoda wytwórcza

      By Jerzy Piechowiak | Brak komentarzy

      Czasem mówi się, że wzorce projektowe uzupełniają braki techniczne w językach programowania. Jeśli spojrzymy na kilka popularnych wzorców, to szybko dostrzeżemy schemat. Wiele z nich krąży wokół szeroko rozumianego polimorfizmu.

    • Wzorzec projektowy singleton

      By Jerzy Piechowiak | Brak komentarzy

      Ze wzorcami projektowymi bywa różnie. Niektóre są bardzo przydatne, inne mniej, a jeszcze inne są pożądane tylko w określonych sytuacjach. Ogólnie bardzo łatwo wpaść w pułapkę nadużycia określonych rozwiązań. Taka sytuacja ma często miejsce wCzytaj więcej…

    • Wzorce projektowe – Template method

      By Jerzy Piechowiak | Brak komentarzy

      Kilka razy spotkałem się z opinią, że wzorce projektowe są pewnym uzupełnieniem języków programowania, ponieważ realizują zadania, które powinny być dostępne out of the box. Trudno polemizować z takim stwierdzeniem w przypadku singletona czy factoryCzytaj więcej…

    • Mnemonik SOLID – D jak Dependency Inversion Principle

      By Jerzy Piechowiak | Brak komentarzy

      Przyszła pora na ostatnią literkę z mnemonika SOLID, ale to wcale nie oznacza, że jest ona najmniej ważna. D rozwijane jest jako Dependency Inversion Principle co tłumaczone jest jako Zasada Odwrócenia Zależności.

    NastępnyPoprzedni

    Znajdź post

    Bądźmy w kontakcie

    Książka dnia

    Wszechstronny JavaScript. Technologie: GraphQL, React, React Native i Electron

    Autor: Adam D. Scott

    Cena: 34.50 zł 69.00 zł
    (Cena e-booka: 34.50 zł 69.00 zł)

    O 35zł taniej!

    kup teraz

    Ostatnie wpisy

    • Błyskawiczny kurs pisania skryptów powłoki
    • Przykładowa aplikacja webowa zaimplementowana w ASP .NET Core
    • Wprowadzenie do .NET Core: instalacja, konfiguracja, pierwsza aplikacja w systemie Linux
    • Grupa Helion zaprasza na szkolenia stacjonarne!
    • Hello World! Czym jest programowanie?

    Tagi

    .net agile altcontroldelete asp.net c# czysty kod debugowanie design patterns e-biznes emarketing Google Google Analytics hacking Jerzy Piechowiak kod kodowanie Krzysztof Marzec książka Maciej Dutko magazyn programista Magdalena Daniłoś marketing MVVM onepress organizacja czasu praca prograista programista programowanie prokrastynacja rafał kocisz reklama rozwój software craftsman SOLID startup techniki programowania testowanie video marketing visual studio WPF wzorce projektowe youtube zarządzanie czasem zarządzanie projektami

    Archiwum

    • lipiec 2017
    • czerwiec 2017
    • maj 2017
    • kwiecień 2017
    • marzec 2017
    • luty 2017
    • styczeń 2017
    • grudzień 2016
    • listopad 2016
    • październik 2016
    • wrzesień 2016
    • lipiec 2016
    • czerwiec 2016
    Blog wydawnictwa
    Informatyka w najlepszym wydaniu
    Strona wydawcy:
    www.helion.pl
    Księgarnia Helion.pl
    Nowości
    Bestsellery
    Promocje
    Bądźmy w kontakcie:
    Chcesz zostać autorem?
    Masz pytania do redakcji?
    Napisz do nas »
    • Artykuły
    • Autorzy
    • Recenzje
    • Konkursy
    Blog Helion