Obiektowość na AVR od podstaw 6 z n...

Tu poruszamy tematy związane z pisaniem programów w języku C++ dla AVR.
Awatar użytkownika
mokrowski
User
User
Posty: 190
Rejestracja: czwartek 08 paź 2015, 20:50
Lokalizacja: Tam gdzie Centymetro

Obiektowość na AVR od podstaw 6 z n...

Postautor: mokrowski » sobota 24 cze 2017, 04:33

Tym razem zaczniemy nietypowo bo od omówienia pewnych mechanizmów obiektowych dużego projektu a dopiero w dalszych częściach przejdziemy do kodu. To będzie trochę „wystrzelenie w kosmos” ale człowiek zawsze mierzył wysoko:-) Duży obraz poważnego zastosowania :-) Wbrew pozorom przy takim podejściu wcale nie powstaje opasły kod jeśli wiesz co robisz. Oczywiście nie będzie to „czyste teoretyzowawanie” a przykład na pewno przyda Ci się w praktyce programowania na MCU.

Załóżmy przez chwilę że tworzysz klasę do obsługi akwizycji danych z czujników stacji pogodowej. Każdy zapis który będziesz zbierał/a, będzie z zapisywany pewnie na karcie SD (mikro/mini czy innej) i będzie zawierał informacje o wartościach w danym czasie. Ja w mojej wyimaginowanej stacji będę zbierał: temperaturę i ciśnienie. Z różnych powodów narzuconych przez wymagania klienta, nakazał Ci on zbieranie tych danych w plikach w którym każdy dzień to oddzielny plik. Zadanie zrealizowane jest ambitnie, z użyciem podejścia obiektowego a że widzisz że takich klientów jest więcej, chcesz mieć reużywalny kod. Zaprojektujmy więc taki kod na razie z użyciem notacji UML.

Pojawią się (możesz polemizować z pomysłem bo to tylko propozycja do tutoriala) więc klasy:

To jest 1 podejście (ja wiem że ułomne bo chcę pokazać istotne zagadnienia)

Plik – klasa będzie odpowiedzialna (odsyłam do poprzednich części tego cyklu co oznacza odpowiedzialność) za obsługę działań zapis/odczyt pliku. Jeśli zmieni się format danych docelowych tu zmienisz kod.
Pomiar – klasa zawiera w zasadzie wyłącznie dane typu: czas z datą, wartość ciśnienia i wilgotności. Zawierać będzie metody rozwiązujące problemy jak prezentować dane ASCII, dane surowe, dane do zapisu na kartę lub do przesłania przez interfejsy komunikacyjne. To taki „worek na dane” :-)
CzujnikCisnienia – obsługuje wykonanie pomiaru ciśnienia i zwraca tenże pomiar. Kod ew. będzie zmieniany jeśli zdecydujesz się na zmianę układu czujnika/jego szyny/sposobu odczytu.
CzujnikTemperatury – bardzo podobnie jak CzujnikCisnienia. Obsługuje pomiar temperatury.
Stacja – główny obiekt obsługujący stację i koordynujący działanie menu oraz wykonywanych pomiarów

Może być? Ok, może być jak na pierwszy szkic. Spróbujmy to narysować:
Obrazek

Jak widzisz pojawiły się zwykłe linie nazywane asocjacją które oznaczają że dane obiekty wyprowadzone z klas będą ze sobą współpracowały. Widzisz także że klasa Stacja ma wiele linii połączonych z innymi klasami. Ja ją tu specjalnie narysowałem w takim układzie że wygląda jak „słońce” :-) Często bowiem taka ilość „sznurków” prowadząca do 1 klasy oznacza że klasa za wiele chce robić w aplikacji? Nie wierzysz? Ok. No to zastanówmy się razem....

Jeśli będziesz chciał zbierać dane raz z 4 czujników a raz z 40. Kto je będzie wykrywał/przetrzymywał wskazania do nich ... klasa Stacja. Jeśli klient będzie chciał w urządzeniu X1 podłączyć wyświetlacz LCD 2x16 znaków kto go obsłuży ... klasa Stacja. Jeśli klient będzie chciał obsłużyć komunikację szeregową (nie ważne czy RS232 czy Bluetooth czy inny modem) jaki kod obsłuży sposób prezentacji danych ... klasa Stacja. Nie będę się już pastwił nad tym monstrum (jeśli powstał by kod klasy Stacja). Jak widać jest tu błąd koncepcyjny związany ze zbyt dużą ilością odpowiedzialności w jednostce kodu. Widać także że należy kod podzielić na inne klasy.

Dobrze. Pojawiły się często występujące aspekty projektu:
1. Prezentacji danych (LCD, RS..., Bluetooth, modem radio/telco ... )
2. Koordynacji pomiarów (ile czujników, jak je zainicjować i jak je obsłużyć)
3. Sterowania całością urządzenia

Wyrzucamy więc klasę Stacja i tworzymy dodatkowe klasy:
KontrolerPomiarow – wie jakie czujniki posiada stacja i potrafi wywołać pomiar. Współpracuje z innymi obiektami w celu wykonania zadań Stacji Pogodowej
KontrolerStacji – zajmuje się wywołaniem pomiaru na KontrolerzePomiarów, przetrzymuje ustawienia. Jeśli będzie to potrzebne, zapyta inne obiekty np. o ilość czujników, zakres ich pracy itp.
KontrolerKomunikacji – obsłuży wysyłanie danych czy na kartę (wtedy będzie współpracował z obiektem Plik) czy przez inne media. W zależności od ilości kodu jeszcze zadecydujemy czy będzie oddzielna klasa dla komunikacji szeregowej, z użyciem modemu radiowego itp...
MenuStacji – klasa która obsłuży ustawianie parametrów stacji Będzie współpracowała głównie z KontroleremStacji i KontroleremKomunikacji.

No to rysujemy to w UML'u. Ten diagram z konieczności jeszcze jest niekompletny ale dla tych którzy zapytają po co tak wiele zachodu, odpowiem.
Obrazek
Przede wszystkim pokazuję taki wielki (a może mały :-)) obraz abyś na własne oczy zobaczył/a jak można zrealizować hermetyzację pracy zespołu programistów oraz przyśpieszyć wytwarzanie nowych produktów. Oczywiście do małego projektu „zamrugaj diodą” nie ma merytorycznego sensu stosowanie podejścia obiektowego (chyba że tak jak tu chcesz się go nauczyć). Takie dobrze napisane klasy które współpracują ze sobą będą bardzo reużywalne w kontekstach różnych projektów. Budowanie z takich klocków rozwiązań będzie i szybkie i pozbawione większości błędów które masz w podejściu proceduralnym. Tam często występuje syndrom: „jak przestawię tę zmienną to przestaje działać” albo drugi syndrom „volatile”. Czytasz forum to wiesz o czym mówię :-) To jest pierwszy powód.

Drugi powód jest bardzo prosty. Tym przykładem chcę sprowokować wystąpienia różnych form współpracy pomiędzy obiektami. Na pewno nie wyczerpię tematu wszystkich kruczków i sztuczek podejścia obiektowego ale to co będzie niezbędne, na pewno będę prezentował na praktycznym przykładzie.

Wróćmy więc do diagramu.

KontrolerPomiarow zawiera w sobie odniesienia do czujników. Nikt więcej (żadna klasa) nie zajmuje się komunikacją bezpośrednią z czujnikiem. Sensowne jest więc że KontrolerPomiarów „łączy w sobie” jakieś wskazania do czujników. Ba, ale jak łączy. Bo łączyć można na wiele sposobów.

Jeśli jedynie „wie” jak się do nich dostać no to pewnie będzie miał jakąś tablicę wskaźników a gdzie indziej w kodzie będziemy zajmowali się „powołaniem do życia” czujników. Taka relacja będzie nazywana agregacją. To jest tak jak wracasz z hipermarketu z kiepską torbą papierową która zawiera produkty (jakie zawiera!.. teraz fachowo agreguje :-) ). Jeśli pęknie, produkty rozsypią się na parkingu ale nie znikną. Oczywiście nie oznacza to że masz od dziś na prośbę partnera/partnerki „proszę pójdź do sklepu” odpowiadać że „nie mam gdzie zagregować produktów” :-P

Jeśli KontrolerPomiarow sam będzie wykrywał i zarządzał czujnikami, to będzie tu relacja kompozycji. KontrolerPomiarow ma „wbudowane” odniesienia do czujników i zajmuje się powołaniem ich do życia ale także i ew. destrukcją (to ostatnie pewnie nie nastąpi w zwykłym MCU, ale aby jednak podać przykład takiej możliwości załóżmy że stacja jeśli będzie pracowała na zasilaniu z baterii znajdującej się na wyczerpaniu może podjąć decyzję o wyłączeniu części czujników lub ich włączania/wyłączania/robienia pomiaru rzadziej). W relacji kompozycji KontrolerPomiarow będzie w swoim kodzie kreował czujniki a w swoim destruktorze je .. niszczył (spokojnie ... chodzi o obiekt czujnika a nie „dym z kostki” :-)).
Obrazek

Relacja agregacji to strzałka z zakończeniem w postaci rombu „pustego w środku” a relacja kompozycji to zaczerniony romb. Tu aby nie umieszczać 2 rysunków, narysuję relację agregacji dla CzujnikCisnienia a kompozycji dla CzujnikTemperatury. W moim prostym projekcie zdecyduję się jednak tu na relacje agregacji.

W przypadku implementowania agregacji w KontrolerzePomiarow wystąpi jakaś metoda typu dodajCzujnik*(...). Zanotuję ją od razu. Przyjmie argument czujnika danego typu w postaci referencji (już o niej wspominałem) i zwróci wartość true/false czy dodanie się udało (specjaliści jeśli ktoś programuje obiektowo, proszę wstrzymać się od krytyki tu wprowadziłem celowo pewien błąd koncepcyjny). A jeśli będziemy mieli kompozycję, wykrywanie/tworzenie czujników nastąpi w konstruktorze KontroleraPomiarow. Pamiętaj (jeszcze raz to powtórzę) że w destruktorze KontroleraPomiarow koniecznie zaimplementuj niszczenie obiektu czujników. Przypominam zasadę harcerza.

No dobrze, a co się stanie jeśli do mojej stacji będę chciał dołożyć czujniki wilgotności, szybkości wiatru, nasłonecznienia itd? Jak to zrobić aby KontrolerPomiarow mógł komunikować się z „nowym czujnikiem”? Bingo! Znamy już ten mechanizm. Wystarczy powołanie do życia klasy, być może czysto abstrakcyjnej, która zdefiniuje jak ma wyglądać komunikacja z czujnikiem i dziedziczenie jej do klasy Czujnik*! Nazwiemy tę klasę nadrzędną Czujnik i w niej (możliwie ogólnie) zdefiniujemy metody włączenia/wyłączenia/wykonania pomiaru itp. dla czujników tych które mamy i przyszłych. KontrolerPomiaru będzie wiedział wtedy jak ma komunikować się z czujnikami a nie będzie znał ich wewnętrznej implementacji bo po co mu to potrzebne? Nie będzie potrzebnych także specjalizowanych metod do obsługi danego typu czujnika (spójrz na rysunek pierwotny, tam są 2 metody, 1 dla czujników temperatury i 1 dla ciśnienia). Każda klasa dziedzicząca z Czujnik będzie także jego rodzajem. A jak ujednolicić zwracaną wartość pomiarową? Proste. To przecież Pomiar :-) W nim decydujemy jak wyłuskamy wartość, jak ma wyglądać postać do wyświetlenia czy do zapisu.
Obrazek

Taką analizę aplikacji jeszcze przed napisaniem kodu, możemy przeprowadzić dla wszystkich klas oprogramowania tego urządzenia. Myślę że pokazałem przykład jak to można zrobić na tym prostym fragmencie. Tu więc narysuję diagram da bieżących rozważań w tym tutorialu a dalszą analizę pozostawię jako „pracę domową”.

A po co mi te rysuneczki – możesz zapytać. Cóż, popatrz. To co widzisz poniżej to przedstawiona idea zestawu klas które mogą obsłużyć .... zależności obiektów w postaci drzewa :-) Zacytowałem tu Wikipedię (http://en.wikipedia.org/wiki/Composite_ ... xed%29.svg) aby i Ciebie zachęcić do lektury. Jest to jeden z tzw. wzorców projektowych i nazywa się kompozyt (ang. composite) , a z takich wzorców bardzo szybko buduje się aplikacje (zainteresowanych odsyłam do pojęcia: GoF patterns w wyszukiwarce internetowej).
Obrazek

Jak widzisz Liść (ang. Leaf) jest rodzajem Komponentu (ang. Component). Także Kompozyt (ang. Composite) jest rodzajem Kompomentu (ang. Component). Są rodzajem bo dziedziczą z Komponentu.

Jednocześnie Komponent jest agregowany w Kompozycie (ang. Composite). Co oznacza że Kompozyt może trzymać wiele Komponentów. Wiele komponentów czyli dokładnie Kompozyty i Liście :-)

Liść nie może nic trzymać „wewnątrz”. Zwróć uwagę że taka struktura obsługuje wszelkiego rodzaju drzewa. Począwszy od systemu pliku (katalog/podkatalog to Composite, a plik to Leaf) poprzez ... system menu :-) (no tak, już pewnie podejrzewasz że może wystąpić w projekcie naszej stacji „w okolicach” menu i systemu plików) a zakończywszy choćby na topologii sieciowej gdzie Composite może być czujnikiem komunikującym się z wieloma Liśćmi/czujnikami (taki hub? :-) ).

No to teraz będziesz już wiedział że klasyczne 23/24 (zależy jak liczyć) wzorce GoF (ang. Gang of Four) mogą rozwiązywać wiele problemów. Ich znajomość to taka... klasyka. Coś jak prawo Ohma dla elektronika :-)

Teraz wyobraź sobie spotkanie grupy 3-5 programistów/programistek którzy będą pisali taką aplikację. Mają 15 minut na omówienie jaki kod piszą danego dnia. Rysunek i krótka dyskusja pozwala im pójść do pisania kodu tuż po spotkaniu. Każdy zajmie się określoną liczbą klas i kod powstanie bardzo szybko przy mniejszej ilości toksycznych sprzężeń.
,,Myślenie nie jest łatwe, ale można się do niego przyzwyczaić" - Alan Alexander Milne: Kubuś Puchatek

kijas1
Posty: 10
Rejestracja: sobota 02 sty 2016, 18:50

Re: Obiektowość na AVR od podstaw 6 z n...

Postautor: kijas1 » wtorek 27 cze 2017, 20:29

Szkoda, że cykl urwał się w momencie , kiedy zaczynało się już robić bardzo ciekawie. Może jeszcze mokrowski znajdzie chęci i czas, żeby go kontynuować, bo brakuję takich poradników jak podchodzić do projektowania aplikacji od podstaw.

Awatar użytkownika
mokrowski
User
User
Posty: 190
Rejestracja: czwartek 08 paź 2015, 20:50
Lokalizacja: Tam gdzie Centymetro

Re: Obiektowość na AVR od podstaw 6 z n...

Postautor: mokrowski » wtorek 04 lip 2017, 20:44

Nie ukrywam że sam chciałbym wrócić do tego cyklu ale na tę chwilę ma to bardzo niski priorytet ze względu na dużą ilość i toczących się projektów :-/
,,Myślenie nie jest łatwe, ale można się do niego przyzwyczaić" - Alan Alexander Milne: Kubuś Puchatek


Wróć do „Programowanie AVR w C++”

Kto jest online

Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 5 gości