Wzorce projektowe przydatne w systemach embedded

ucgosu.pl 5 lat temu

Wzorce projektowe są bardzo popularnym tematem wśród programistów. Zwykle rozmawia się o nich w kontekście języków obiektowych i dużych systemów. Jednak podobnie jak z innymi zagadnieniami dotyczącymi architektury – część wzorców da się z powodzeniem przenieść na grunt systemów embedded. W dzisiejszym wpisie opowiem o trzech wzorcach z najpopularniejszego katalogu wzorców – książki „Gang of four” – które mogą zrobić najwięcej dobrego w architekturze systemów embedded.

Adapter

Zadaniem Adaptera jest dostosowanie istniejącego komponentu do potrzeb naszej aplikacji. Takim komponentem może być na przykład zewnętrzna biblioteka. Posiada ona pewne API, którego nie możemy zmieniać. Możemy wtedy stosować wywołania tej biblioteki bezpośrednio w naszym kodzie. Negatywnym efektem jest wtedy głębokie uzależnienie się od API biblioteki. o ile na przykład zaktualizujemy ją do nowej wersji, albo postanowimy zamienić na inną – będziemy mieć ogromny problem. Będziemy musieli poprawić wszystkie wywołania funkcji API, a nowa biblioteka pewnie się różni i trzeba dodatkowo przystosować do niej logikę aplikacji.

Dlatego w dobrej architekturze zależność idzie w drugą stronę. To nasza aplikacja powinna jasno zdefiniować interfejs jakiego potrzebuje. Biblioteka z kolei musi się do niego dopasować. o ile jej API odbiega od naszego, musimy utworzyć specjalne funkcje wywołujące pod spodem odpowiednie funkcje biblioteki, czyli wrappery. o ile zmieni się API biblioteki, albo zamienimy ją na inną, zakres wymaganych zmian ograniczamy tylko do tych wrapperów. Główna logika naszej aplikacji w ogóle nie odczuje tej zmiany. Adapter między naszą aplikacją a zewnętrzną biblioteką to właśnie zbiór wrapperów.

Zastosowania wzorca Adapter nie musimy ograniczać do odseparowywania gotowych komponentów. Możemy takiej separacji dokonać we wczesnym stadium powstawania architektury umożliwiając w ten sposób niezależne prace nad obydwoma modułami.

Główne zalety wzorca Adapter:

  • Uniezależnienie się od API komponentów zewnętrznych.
  • Możliwość łatwiejszego wprowadzania zmian.
  • Możliwość łatwej wymiany użytych komponentów.

Fasada

Adaptery zwykle służą do tłumaczenia funkcji, czy metod jeden do jednego. Ewentualnie zawierają jakąś prostą logikę. Czasem jednak mogą urosnąć do monstrualnych rozmiarów. Wtedy już nie mówimy o adapterze, tylko o Fasadzie. Fasada to uproszczony interfejs dla całego podsystemu znajdującego się pod spodem ukrywający skomplikowane szczegóły przed użytkownikiem.

Stosując wzorzec Fasady zapobiegamy rozwijaniu się zależności naszej aplikacji od tego podsystemu. Możemy na przykład ukryć w niej wiedzę o kolejnych krokach potrzebnych do wykonania pewnej większej operacji. Dzięki temu możemy zmniejszyć duplikację i szczegółowość naszego kodu. Większość kodu będzie potrzebowała systemu do wykonania pewnych prostych wysokopoziomowych operacji i użycie pojedynczego wywołania funkcji udostępnionej przez Fasadę będzie dużo prostsze niż ciąg wielu czynności na różnych komponentach podsystemu. Jednocześnie o ile jakiemuś komponentowi nie wystarcza uproszczony interfejs Fasady, zawsze może dostać się do funkcji podsystemu. Jednak zwykle oznacza to raczej jakiś błąd w designie. Być może brakuje pewnych funkcji interfejsu w naszej Fasadzie.

Główne zalety wzorca Fasada:

  • Ukrycie szczegółów dużego podsystemu.
  • Większa czytelność kodu korzystającego z Fasady.
  • Szybsze powstawanie kodu klienckiego.

Strategia

Często w naszych systemach mamy do wykonania pewien algorytm, który może być zrealizowany na różne sposoby. Możliwe implementacje różnią się złożonością, szybkością wykonania, czy potrzebną pamięcią. Najprostszym rozwiązaniem jest wybór jednej implementacji i zahardcodowanie jej w systemie. Problem oczywiście pojawia się jeżeli zmienimy zdanie i chcemy użyć innego algorytmu, albo w ogóle chcemy korzystać z nich wymiennie i decydować w runtime. Z pomocą przychodzi nam wtedy wzorzec Strategii, znany również pod nazwą Polityki (Policy).

Wzorzec Strategii polega na wydzieleniu naszego algorytmu jako osobny moduł z własnym interfejsem wspólnym dla wszystkich implementacji. o ile chcemy zmienić algorytm, wystarczy użyć innej implementacji tego samego interfejsu. Z punktu widzenia kodu aplikacji wywołującego algorytm nie będzie żadnej zmiany. Możemy więc napisać naszą aplikację tak, aby wywoływała algorytm, który na początku działa prosto. W dalszej fazie projektu możemy ulepszać algorytmy przy minimalnym nakładzie pracy związanym z integracją z istniejącym systemem.

Główne zalety wzorca Strategia:

  • Możliwość zmiany algorytmu w dowolnym momencie.
  • Możliwość dodawania nowych algorytmów wraz z rozwojem projektu.
  • Zwiększenie czytelności poprzez wydzielenie algorytmu.

Podsumowanie

Opisane wzorce pozwalają na oddzielenie logiki naszej aplikacji od szczegółów implementacyjnych takich jak algorytmy, biblioteki zewnętrzne, czy choćby całe podsystemy. Dzięki takiemu zabiegowi zyskujemy wielką elastyczność. Możemy te elementy wymieniać w dowolnym momencie trwania projektu przy minimalnym nakładzie pracy na integrację.

Wszystkie te techniki mają jednak jedną wspólną wadę – wprowadzają dodatkową warstwę abstrakcji zwiększającą złożoność systemu i mogącą negatywnie wpłynąć na wydajność. W dużych projektach otrzymana w zamian elastyczność jest dużo cenniejsza. Jednak analizę należy wykonać zawsze pod kątem konkretnej sytuacji.

Idź do oryginalnego materiału