Programowanie zorientowane obiektowo: elementarz

Jako inżynier automatyk masz prawdopodobnie pewien zasób wiedzy o programowaniu. We współczesnych aplikacjach wzrasta udział programowania zorientowanego obiektowo. To krótkie wprowadzenie powinno pomóc w odkryciu jego tajemnic.

Pochodzenie programowania zorientowanego obiektowo (ang. Object Oriented Programming) można wywodzić z koncepcji API (ang. Application Programming Interface – interfejs programu użytkowego).

API definiuje zestaw podprogramów i sposób zachowania się aplikacji po ich wywołaniu. Na przykład graficzne API może zawierać takie funkcje, jak DrawLine (a, b) (rysuj linię [a, b]) i DrawCircle (x, y, radius) (rysuj okrąg [x, y, promień]), po wywołaniu której na ekranie powinien pojawić się okrąg. Może to stanowić problem, ponieważ różni dostawcy sprzętu graficznego, Intel, Matrox, nVidia i inni, mogą dostarczać takie same API.

Funkcja DrawCircle może być używana przez dowolną aplikację, nawet jeśli wewnętrzna konstrukcja sprzętu i kod programowy implementujący funkcję DrawCircle są całkowicie różne. Trzeba tylko upewnić się, czy zainstalowany w systemie moduł programowy obsługujący graficzne API odpowiada sprzętowi. Później można zainstalować nowszy i silniejszy sprzęt (z nowym oprogramowaniem), a stara aplikacja graficzna będzie nadal dobrze pracować.

Jak zatem przejść do programowania zorientowanego obiektowo?

Można mieć tylko jedno API – to spora niedogodność. A co robić, jeśli ma się komputer z dwoma wyświetlaczami graficznymi? Nie można użyć API opisanego wcześniej, bo nie dałoby się wybrać wyświetlacza, jakiego chce się używać, i nie można zainstalować dwóch bibliotek API, ponieważ funkcje mają takie same nazwy i nie da się określić, którą z nich należy wywołać. Istnieje wiele różnorodnych technicznych rozwiązań tego zagadnienia, w tym również metoda dodawania „numeru adaptora” do każdej funkcji w API.

Załóżmy jednak, że wymyśliliśmy funkcję systemową o nazwie GetNewGraphicsAPI (), która będzie nam zwracać wskaźnik do dobrze zdefiniowanej listy funkcji, takiej jak API opisane wcześniej. Załóżmy ponadto, że dodaliśmy do API funkcję o nazwie Select-GraphicsAdaptor (…). Możemy napisać fragment kodu, pokazany w ramce Kod GetNew-GraphicsAPI.

Możemy zastosować nasze dwa wskaźniki g1 i g2 do wykonania niezależnych operacji na tych dwóch adaptorach graficznych. Obecnie zamiast myśleć o „wskaźniku do listy funkcji”, co jest powszechną, choć skomplikowaną metodą stosowaną w przypadku programowania w zwykłym C, pomyślmy raczej o „obiekcie”. W istocie obiekty C++ są wewnętrznie zaimplementowane jako wskaźniki listy funkcji. Zamiast wywoływać funkcję systemową GetNewGraphicsAPI, zmienimy nieco składnię i napiszemy w to miejsce:

g1 = new GraphicsAPI ();

W tym przypadku „new” jest nową komendą wykorzystywaną przez C++, która sygnalizuje, że system mapobrać nową kopię modułu/obiektu, tu nazwanego GraphicsAPI.

 

Klasa a instancja

Wśród powszechnie używanych określeń związanych z programowaniem zorientowanym obiektowo do najczęściej występujących i mylących należą: obiekt, klasa i instancja (Object, Class, Instance). Pojęcia klasy i instancji są nieźle zdefiniowane, ale pojęcie obiektu – już gorzej. Aby określić, jak w szczególnym przypadku jest zastosowany obiekt, należy uwzględnić kontekst.

Rozważmy plik. Można na przykład mieć do czynienia z plikiem raportu zmianowego. Jest rzeczą zrozumiałą, że dla każdej zmiany tworzony jest nowy plik, a format danych w tym pliku jest różny dla każdej zmiany. Gdyby ktoś zapytał: „Czy istnieje plik raportu zmiany?”, wygodnie byłoby odpowiedzieć: „To zależy od tego, czy został uruchomiony program, który tworzy plik raportu zmiany”.

Kod API GetNewGraphics

 

  g1 = GetNewGraphicsAPI( );
  g2 = GetNewGraphicsAPI( );
  g1-> SelectGraphicsAdaptor („intel”);
  g2-> SelectGraphicsAdaptor („nVidia”);
  g1-> DrawCircle (50,50,25);
  g2-> DrawCircle (100,100,25);

 

Klasa jest definicją obiektu, który może być utworzony (w taki sam sposób jak raport zmiany). Wróćmy na chwilę do starego stylu programowania API – deklaracja klasy zawiera coś analogicznego do pliku nagłówkowego, który definiował funkcje API. Różnica polega na tym, że termin „klasa” obejmuje również kod – w przypadku API zazwyczaj dostarczany jako biblioteka programowa.

Zauważmy, że kod tworzący plik raportu istnieje niezależnie od tego, czy plik raportu został utworzony. Podobnie istnieje zarówno kod (który implementuje funkcje obiektu takie jak GraphicsAPI), jak i plik nagłówkowy (który definiuje funkcje w tym kodzie), niezależnie od tego, czy utworzono kopię obiektu GraphicsAPI.

Dochodzimy teraz do pojęcia instancji, która jest po prostu realną kopią obiektu. Gdy wydamy polecenie open/write/close (otwórz/zapisz/zamknij), zostanie utworzony realny plik raportu (jako uzupełnienie kodu znajdującego się w konkretnym systemie, przeznaczonego do obsługi plików raportów). Za pomocą komendy „new” (nowy) stworzymy rzeczywisty obiekt (jako dodatek do kodu, który służy do obsługi funkcji stowarzyszonych z tym obiektem).

Dalsze informacje o klasie i instancji zamieszczono w ramce „Pytania dotyczące klasy i instancji”.

  

Zaleta modułowości

 W skrócie, dzięki użyciu obiektów szybciej tworzymy lepszy i tańszy kod – to największa zaleta.

Dobrze zdefiniowane API okazały się znacznym postępem w porównaniu z pracochłonnym kodowaniem funkcji graficznych. Z kolei obiekty mają znaczną przewagę nad API. Umożliwiają przygotowanie bardziej modułowego kodu, a także pozwalają jaśniej i bardziej zwięźle zdefiniować moduły. Oznacza to, że kod może być pisany szybciej, z mniejszą liczbą błędów i łatwiej go przetestować. To zaś oznacza, że łatwiej dodać do systemu nową funkcję, często bez zmieniania kodu systemu. Łatwiej tworzyć interfejsy i elementy dostosowane do systemu, ponieważ są jaśniej zdefiniowane.

Dlaczego? Modelowanie technik zorientowanych obiektowo może być zastosowane w sterowaniu procesem na co najmniej trzech poziomach.

Po pierwsze, może i powinno być stosowane przez dostawców do budowy systemów. Powstaną wtedy systemy, które da się szybciej ukończyć, bardziej niezawodne, łatwiej poddające się modyfikacjom i ulepszaniu.

Po drugie, można je stosować do wbudowanej rozszerzalności, często zapewnianej przez języki skryptowe, takie jak Visual Basic for Applications. Umożliwia to integratorowi systemu lub użytkownikowi końcowemu programowanie systemu na poziomie aplikacji, z zastosowaniem technik zorientowanych obiektowo. W szczególności można w ten sposób tworzyć obiekty systemowe, takie jak kontrolki graficzne (pola edycyjne, suwaki itp.) oraz kolekcje tych obiektów (pola dialogowe, płyty czołowe regulatorów i dostosowane obrazowanie trendu).

Ponadto można później instalować w systemie kontrolki graficzne dostarczane przez innych producentów, takie jak ActiveX, dodając je do aplikacji bez modyfikowania pierwotnego oprogramowania systemu (analogicznie jak w przypadku instalowania nowego elementu sprzętowego w komputerze osobistym bez potrzeby ponownego instalowania Microsoft Windows). Dodatkowe kontrolki mogą być używane w celu ułatwienia adaptowania systemu do specyficznych potrzeb użytkownika. Umożliwiają one integratorom systemu użycie podstawowego systemu dostawcy do szerszych zastosowań oraz stworzenie lepszego dopasowania (tzn. bardziej efektywnego środowiska) dla operatorów i inżynierów procesu.

Po trzecie, techniki zorientowane obiektowo mogą być włączane do narzędzi „modelu procesu” zapewnianych przez dostawcę. Im bardziej architektura bazy danych sterowania procesu i wizualizacji odpowiada architekturze rzeczywistego systemu, tym łatwiej stworzyć, utrzymać i użytkować system.

Nowoczesne systemy sterowania i nadzoru operatorskiego (HMI) umożliwiają użytkownikowi końcowemu wykorzystanie techniki „przeciągaj i upuść” do agregowania różnorodnych prostych sygnałów We/Wy (analogowych i cyfrowych) w bardziej złożone obiekty, które reprezentują rzeczywiste obiekty procesu, np. zawory i pompy. Obiekty te mogą być z kolei łączone w celu utworzenia obiektów wyższego poziomu, takich jak kotły, mieszalniki lub suszarki. Obiekty wyższego poziomu po połączeniu tworzą linię, budynek, instalację lokalną, a nawet całe przedsiębiorstwo. 

 

     Pytania dotyczące klasy i instancji

 

   Często trudno wyjaśnić różnicę pomiędzy klasą i instancją, szczególnie w graficznych systemach wyższego poziomu wykorzystujących metodę „przeciągnij i upuść” (drag and drop). Należy rozważyć następujące pytania:

  • „Jeżeli grupuję graficznie kilka zaworów i czujników, aby zdefiniować kocioł, to czy zdefiniowałem uniwersalny kocioł do wielokrotnego użytku, czy też po prostu zdefiniowałem/zgrupowałem punkty We/Wy dla jednego szczególnego kotła ze specjalnym zestawem zaworów i czujników?
  • Jeżeli przeciągnę i upuszczę ten obiekt w inne miejsce, to czy jest to nowy kocioł, czy tylko kopia starego kotła z łączami do tych samych sygnałów We/Wy?
  • Jeżeli muszę interweniować i zmienić połączenia oraz nazwy znaczników, aby podłączyć logicznie ten element do mojego fizycznego kotła nr 2, to skąd będę wiedział, które elementy zmienić?
  • Gdzie mogę sprawdzić, czy czegoś nie zapomniałem?
  • Co się stanie, jeżeli zmienię definicję „kotła”?
  • Czy wszystkie elementy kodu, których już używałem, zmienią się w taki sam sposób, czy też tylko kotły, które w przyszłości „przeciągnę i upuszczę” będą zawierać dokonane przeze mnie zmiany?
  • Co się stanie, jeżeli jeden z moich kotłów nieznacznie różni się fizycznie od innych, co często się zdarza?
  • Czy mogę podkręcić instancję „kotła” skojarzoną z tym fizycznym urządzeniem bez wpływania na inne?

 

Niedogodność: trudno poprawiać źle napisany kod

Kod zorientowany obiektowo, podobnie jak kod w każdej innej postaci, może być niewłaściwie napisany. Zły kod zorientowany obiektowo jest, ogólnie mówiąc, znacznie gorszy niż zły kod konwencjonalny – trudniej go zrozumieć i trudniej poprawić. Aby dobrze napisać kod, trzeba mieć rozległą wiedzę, dużą praktykę i prawdopodobnie więcej naturalnych zdolności niż w przypadku kodu konwencjonalnego. Oznacza to, że słaby kod obiektowy pojawia się na świecie częściej niż słaby kod konwencjonalny.

 

     Programowanie zorientowane obiektowo w pigułce

  • Techniki zorientowane obiektowo są bardziej skomplikowane od  konwencjonalnych technik programowania i trudniej je opanować.
  • Dobrze wykonane techniki zorientowane obiektowo ułatwiają tworzenie oprogramowania oraz zapewniają jego większą niezawodność i elastyczność.
  • Techniki zorientowane obiektowo można stosować na wszystkich poziomach sterowania procesem, począwszy od pierwotnego kodu dostawcy, aż do języka
    skryptowego udostępnianego użytkownikowi/integratorowi w celu organizacji i utrzymania aktualnej bazy danych procesu i obrazowania.
  • Same techniki zorientowane obiektowo nie gwarantują lepszego rezultatu. Należy starannie wybrać dostawcę, przetestować dokładnie system i upewnić się, czy będzie dobrze pracować w konkretnym zastosowaniu.

Zorientowane obiektowo systemy sterowania procesem zwykle wykorzystują technikę „przeciągaj i upuść”, tworząc w ten sposób graficzne odwzorowanie definicji całego systemu. Takie systemy wyglądają naprawdę wspaniale podczas demonstracji na wystawach. W praktyce jednak graficzne odwzorowania bazodanowe może być trudno nawigować i utrzymać, zwłaszcza w przypadku dużych systemów. Wówczas może być pomocna dobrze przemyślana, hierarchiczna organizacja, nadal jednak trudno je będzie zaprojektować i utrzymać.

Na przykład w przypadku połączeń pomiędzy różnymi warstwami i obiektami na różnych stronach mogą pojawiać się problemy związane z odwzorowaniem i graficzną pracą z nimi. Dobre systemy będą przechowywać dane wewnętrzne w którymś ze standardowych formatów, być może w postaci relacyjnej bazy danych albo przynajmniej pliku XML. Będą również umożliwiać pracę z danymi albo metodą graficzną, albo poprzez zastosowanie tabel i zapytań.

Al Chisholm był współzałożycielem Intellution oraz pierwotnym architektem specyfikacji OPC Data Access i OPC Alarm. Obecnie jest zaangażowany w uruchamianie firmy MediaCaster Inc., nastawionej na zdalne nadzorowanie rynku przemysłu wodnego i gazowniczego.