Difference between revisions of "Rzut oka na BOOPSI"

From MorphOS Library

(Programowanie zorientowane obiektowo: Translation finished.)
(Switched a link to Polish version.)
 
(9 intermediate revisions by the same user not shown)
Line 12: Line 12:
  
 
Moduł BOOPSI znajduje się w bibliotece ''intuition.library'', niektóre ważne funkcje znajdziemy w statycznej bibliotece ''libabox''. Pierwotnym zadaniem BOOPSI było "zapakowanie w obiekty" elementów GUI tworzonych przez Intuition. Takie podejście okazało się jednak za mało elastyczne, dlatego MUI używa BOOPSI wyłącznie jako podstawę programowania obiektowego, tworząc własne elementy GUI i wykorzystując Intuition jedynie do podstawowych rzeczy (np. tworzenie okien). BOOPSI tworzy cztery podstawowe pojęcia programowania obiektowego: '''klasy''', '''obiekty''', '''metody''' i '''atrybuty'''. Obsługuje również dziedziczenie klas. Dzięki swojej prostocie BOOPSI jest łatwe w użyciu, szczególnie w porównaniu do bardziej zaawansowanych platform obiektówych, takich jak np. język C++.
 
Moduł BOOPSI znajduje się w bibliotece ''intuition.library'', niektóre ważne funkcje znajdziemy w statycznej bibliotece ''libabox''. Pierwotnym zadaniem BOOPSI było "zapakowanie w obiekty" elementów GUI tworzonych przez Intuition. Takie podejście okazało się jednak za mało elastyczne, dlatego MUI używa BOOPSI wyłącznie jako podstawę programowania obiektowego, tworząc własne elementy GUI i wykorzystując Intuition jedynie do podstawowych rzeczy (np. tworzenie okien). BOOPSI tworzy cztery podstawowe pojęcia programowania obiektowego: '''klasy''', '''obiekty''', '''metody''' i '''atrybuty'''. Obsługuje również dziedziczenie klas. Dzięki swojej prostocie BOOPSI jest łatwe w użyciu, szczególnie w porównaniu do bardziej zaawansowanych platform obiektówych, takich jak np. język C++.
 +
  
 
==Klasy==
 
==Klasy==
  
A class is the basic term of object oriented programming. It is the complete description of its objects, their attributes and methods. In the BOOPSI framework, a class consists of:
+
Klasa jest podstawowym pojęciem programowania obiektowego. Jest ona kompletnym opisem swoich obiektów, definiuje ich metody i atrybuty. W BOOPSI klasa składa się z:
*An ''IClass'' structure. A pointer to this structure is used as a reference to the class. The ''IClass'' structure is defined in the ''<intuition/classes.h>'' system header file. There is also the ''Class'' type, which is the same as ''struct IClass''.
+
* Struktury ''IClass''. Do klasy odwołujemy się zawsze poprzez wskaźnik na tę strukturę. Struktura ''IClass'' jest zdefiniowana w systemowym pliku nagłówkowym ''<intuition/classes.h>''. Znajduje się tam również definicja typu ''Class'', który jest tożsamy z ''struct IClass''.
* A class dispatcher function. When an application calls a method on an object, the object's class dispatcher is called. The dispatcher checks the method's identifier and jumps to this method code. The dispatcher is usually implemented as a big ''switch'' statement. For simple classes, which implement only a few short methods, code of these methods is often placed inside ''case'' statements. Bigger classes have methods' code separated into functions placed outside of the dispatcher. As every method goes through a dispatcher, all BOOPSI methods are virtual in the C++ meaning. For this reason, calling a method in BOOPSI is usually slower than in C++.
+
* Funkcji, tzw. ''dispatchera''. Gdy program wykonuje metodę na obiekcie, wywoływany jest ''dispatcher'' klasy obiektu. Na podstawie identyfikatora metody ''dispatcher'' wykonuje skok do metody. Jest on zwykle implementowany w postaci dużego bloku ''switch''. W przypadku małych klas zawierających jedynie kilka krótkich metod, kod metod jest często umieszczany bezpośrednio w poszczególnych wyrażeniach ''case''. Większe klasy mają metody w oddzielnych funkcjach umieszczonych poza ''dispatcherem''. Ponieważ każda metoda przechodzi przez ''dispatcher'', wszystkie metody BOOPSI są wirtualne w sensie C++. Z tego powodu wywołanie metody w BOOPSI jest zwykle wolniejsze niż w C++.  
  
A class defines a set of methods available for its objects (instances) by the set of ''case'' statements in the dispatcher. Object's attributes are set using the ''OM_SET()'' method and are gotten using ''OM_GET()''. The attributes may also be passed to the object constructor directly. The set of attributes for a class and applicability of the attributes are then defined by the source code of ''OM_NEW()'' (the constructor), ''OM_SET()'' and ''OM_GET()'' methods. There is no formal declaration of class. There is also no division between public and private methods and attributes. Some kind of formal declaration and levels of access may be imposed by putting every class in a separate source code file. An accompanying header file would contain definitions of method identifiers, attribute identifiers and method parameters structures, but only those considered "public". Private method identifiers should be defined inside the source code of the class, so they are not visible outside of the class source code.
+
Zestaw metod klasy (metod, jakie można wykonywać na jej obiektach) jest określony przez zestaw wyrażeń ''case'' dispatchera. Atrybuty obiektu są ustawiane przy użyciu metody ''OM_SET()'', a odczytywane metodą ''OM_GET()''. Atrybuty mogą być również przekazane bezpośrednio do [[#Konstrukcja obiektu|konstruktora]] obiektu. Zestaw atrybutów klasy, oraz możliwość ich odczytu, zapisu i użycia przy inicjacji obiektu są zatem zdefiniowane przez kod metody ''OM_GET()'' (odczyt), ''OM_SET()'' (zapis) i ''OM_NEW()'' (konstruktor, a więc inicjacja). W BOOPSI nie występuje formalna deklaracja klasy, nie ma też podziału między metody i atrybuty publiczne i prywatne. Pewną imitację deklaracji i poziomy dostępu można wprowadzić umieszczając każdą klasę w odrębnym pliku źródłowym. Towarzyszący mu plik nagłówkowy zawiera wtedy definicje identyfikatorów metod i atrybutów i struktur parametrów metod, ale tylko te uważane za "publiczne". Identyfikatory metod prywatnych umieszcza się wewnątrz pliku z kodem klasy, nie są więc formalnie dostępne na zewnątrz.
  
A BOOPSI class can be shared between applications. All the MUI built-in classes are shared ones. The BOOPSI maintains a system-wide list of public classes (the list can be browsed with the ''Scout'' monitoring tool). Shared classes are identified by names. A part of the MUI standard classes is contained inside the main MUI library &ndash; ''muimaster.library''. The library adds these classes to the system list when opened for the first time. The rest of the MUI standard classes are stored on the system disk partition in the ''MOSSYS:Classes/MUI/'' directory. Additional third party classes may be placed in the ''SYS:Classes/MUI/'' directory.
+
Klasa BOOPSI może być współdzielona między programami. Wszystkie klasy wbudowane w MUI są klasami współdzielonymi. BOOPSI tworzy systemową listę klas publicznych (listę tę można przeglądać np. za pomocą monitora systemu ''Scout''). Klasy publiczne są identyfikowane przez nazwę. Część klas standardowych MUI znajduje się w jego głównej bibliotece &ndash; ''muimaster.library''. Biblioteka dodaje te klasy do listy klas publicznych, kiedy zostaje załadowana do pamięci przy pierwszym otwarciu. Pozostałe standardowe klasy MUI są umieszczone na partycji systemowej dysku w katalogu ''MOSSYS:Classes/MUI/''. Dodatkowe klasy, pisane przez niezależnych programistów, powinny być instalowane w katalogu ''SYS:Classes/MUI/''.
  
Shared classes use the MorphOS shared library framework, in other words a shared BOOPSI class is just a kind of shared library. The class adds itself to the public list of classes, when it is opened from disk. As such, a BOOPSI shared class should be opened with ''OpenLibrary()'' before use ([[MorphOS_API_and_Its_Organization#Manual_Library_Opening_and_Closing|see details]]), especially as BOOPSI classes are usually not included into the list of libraries opened automatically. '''This is not the case for MUI classes''' however. MUI shared classes can be used without opening them. It is explained below, in the [[Short_BOOPSI_Overview#MUI_Extensions_to_BOOPSI|MUI Extensions to BOOPSI]] section.
+
Klasy publiczne są zaimplementowane jako biblioteki współdzielone MorphOS-a. Taka biblioteka zawiera kod klasy i dodaje klasę do listy systemowej podczas pierwszego otwarcia i załadowania z dysku. Ponieważ klasy publiczne są bibliotekami, powinny być przed użyciem otwarte funkcją ''OpenLibrary()'' ([[MorphOS_API_and_Its_Organization#Manual_Library_Opening_and_Closing|więcej na temat]]), tym bardziej, że klasy BOOPSI zazwyczaj nie znajdują się na liście bibliotek otwieranych automatycznie. '''Inaczej jest w przypadku klas MUI''', które mogą być używane, bez ich jawnego otwierania w programie. Zostało to wyjaśnione w rozdziale [[Rzut oka na BOOPSI#Jak MUI rozszerza BOOPSI|"Jak MUI rozszerza BOOPSI"]] poniżej.
  
  
Line 29: Line 30:
  
 
====Metody====
 
====Metody====
Methods are just actions, which can be performed on an object. A set of available methods is defined by the object's class. Technically speaking, a method is a function called with an object as its parameter in order to change the object's state. In BOOPSI, methods are called using the ''DoMethod()'' call from ''libabox'':
+
Metody to akcje, jakie możemy wykonać na obiekcie danej klasy. Zestaw dostępnych metod jest definiowany przez klasę. Bardziej technicznie, metoda jest funkcją, wywoływaną z wskaźnikiem na obiekt jako parametr, w celu zmany stanu obiektu. W BOOPSI metody są wywoływane przy użyciu funkcji ''DoMethod()'' z biblioteki statycznej ''libabox'':
  
  result = DoMethod(object, method_id, ... /* method parameters */);
+
  wynik = DoMethod(obiekt, id_metody, ... /* parametry metody */);
  result = DoMethodA(object, method_struct);
+
  wynik = DoMethodA(obiekt, struktura_metody);
  
The first, more popular form of the call just builds the method structure on the fly, from arguments passed to it. Any method structure always has the method identifier as the first field. The ''DoMethodA()'' call gets a pointer to the method structure, the structure is built by the application. The second form is rarely used. The number and meaning of parameters, as well as the meaning of the result are method specific. Comparison of executing a method with both forms of the call is given below:
+
Pierwsza, bardziej popularna forma wywołania tworzy strukturę metody "w locie" na stosie procesora z podanych argumentów. Każda struktura metody (zwana często "wiadomością", ang. ''message'') zawiera identyfikator metody jako pierwsze pole. Forma ''DoMethodA()'' otrzymuje gotową strukturę metody poprzez wskaźnik, struktura musi być stworzona przez program. Ta druga forma jest raczej rzadko używana. Ilość i znaczenie parametrów oraz znaczenie wyniku są zmienne i zależą od konkretnej metody. Poniższy kod ilustruje różnice w obu formach wywołania metody:
  
  struct MUIP_SomeMethod
+
  struct MUIP_JakasMetoda
 
  {
 
  {
 
   ULONG MethodID;
 
   ULONG MethodID;
   LONG ParameterA;
+
   LONG ParametrA;
   LONG ParameterB;
+
   LONG ParametrB;
 
  };
 
  };
 
   
 
   
  DoMethod(object, MUIM_SomeMethod, 3, 7);
+
  DoMethod(obiekt, MUIM_JakasMetoda, 3, 7);
 
   
 
   
  struct MUIP_SomeMethod mparams = { MUIM_SomeMethod, 3, 7 };
+
  struct MUIP_JakasMetoda mparams = { MUIM_JakasMetoda, 3, 7 };
  DoMethodA(object, &mparams);
+
  DoMethodA(obiekt, &mparams);
  
The ''DoMethod()'' form is more convenient, so it is commonly used. MUI uses specific prefixes for all its structures and constants:
+
Oczywiście forma ''DoMethod()'' jest znacznie wygodniejsza i dlatego jest w powszechnym użyciu. MUI używa ustalonych przedrostków w nazwach swoich struktur i stałych:
* '''''MUIM_''''' for method identifiers.
+
* '''''MUIM_''''' dla identyfikatorów metod.
* '''''MUIP_''''' for method parameter structures.
+
* '''''MUIP_''''' dla struktur (wiadomości) metod.
* '''''MUIA_''''' for attribute identifiers.
+
* '''''MUIA_''''' dla identyfikatorów atrybutów.
* '''''MUIV_''''' for special, predefined attribute values.
+
* '''''MUIV_''''' dla specjalnych, predefiniowanych wartości atrybutów.
  
  
<small>The C types used in the method structure above may need some explanation. LONG is a 32-bit signed integer, ULONG is an unsigned one. Because the structure is usually built on the processor stack, all parameters are extended and aligned to 32 bits. Then every parameter in the structure must be defined either as a 32-bit integer or a pointer. Any parameter larger than 32 bits must be passed via pointer (for example double precision floats or strings).</small>
+
<small>Typy języka C użyte w powyższej strukturze metody wymagają wyjaśnienia. ''LONG'' to 32-bitowa liczba całkowita ze znakiem, ''ULONG'' to taka sama liczba bez znaku. Ponieważ struktura metody jest zwykle budowana na stosie procesora, wszystkie parametry muszą mieć rozmiar i początek wyrównany do 32 bitów. Dlatego każdy parametr musi być zdefiniowany albo jako liczba 32-bitowa, albo jako wskaźnik. Każdy parametr większy niż 32 bity musi być przekazany poprzez wskaźnik (na przykład liczby zmiennoprzecinkowe podwójnej precyzji albo łańcuchy tekstowe).</small>
  
 
====Ustawianie wartości atrybutu====
 
====Ustawianie wartości atrybutu====
  
An object's attributes represent its properties. They are written and read using special methods, ''OM_SET()'' and ''OM_GET()'' respectively. This differs from most object oriented programming languages, where attributes (being implemented as an object's fields) are accessed directly. Manipulating attributes in BOOPSI is slower then, as it implies performing a method.
+
Atrybuty obiektu reprezentują jego właściwości. Atrybuty zapisuje się i odczytuje używając dwóch specjalnych metod: ''OM_SET()'' i ''OM_GET()''. To dość nietypowy sposób w porównaniu z większością obiektowo zorientowanych języków programowania, gdzie atrybuty (implementowane jako pola obiektu) są odczytywane i zapisywane bezpośrednio. Manipulowanie atrybutami w BOOPSI jest więc nieco wolniejsze, bo wymaga wykonania metody.
  
The ''OM_SET()'' method does not take a single attribute and its value, but a [[Taglists|taglist]] of them, so one can set multiple attributes at once. The setting of two attributes to an object may be done as follows:
+
Metoda ''OM_SET()'' jest w stanie ustawić wiele atrybutów jednocześnie, bowiem jej argumentami nie są atrybut i jego nowa wartość, ale tablica identyfikatorów atrybutów i ich wartości, zwana potocznie [[Taglists|taglistą]]. Poniższy przykład pokazuje ustawienie dwóch atrybutów:
  
  struct TagItem attributes[] = {
+
  struct TagItem atrybuty[] = {
   { MUIA_SomeAttr1, 756 },
+
   { MUIA_JakisAtr1, 756 },
   { MUIA_SomeAttr2, 926 },
+
   { MUIA_JakisAtr2, 926 },
 
   { TAG_END, 0 }
 
   { TAG_END, 0 }
 
  };
 
  };
 
   
 
   
  DoMethod(object, OM_SET, (ULONG)attributes);
+
  DoMethod(obiekt, OM_SET, (ULONG)atrybuty);
  
However, this is cumbersome and the code is not easily readable. The ''intuition.library'' makes it easier by providing the ''SetAttrsA()'' function, which is a wrapper for the ''OM_SET()'' method. Using this function and the array defined above, one can write:
+
Nie jest to zbyt wygodne, a i czytelność kodu pozostawia nieco do życzenia. Życie ułatwia nam ''intuition.library'' za pomocą funkcji ''SetAttrsA()'', która jest swego rodzaju nakładką na metodę ''OM_SET()''. Używając tej funkcji i tablicy zdefiniowanej powyżej, możemy napisać:
  
  SetAttrsA(object, attributes);
+
  SetAttrsA(obiekt, atrybuty);
  
It still requires definition of a temporary [[Taglists|taglist]], but the function also has a variadic (meaning it can take a variable number of arguments) form ''SetAttrs()'', which allows for building the taglist on-the-fly:
+
Mimo wszystko wciąż trzeba zdefiniować tymczasową [[Taglists|taglistę]], ale funkcja ma na szczęście formę ze zmienną liczbą argumentów, pozwalającą na zbudowanie taglisty "w locie" na stosie, forma ta nazywa się ''SetAttrs()''.
  
  SetAttrs(object,
+
  SetAttrs(obiekt,
   MUIA_SomeAttr1, 756,
+
   MUIA_JakisAtr1, 756,
   MUIA_SomeAttr2, 926,
+
   MUIA_JakisAtr2, 926,
 
  TAG_END);
 
  TAG_END);
  
This is not all however. Programmers are lazy and decided that in the common case of setting a single attribute, ''SetAttrs()'' is still too much typing. A common practice found in sources using MUI was to define an ''xset()'' or ''set()'' macro, which is now defined in the system headers, in the ''<libraries/mui.h>'' file.
+
To jednak nie wszystko. Programiści są leniwi, więc doszli do wniosku, że w często spotykanym przypadku ustawiania pojedynczego atrybutu, ''SetAttrs()'' to wciąż za dużo pisania. Powszechną praktyką w programach używających MUI było definiowanie makr nazywanych ''xset()'' lub ''set()'', obecnie makro ''set()'' jest zdefiniowane w systemowym pliku nagłówkowym ''<libraries/mui.h>''.
  
 
  #define set(object, attribute, value) SetAttrs(object, attribute, value, TAG_END)
 
  #define set(object, attribute, value) SetAttrs(object, attribute, value, TAG_END)
  
Then, setting a single attribute can be coded as follows:
+
Używając tego makra pojedynczy atrybut ustawia się następująco:
  
  set(object, MUIA_SomeAttr1, 756);
+
  set(object, MUIA_JakisAtr1, 756);
  
The ''OM_SET()'' method returns the number of attributes applied to the object. If some attributes are not known to the object's class (and superclasses), they are not counted. This return value is usually ignored, it may be used for testing an attribute applicability.
+
Metoda ''OM_SET()'' zwraca jako wynik ilość ustawionych w obiekcie atrybutów. Jeżeli jakieś atrybuty nie są znane w klasie obiektu (i klasach nadrzędnych), nie są zliczane. Wynik tej metody jest zwykle ignorowany, może być użyty do testowania czy dany atrybut jest obsługiwany i czy można go ustawiać.
  
MUI provides a few additional methods for setting attributes, namely ''MUIM_Set()'', ''MUIM_NoNotifySet()'' and ''MUIM_MultiSet()''. They are mainly used in [[Event_Driven_Programming,_Notifications#Notifications_in_MUI|notifications]].
+
W MUI znajdziemy kilka dodatkowych metod ustawiających atrybuty, są to ''MUIM_Set()'', ''MUIM_NoNotifySet()'' i ''MUIM_MultiSet()''. Są używane przede wszystkim w [[Programowanie sterowane zdarzeniami, notyfikacje#Notyfikacje w MUI|notyfikacjach]].
  
 
====Odczytywanie wartości atrybutu====
 
====Odczytywanie wartości atrybutu====
  
The ''OM_GET()'' method gets a single attribute from an object. There is no multiple attributes getting method. Its first, obvious parameter is the attribute identifier. The attribute value is not returned as the result of the method however. Instead the second parameter is a pointer to a memory area, where the value is to be stored. This allows for passing attributes larger than 32 bits, they are just copied to the pointed memory area. This only works for fixed size attributes. Text strings cannot be passed this way, so they are passed as pointers (a pointer to the string is stored at a place in memory pointed to by the second parameter of ''OM_GET()''). The three examples below demonstrate all three cases:
+
Metoda ''OM_GET()'' odczytuje wartość pojedynczego atrybutu z obiektu. Nie ma metody do odczytania wielu atrybutów jednocześnie. Pierwszym, oczywistym parametrem ''OM_GET()'' jest identyfikator atrybutu. Jego wartość nie jest jednak zwracana jako wynik metody. Drugi argument metody jest wskaźnikiem na miejsce w pamięci, gdzie ma być zapisana wartość atrybutu. Pozwala to na przekazywanie wartości nie mieszczących się w 32 bitach, są one po prostu kopiowane do wskazanego miejsca w pamięci. Oczywiście sposób ten sprawdza się wyłącznie dla wartości o stałych rozmiarach. Łańcuchy tekstowe nie mogą być przekazane w ten sposób, są więc przekazywane poprzez wskaźnik (adres łańcucha tekstowego jest zapisywany pod adresem przekazanym jako drugi parametr ''OM_GET()''). Poniższe przykłady demonstrują te trzy przypadki:
  
  LONG value1;
+
  LONG wartosc1;
  QUAD value2;   /* 64-bit signed integer */
+
  QUAD wartosc2;     /* liczba całkowita 64-bitowa ze znakiem */
  STRPTR *value3;
+
  char **wartosc3;
 
   
 
   
  DoMethod(object, OM_GET, MUIA_Attribute1, (ULONG)&value1);  /* integer attr */
+
  DoMethod(obiekt, OM_GET, MUIA_Atrybut1, (ULONG)&wartosc1);  /* atrybut całkowity */
  DoMethod(object, OM_GET, MUIA_Attribute2, (ULONG)&value2);  /* fixed size big attr */
+
  DoMethod(obiekt, OM_GET, MUIA_Atrybut2, (ULONG)&wartosc2);  /* duży atrybut o stałym rozmiarze */
  DoMethod(object, OM_GET, MUIA_Attribute3, (ULONG)&value3);  /* string attr */
+
  DoMethod(obiekt, OM_GET, MUIA_Atrybut3, (ULONG)&wartosc3);  /* łańcuch tekstowy */
  
In cases when an attribute is returned by pointer, the data pointed to should be treated as read-only unless documented otherwise.
+
W przypadku, gdy wartość atrybutu jest przekazywana przez wskaźnik, wskazywane dane powinny być traktowane jako tylko do odczytu, o ile dokumentacja wyraźnie nie stwierdza inaczej.
  
Similarly as for ''OM_SET()'', there is a wrapper function for ''OM_GET()'' in the ''intuition.library'', named ''GetAttr()''. This function unexpectedly changes the order of arguments: attribute identifier is the first, object pointer is the second. The three examples above may be written with ''GetAttr()'' as follows:
+
Podobnie jak dla metody ''OM_SET()'', istnieje w ''intuition.library'' nakładka na metodę ''OM_GET()'' nazwana ''GetAttr()''. Nieoczekiwanie dla programisty, funkcja ta zmienia kolejność argumentów: identyfikator atrybutu jest pierwszym argumentem, potem następuje wskaźnik na obiekt, a na końcu wskaźnik na miejsce w pamięci do zapisania wartości atrybutu. Korzystając z ''GetAttrs()'' powyższe trzy przykłady można zapisać w następujący sposób:
  
  GetAttr(MUIA_Attribute1, object, &value1);
+
  GetAttr(MUIA_Atrybut1, obiekt, &wartosc1);
  GetAttr(MUIA_Attribute2, object, (ULONG*)&value2);
+
  GetAttr(MUIA_Atrybut2, obiekt, (ULONG*)&wartosc2);
  GetAttr(MUIA_Attribute3, object, (ULONG*)&value3);
+
  GetAttr(MUIA_Atrybut3, obiekt, (ULONG*)&wartosc3);
  
The third parameter, a storage pointer is prototyped as pointer to ULONG, so in the first example type casting is not needed.
+
Trzeci parametr funkcji (wskaźnik na miejsce zapisu wartości) jest zdefiniowany jako wskaźnik na ULONG, więc w pierwszym przykładzie rzutowanie typu nie jest potrzebne.
  
The ''<libraries/mui.h>'' system header file defines a macro ''get()'', which reverses the order of the two first arguments of ''GetAttr()'' and adds the typecasting to ''ULONG*''. The order of arguments of ''get()'' is the same as for ''set()'', which helps to avoid mistakes. The third line of the above example may be rewritten with ''get()'' this way:
+
W pliku nagłówkowym ''<libraries/mui.h>'' jest zdefiniowane makro ''get()'', które odwraca porządek argumentów na taki jak w ''OM_GET()'' oraz dodaje wyżej wspomniane rzutowanie typu na (''ULONG''*). Kolejność argumentów ''get()'' jest taka sama jak w ''set()'', co pozwala uniknąć pomyłek. Trzeci przykład z użyciem ''get()'' przyjmuje następującą formę:
  
  get(object, MUIA_Attribute3, &value3);
+
  get(obiekt, MUIA_Atrybut3, &wartosc3);
  
The most often used attributes are integers (32-bit or shorter) and strings. Both of them fit into a 32-bit variable, as strings have to be passed via pointers. Taking this into account, MUI programmers invented a function (sometimes defined as a macro), which just returns the attribute value instead of storing it at a specified address. The function is named ''xget()'' and works as shown below:
+
Najczęściej używane typy atrybutów to liczby całkowite (32-bitowe lub krótsze) i łańcuchy tekstowe. Oba mieszczą się w zmiennej 32-bitowej, jako że teksty są przekazywane poprzez wskaźnik. Biorąc to pod uwagę, programiści używający MUI zaczęli używać makra (czasami definiowanego jako funkcja), które zwraca wartość atrybutu jako wynik, zamiast zapisywać ją w przygotowanym miejscu w pamięci. Funkcja nazywa się ''xget()'', oto przykłady jej użycia:  
  
  value1 = xget(object, MUIA_Attribute1);
+
  wartosc1 = xget(obiekt, MUIA_Atrybut1);
  /* MUIA_Attribute2 can't be retrieved with xget() */
+
  /* MUIA_Atrybut2 nie może być odczytany przez xget() */
  value3 = (STRPTR)xget(object, MUIA_Attribute3);
+
  wartosc3 = (char*)xget(obiekt, MUIA_Atrybut3);
  
The ''xget()'' function may be defined in the following way:
+
Funkcję ''xget()'' można zdefiniować następująco:
  
  inline ULONG xget(Object *obj, ULONG attribute)
+
  inline ULONG xget(Object *obiekt, ULONG atrybut)
 
  {
 
  {
   ULONG value;
+
   ULONG wartosc;
 
   
 
   
   GetAttr(attribute, object, &value);
+
   GetAttr(atrybut, obiekt, &wartosc);
   return value;
+
   return wartosc;
 
  }
 
  }
  
The function is very simple and is compiled to a few processor instructions. That is why it is declared as ''inline'', which causes the compiler to insert the function's code in-place instead of generating a jump. This makes the code faster, albeit a bit bigger. Except for working only with 32-bit attributes, ''xget()'' also has the disadvantage of loosing the ''OM_GET()'' return value. The value is boolean and is ''TRUE'' if the object's class (or any of its superclasses) recognizes the attribute, ''FALSE'' otherwise. This value is usually ignored, but may be useful for scanning objects for supported attributes.
+
Funkcja jest bardzo prosta i kompiluje się do kilku rozkazów procesora. Dlatego jest zadeklarowana jako ''inline'', co powoduje wstawianie jej kodu w każdym miejscu wywołania, zamiast skoku do niej. Czyni to program nieco szybszym za cenę niewielkiego zwiększenia jego rozmiarów. Wadą ''xget()'', oprócz działania wyłącznie z atrybutami o wartościach miesczczących się w 32 bitach, jest ignorowanie wyniku metody ''OM_GET()''. Wynik ten jest wartością logiczną i jest równy ''TRUE'', jeżeli klasa obiektu (albo któraś z jej klas nadrzędnych) rozpozna atrybut, w przeciwnym wypadku ma wartość ''FALSE''. Wynik ''OM_GET()'' jest zwykle ignorowany, ale może się przydać do sprawdzania atrybutów obsługiwanych przez klasę.
  
<small>The ''xget()'' function is not defined in the system headers. It has been described here because of its common use in MUI applications. Its counterparts for bigger sized arguments may be defined if needed.</small>
+
<small>Funkcja ''xget()'' nie jest zdefiniowana w systemowych plikach nagłówkowych. Została tu opisana dlatego, że jest powszechnie używana w programach korzystających z MUI. Można również, w razie potrzeby, zdefiniować jej odpowiedniki dla wartości typów nie mieszczących się w 32 bitach.</small>
  
  
 
==Konstrukcja obiektu==
 
==Konstrukcja obiektu==
  
Having a class, the programmer can create an unlimited number of objects (instances) of this class. Every object has its own instance data area, which is allocated and cleared automatically by the BOOPSI. Of course only object data are allocated for each instance. The code is not duplicated, so it must be reentrant (static variables and code self-modification must not be used).
+
Posiadając zdefiniowaną klasę, programista może stworzyć nieograniczoną ilość obiektów (zwanych też czasami ''instancjami'' klasy). Każdy obiekt posiada własny obszar danych, alokowany i zwalniany automatycznie przez BOOPSI. Oczywiście wyłącznie dane obiektu są przydzielane dla każdej instancji. Kod metod nie jest powielany, więc musi być napisany w sposób pozwalający na jednoczesne wykonywanie go przez wiele procesów (ang. ''reentrant''), oznacza to m. in. nieużywanie zmiennych statycznych i samomodyfikującego się kodu.
 +
 
 +
Obiekty są tworzone i niszczone za pomocą dwóch specjalnych metod, odpowiednio konstruktora (''OM_NEW()'') i destruktora (''OM_DISPOSE()'').
  
Objects are created and disposed with two special methods: the constructor, ''OM_NEW()'' and the destructor, ''OM_DISPOSE()''. Of course the constructor method cannot be called on an object, because it creates a new one. It needs a pointer to the object's class instead, so it cannot be invoked with ''DoMethod()''. The ''intuition.library'' provides ''NewObjectA()'' and ''NewObject()'' functions for calling the constructor. The difference between them is that ''NewObjectA()'' takes a pointer to a [[Taglists|taglist]] specifying initial values for objects. ''NewObject()'' allows the programmer to build this taglist from a variable number of function arguments.
+
Konstruktor nie może być, rzecz jasna, wywołany na obiekcie, ponieważ dopiero stworzy ten obiekt. Potrzebuje więc wskaźnika na klasę obiektu do stworzenia. Z tego wynika, że nie można wywołać konstruktora poprzez ''DoMethod()''. Do tego celu biblioteka ''intuition.library'' ma w swoim obfitym API funkcje ''NewObjectA()'' i ''NewObject()''. Różnica między nimi to sposób podania konstruktorowi początkowych wartości atrybutów. ''NewObjectA()'' przyjmuje wskaźnik na [[Taglists|taglistę]] zawierającą wartości początkowe. ''NewObject()'' pozwala na zbudowanie tej listy tagów na stosie z kolejnych agrumentów.
  
''NewObject[A]()'' has two alternative ways of specifying the created object's class. Private classes are specified by pointers of ''Class'' type. Shared classes are specified by name, which is a null-terminated string. If the pointer is used for class specification, the name should be NULL, if a name is used, the pointer should be ''NULL''. Four examples below show creating instances of private and public class with both ''NewObjectA()'' and ''NewObject()'':
+
Funkcja ''NewObject[A]()'' posiada dwa alternatywne argumenty podające klasę obiektu do stworzenia. Klasę prywatną podaje się poprzez wskaźnik na strukturę ''Class''. Klasę publiczną podaje się poprzez wskaźnik nazwę, która jest łańcuchem tekstowym (zakończonym, jak to zwykle bywa, bajtem 0). Jeżeli podajemy wskaźnik na klasę prywatną, wskaźnik na nazwę powinien mieć wartość ''NULL'' i odwrotnie, jeżeli używamy nazwy, wskaźnik na klasę powinien być ''NULL''-em. Poniższe cztery przykłady demonstrują tworzenie obiektów klasy publicznej i prywatnej z użyciem bądź to ''NewObjectA()'', bądź to ''NewObject()'':
  
 
  Object *obj;
 
  Object *obj;
 
  Class *private;
 
  Class *private;
 
   
 
   
  struct TagItem initial = {
+
  struct TagItem initial = {       /* wartości początkowe atrybutów */
 
   { MUIA_Attribute1, 4 },
 
   { MUIA_Attribute1, 4 },
 
   { MUIA_Attribute2, 46 },
 
   { MUIA_Attribute2, 46 },
Line 158: Line 161:
 
  };
 
  };
  
A private class, NewObjectA():
+
Klasa prywatna, ''NewObjectA()'':
  
 
  obj = NewObjectA(private, NULL, initial);
 
  obj = NewObjectA(private, NULL, initial);
  
A private class, NewObject():
+
Klasa prywatna, ''NewObject()'':
  
 
  obj = NewObject(private, NULL,
 
  obj = NewObject(private, NULL,
Line 169: Line 172:
 
  TAG_END);
 
  TAG_END);
  
A public class, NewObjectA():
+
Klasa publiczna, ''NewObjectA()'':
  
 
  obj = NewObjectA(NULL, "some.class", initial);
 
  obj = NewObjectA(NULL, "some.class", initial);
  
A public class, NewObject():
+
Klasa publiczna, ''NewObject()'':
  
 
  obj = NewObject(NULL, "some.class",
 
  obj = NewObject(NULL, "some.class",
Line 180: Line 183:
 
  TAG_END);
 
  TAG_END);
  
''NewObject[A]()'' returns ''NULL'' in case of an object creation failure. Usual reasons are: wrong class pointer/name, lack of free memory, wrong/missing initial values of attributes. The return value of ''NewObject[A]()'' should always be checked in the code.
+
Funkcja ''NewObject[A]()'' zwraca wskaźnik zerowy w przypadku nieudanej konstrukcji obiektu. Typowe przyczyny to: nieprawidłowy wskaźnik na klasę lub nazwę klasy, brak wolnej pamięci, błędne lub brakujące wartości początkowe atrybutów. Wartość zwracana przez konstruktor powinna być zawsze sprawdzana w kodzie.
  
  
 
==Destrukcja obiektu==
 
==Destrukcja obiektu==
  
The ''OM_DISPOSE()'' method is used to destroy an object. Unlike ''OM_NEW()'' the destructor may be invoked with ''DoMethod()'':
+
Do zniszczenia obiektu służy metoda ''OM_DISPOSE()''. W przeciwieństwie do konstruktora, destruktor może być wywołany poprzez ''DoMethod()'':
  
  DoMethod(object, OM_DISPOSE);
+
  DoMethod(obiekt, OM_DISPOSE);
  
The ''intuition.library'' has a wrapper for this however, named ''DisposeObject()'':
+
W bibliotece ''intuition.library'' znajdziemy nakładkę na tę metodę, nazwaną ''DisposeObject()'':
  
  DisposeObject(object);
+
  DisposeObject(obiekt);
  
  
 
==Jak MUI rozszerza BOOPSI==
 
==Jak MUI rozszerza BOOPSI==
  
The Magic User Interface not only builds on BOOPSI but also extends it. Other than providing a broad set of classes, MUI also modifies the BOOPSI mode of operation a bit. Two modifications are discussed in this chapter: extension of the ''IClass'' structure and MUI's own functions for object construction and destruction.
+
Magic User Interface nie tylko bazuje na BOOPSI, ale także go rozszerza. Oprócz postawienia do dyspozycji programisty szerokiej palety klas standardowych, MUI modyfikuje także co nieco sposób działania BOOPSI. W tym rozdziale omówione są dwie modyfikacje: rozszerzenie struktury ''Class'' oraz własne funkcje do konstrukcji i destrukcji obiektów.
  
MUI uses the ''MUI_CustomClass'' structure for its internal class representation. This structure contains the standard ''Class'' structure inside. It is important when creating objects from MUI private classes with ''NewObject()'', that the ''Class'' structure must be extracted from the ''MUI_CustomClass'' structure:
+
MUI używa struktury ''MUI_CustomClass'' do reprezentowania swoich klas. Struktura ta zawiera w sobie standardową strukturę ''Class''. W czasie tworzenia obiektów klas prywatnych MUI za pomocą ''NewObject()'', należy "wydobyć" strukturę ''Class'' z ''MUI_CustomClass'':
  
 
  struct MUI_CustomClass *priv_class;
 
  struct MUI_CustomClass *priv_class;
Line 205: Line 208:
 
  obj = NewObject('''priv_class->mcc_Class''', NULL, /* ... */ TAG_END);
 
  obj = NewObject('''priv_class->mcc_Class''', NULL, /* ... */ TAG_END);
  
MUI's second modification of BOOPSI is using its own functions for object construction and destruction, ''MUI_NewObject[A]()'' and ''MUI_DisposeObject()'' respectively. These two functions are used '''only''' for objects of MUI shared (public) classes. Objects of private classes are created with ''NewObject()'' as shown above. The main advantage of ''MUI_NewObject()'' is automatic opening and closing of disk based shared classes. Here is an example:
+
Druga różnica między BOOPSI a MUI to funkcje używane do wywoływania konstruktora i destruktora. Używając MUI należy używać funkcji ''MUI_NewObject[A]()'' i ''MUI_DisposeObject()'', ale tylko do klas publicznych. Obiekty klas prywatnych są po staremu tworzone przez ''NewObject()'', jak w przykładzie powyżej. Główną zaletą  ''MUI_NewObject()'' jest automatyczne otwieranie i zamykanie klas publicznych. Oto przykład:
  
 
  Object *text;
 
  Object *text;
Line 211: Line 214:
 
  text = MUI_NewObject(MUIC_Text, MUIA_Text_Contents, "foobar", TAG_END);
 
  text = MUI_NewObject(MUIC_Text, MUIA_Text_Contents, "foobar", TAG_END);
  
'''MUIC_Text'' is a macro defined in ''<libraries/mui.h>'' and it expands to "Text.mui" string. All MUI public classes should be referenced by their ''MUIC_'' macros rather than by direct string literals. It helps to detect mistyped class names, as a typo in a macro will be detected during compilation. The MUI checks if a class named ''Text.mui'' has been added to the public list of classes. If not, the class is found on disk, opened and used for creating the requested object. Closing the class when no longer in use is handled automatically too. All MUI objects should be disposed using ''MUI_DisposeObject()'', which takes the object to be disposed as its only argument, the same as ''DisposeObject()''.
+
'''MUIC_Text'' jest makrem zdefiniowanym w pliku nagłówkowym ''<libraries/mui.h>'' i jest rozwijane do łańcucha tekstowego "Text.mui". Do nazw publicznych klas MUI powinno się odwoływać poprzez makra ''MUIC_'', bezpośrednie używanie nazw nie jest zalecane. Makra pozwalają uniknąć błędów w nazwach klas, takie błędy dzięki użyciu makr zostaną wychwycone na etapie kompilacji. Na początek MUI sprawdza, czy klasa ''Text.mui'' nie znajduje się czasem na systemowej liście klas publicznych. Jeżeli nie, klasa jest ładowana z dysku i otwierana. Następnie tworzony jest obiekt. Zamykanie klasy jest również wykonane automatycznie. Wszystkie obiekty MUI powinny być usuwane funkcją ''MUI_DisposeObject()'', której jedynym argumentem jest wskaźnik na obiekt do usunięcia, podobnie jak w przypadku ''DisposeObject()''.
  
 
  MUI_DisposeObject(text);
 
  MUI_DisposeObject(text);

Latest revision as of 11:51, 19 January 2011

Grzegorz Kraszewski



Ten artykuł w innych językach: angielski

Programowanie zorientowane obiektowo

Programowanie zorientowane obiektowo jest sposobem tworzenia programów, jaki powstał w odpowiedzi na dwie tendencje na rozwijającym się rynku oprogramowania. Po pierwsze zarządzanie tradycyjnie napisanym kodem staje się coraz trudniejsze w miarę zwiększania się jego rozmiarów. Po drugie, w latach osiemdziesiątych ubiegłego wieku nastąpił lawinowy rozwój graficznych interfejsów użytkownika, co oznaczało koniec programów wykonujących się w z góry zdefiniowanej sekwencji. Nowoczesne programy są sterowane zdarzeniami. Oznacza to, że tok wykonywania programu jest określony przez zdarzenia zewnętrzne (najczęściej wywoływane przez użytkownika) i nie jest znany w czasie pisania programu. Programowanie obiektowe dzieli program na zbiór obiektów oddziałujących na siebie poprzez dokładnie zdefiniowane zestawy metod i atrybutów. Wprowadzona w ten sposób modułowość ułatwia zapanowanie nad projektem i w naturalny sposób pasuje do koncepcji nowoczesnego interfejsu użytkownika. Elementy GUI (zwane w MUI "gadżetami") są po prostu obiektami wymieniającymi informacje z innymi obiektami reprezentującymi dane użytkownika.

To krótkie wprowadzenie nie jest oczywiście kompletnym wykładem z programowania obiektowego. Z drugiej strony do zaznajomienia się z BOOPSI nie jest wymagana znajomość jakiegokolwiek obiektowo zorientowanego języka programowania. Zazwyczaj to język programowania daje programiście do ręki podstawowe konstrukcje obiektowe. Taki język jest albo obiektowy od podstaw (np. C++, C#, Java) albo ma obiektowość doklejoną w mniej lub bardziej logiczny sposób (Objective C, PHP). Inaczej jest w przypadku BOOPSI i MUI. Tutaj konstrukcje wspierające obiektowość dostajemy od systemu operacyjnego. Dzięki temu możemy używać MUI w dowolnym języku programowania, również tradycyjnym, jak np. stare dobre C, a nawet asembler.

Moduł BOOPSI znajduje się w bibliotece intuition.library, niektóre ważne funkcje znajdziemy w statycznej bibliotece libabox. Pierwotnym zadaniem BOOPSI było "zapakowanie w obiekty" elementów GUI tworzonych przez Intuition. Takie podejście okazało się jednak za mało elastyczne, dlatego MUI używa BOOPSI wyłącznie jako podstawę programowania obiektowego, tworząc własne elementy GUI i wykorzystując Intuition jedynie do podstawowych rzeczy (np. tworzenie okien). BOOPSI tworzy cztery podstawowe pojęcia programowania obiektowego: klasy, obiekty, metody i atrybuty. Obsługuje również dziedziczenie klas. Dzięki swojej prostocie BOOPSI jest łatwe w użyciu, szczególnie w porównaniu do bardziej zaawansowanych platform obiektówych, takich jak np. język C++.


Klasy

Klasa jest podstawowym pojęciem programowania obiektowego. Jest ona kompletnym opisem swoich obiektów, definiuje ich metody i atrybuty. W BOOPSI klasa składa się z:

  • Struktury IClass. Do klasy odwołujemy się zawsze poprzez wskaźnik na tę strukturę. Struktura IClass jest zdefiniowana w systemowym pliku nagłówkowym <intuition/classes.h>. Znajduje się tam również definicja typu Class, który jest tożsamy z struct IClass.
  • Funkcji, tzw. dispatchera. Gdy program wykonuje metodę na obiekcie, wywoływany jest dispatcher klasy obiektu. Na podstawie identyfikatora metody dispatcher wykonuje skok do metody. Jest on zwykle implementowany w postaci dużego bloku switch. W przypadku małych klas zawierających jedynie kilka krótkich metod, kod metod jest często umieszczany bezpośrednio w poszczególnych wyrażeniach case. Większe klasy mają metody w oddzielnych funkcjach umieszczonych poza dispatcherem. Ponieważ każda metoda przechodzi przez dispatcher, wszystkie metody BOOPSI są wirtualne w sensie C++. Z tego powodu wywołanie metody w BOOPSI jest zwykle wolniejsze niż w C++.

Zestaw metod klasy (metod, jakie można wykonywać na jej obiektach) jest określony przez zestaw wyrażeń case dispatchera. Atrybuty obiektu są ustawiane przy użyciu metody OM_SET(), a odczytywane metodą OM_GET(). Atrybuty mogą być również przekazane bezpośrednio do konstruktora obiektu. Zestaw atrybutów klasy, oraz możliwość ich odczytu, zapisu i użycia przy inicjacji obiektu są zatem zdefiniowane przez kod metody OM_GET() (odczyt), OM_SET() (zapis) i OM_NEW() (konstruktor, a więc inicjacja). W BOOPSI nie występuje formalna deklaracja klasy, nie ma też podziału między metody i atrybuty publiczne i prywatne. Pewną imitację deklaracji i poziomy dostępu można wprowadzić umieszczając każdą klasę w odrębnym pliku źródłowym. Towarzyszący mu plik nagłówkowy zawiera wtedy definicje identyfikatorów metod i atrybutów i struktur parametrów metod, ale tylko te uważane za "publiczne". Identyfikatory metod prywatnych umieszcza się wewnątrz pliku z kodem klasy, nie są więc formalnie dostępne na zewnątrz.

Klasa BOOPSI może być współdzielona między programami. Wszystkie klasy wbudowane w MUI są klasami współdzielonymi. BOOPSI tworzy systemową listę klas publicznych (listę tę można przeglądać np. za pomocą monitora systemu Scout). Klasy publiczne są identyfikowane przez nazwę. Część klas standardowych MUI znajduje się w jego głównej bibliotece – muimaster.library. Biblioteka dodaje te klasy do listy klas publicznych, kiedy zostaje załadowana do pamięci przy pierwszym otwarciu. Pozostałe standardowe klasy MUI są umieszczone na partycji systemowej dysku w katalogu MOSSYS:Classes/MUI/. Dodatkowe klasy, pisane przez niezależnych programistów, powinny być instalowane w katalogu SYS:Classes/MUI/.

Klasy publiczne są zaimplementowane jako biblioteki współdzielone MorphOS-a. Taka biblioteka zawiera kod klasy i dodaje klasę do listy systemowej podczas pierwszego otwarcia i załadowania z dysku. Ponieważ klasy publiczne są bibliotekami, powinny być przed użyciem otwarte funkcją OpenLibrary() (więcej na temat), tym bardziej, że klasy BOOPSI zazwyczaj nie znajdują się na liście bibliotek otwieranych automatycznie. Inaczej jest w przypadku klas MUI, które mogą być używane, bez ich jawnego otwierania w programie. Zostało to wyjaśnione w rozdziale "Jak MUI rozszerza BOOPSI" poniżej.


Metody i atrybuty

Metody

Metody to akcje, jakie możemy wykonać na obiekcie danej klasy. Zestaw dostępnych metod jest definiowany przez klasę. Bardziej technicznie, metoda jest funkcją, wywoływaną z wskaźnikiem na obiekt jako parametr, w celu zmany stanu obiektu. W BOOPSI metody są wywoływane przy użyciu funkcji DoMethod() z biblioteki statycznej libabox:

wynik = DoMethod(obiekt, id_metody, ... /* parametry metody */);
wynik = DoMethodA(obiekt, struktura_metody);

Pierwsza, bardziej popularna forma wywołania tworzy strukturę metody "w locie" na stosie procesora z podanych argumentów. Każda struktura metody (zwana często "wiadomością", ang. message) zawiera identyfikator metody jako pierwsze pole. Forma DoMethodA() otrzymuje gotową strukturę metody poprzez wskaźnik, struktura musi być stworzona przez program. Ta druga forma jest raczej rzadko używana. Ilość i znaczenie parametrów oraz znaczenie wyniku są zmienne i zależą od konkretnej metody. Poniższy kod ilustruje różnice w obu formach wywołania metody:

struct MUIP_JakasMetoda
{
  ULONG MethodID;
  LONG ParametrA;
  LONG ParametrB;
};

DoMethod(obiekt, MUIM_JakasMetoda, 3, 7);

struct MUIP_JakasMetoda mparams = { MUIM_JakasMetoda, 3, 7 };
DoMethodA(obiekt, &mparams);

Oczywiście forma DoMethod() jest znacznie wygodniejsza i dlatego jest w powszechnym użyciu. MUI używa ustalonych przedrostków w nazwach swoich struktur i stałych:

  • MUIM_ dla identyfikatorów metod.
  • MUIP_ dla struktur (wiadomości) metod.
  • MUIA_ dla identyfikatorów atrybutów.
  • MUIV_ dla specjalnych, predefiniowanych wartości atrybutów.


Typy języka C użyte w powyższej strukturze metody wymagają wyjaśnienia. LONG to 32-bitowa liczba całkowita ze znakiem, ULONG to taka sama liczba bez znaku. Ponieważ struktura metody jest zwykle budowana na stosie procesora, wszystkie parametry muszą mieć rozmiar i początek wyrównany do 32 bitów. Dlatego każdy parametr musi być zdefiniowany albo jako liczba 32-bitowa, albo jako wskaźnik. Każdy parametr większy niż 32 bity musi być przekazany poprzez wskaźnik (na przykład liczby zmiennoprzecinkowe podwójnej precyzji albo łańcuchy tekstowe).

Ustawianie wartości atrybutu

Atrybuty obiektu reprezentują jego właściwości. Atrybuty zapisuje się i odczytuje używając dwóch specjalnych metod: OM_SET() i OM_GET(). To dość nietypowy sposób w porównaniu z większością obiektowo zorientowanych języków programowania, gdzie atrybuty (implementowane jako pola obiektu) są odczytywane i zapisywane bezpośrednio. Manipulowanie atrybutami w BOOPSI jest więc nieco wolniejsze, bo wymaga wykonania metody.

Metoda OM_SET() jest w stanie ustawić wiele atrybutów jednocześnie, bowiem jej argumentami nie są atrybut i jego nowa wartość, ale tablica identyfikatorów atrybutów i ich wartości, zwana potocznie taglistą. Poniższy przykład pokazuje ustawienie dwóch atrybutów:

struct TagItem atrybuty[] = {
  { MUIA_JakisAtr1, 756 },
  { MUIA_JakisAtr2, 926 },
  { TAG_END, 0 }
};

DoMethod(obiekt, OM_SET, (ULONG)atrybuty);

Nie jest to zbyt wygodne, a i czytelność kodu pozostawia nieco do życzenia. Życie ułatwia nam intuition.library za pomocą funkcji SetAttrsA(), która jest swego rodzaju nakładką na metodę OM_SET(). Używając tej funkcji i tablicy zdefiniowanej powyżej, możemy napisać:

SetAttrsA(obiekt, atrybuty);

Mimo wszystko wciąż trzeba zdefiniować tymczasową taglistę, ale funkcja ma na szczęście formę ze zmienną liczbą argumentów, pozwalającą na zbudowanie taglisty "w locie" na stosie, forma ta nazywa się SetAttrs().

SetAttrs(obiekt,
  MUIA_JakisAtr1, 756,
  MUIA_JakisAtr2, 926,
TAG_END);

To jednak nie wszystko. Programiści są leniwi, więc doszli do wniosku, że w często spotykanym przypadku ustawiania pojedynczego atrybutu, SetAttrs() to wciąż za dużo pisania. Powszechną praktyką w programach używających MUI było definiowanie makr nazywanych xset() lub set(), obecnie makro set() jest zdefiniowane w systemowym pliku nagłówkowym <libraries/mui.h>.

#define set(object, attribute, value) SetAttrs(object, attribute, value, TAG_END)

Używając tego makra pojedynczy atrybut ustawia się następująco:

set(object, MUIA_JakisAtr1, 756);

Metoda OM_SET() zwraca jako wynik ilość ustawionych w obiekcie atrybutów. Jeżeli jakieś atrybuty nie są znane w klasie obiektu (i klasach nadrzędnych), nie są zliczane. Wynik tej metody jest zwykle ignorowany, może być użyty do testowania czy dany atrybut jest obsługiwany i czy można go ustawiać.

W MUI znajdziemy kilka dodatkowych metod ustawiających atrybuty, są to MUIM_Set(), MUIM_NoNotifySet() i MUIM_MultiSet(). Są używane przede wszystkim w notyfikacjach.

Odczytywanie wartości atrybutu

Metoda OM_GET() odczytuje wartość pojedynczego atrybutu z obiektu. Nie ma metody do odczytania wielu atrybutów jednocześnie. Pierwszym, oczywistym parametrem OM_GET() jest identyfikator atrybutu. Jego wartość nie jest jednak zwracana jako wynik metody. Drugi argument metody jest wskaźnikiem na miejsce w pamięci, gdzie ma być zapisana wartość atrybutu. Pozwala to na przekazywanie wartości nie mieszczących się w 32 bitach, są one po prostu kopiowane do wskazanego miejsca w pamięci. Oczywiście sposób ten sprawdza się wyłącznie dla wartości o stałych rozmiarach. Łańcuchy tekstowe nie mogą być przekazane w ten sposób, są więc przekazywane poprzez wskaźnik (adres łańcucha tekstowego jest zapisywany pod adresem przekazanym jako drugi parametr OM_GET()). Poniższe przykłady demonstrują te trzy przypadki:

LONG wartosc1;
QUAD wartosc2;     /* liczba całkowita 64-bitowa ze znakiem */
char **wartosc3;  

DoMethod(obiekt, OM_GET, MUIA_Atrybut1, (ULONG)&wartosc1);  /* atrybut całkowity */
DoMethod(obiekt, OM_GET, MUIA_Atrybut2, (ULONG)&wartosc2);  /* duży atrybut o stałym rozmiarze */
DoMethod(obiekt, OM_GET, MUIA_Atrybut3, (ULONG)&wartosc3);  /* łańcuch tekstowy */

W przypadku, gdy wartość atrybutu jest przekazywana przez wskaźnik, wskazywane dane powinny być traktowane jako tylko do odczytu, o ile dokumentacja wyraźnie nie stwierdza inaczej.

Podobnie jak dla metody OM_SET(), istnieje w intuition.library nakładka na metodę OM_GET() nazwana GetAttr(). Nieoczekiwanie dla programisty, funkcja ta zmienia kolejność argumentów: identyfikator atrybutu jest pierwszym argumentem, potem następuje wskaźnik na obiekt, a na końcu wskaźnik na miejsce w pamięci do zapisania wartości atrybutu. Korzystając z GetAttrs() powyższe trzy przykłady można zapisać w następujący sposób:

GetAttr(MUIA_Atrybut1, obiekt, &wartosc1);
GetAttr(MUIA_Atrybut2, obiekt, (ULONG*)&wartosc2);
GetAttr(MUIA_Atrybut3, obiekt, (ULONG*)&wartosc3);

Trzeci parametr funkcji (wskaźnik na miejsce zapisu wartości) jest zdefiniowany jako wskaźnik na ULONG, więc w pierwszym przykładzie rzutowanie typu nie jest potrzebne.

W pliku nagłówkowym <libraries/mui.h> jest zdefiniowane makro get(), które odwraca porządek argumentów na taki jak w OM_GET() oraz dodaje wyżej wspomniane rzutowanie typu na (ULONG*). Kolejność argumentów get() jest taka sama jak w set(), co pozwala uniknąć pomyłek. Trzeci przykład z użyciem get() przyjmuje następującą formę:

get(obiekt, MUIA_Atrybut3, &wartosc3);

Najczęściej używane typy atrybutów to liczby całkowite (32-bitowe lub krótsze) i łańcuchy tekstowe. Oba mieszczą się w zmiennej 32-bitowej, jako że teksty są przekazywane poprzez wskaźnik. Biorąc to pod uwagę, programiści używający MUI zaczęli używać makra (czasami definiowanego jako funkcja), które zwraca wartość atrybutu jako wynik, zamiast zapisywać ją w przygotowanym miejscu w pamięci. Funkcja nazywa się xget(), oto przykłady jej użycia:

wartosc1 = xget(obiekt, MUIA_Atrybut1);
/* MUIA_Atrybut2 nie może być odczytany przez xget() */
wartosc3 = (char*)xget(obiekt, MUIA_Atrybut3);

Funkcję xget() można zdefiniować następująco:

inline ULONG xget(Object *obiekt, ULONG atrybut)
{
  ULONG wartosc;

  GetAttr(atrybut, obiekt, &wartosc);
  return wartosc;
}

Funkcja jest bardzo prosta i kompiluje się do kilku rozkazów procesora. Dlatego jest zadeklarowana jako inline, co powoduje wstawianie jej kodu w każdym miejscu wywołania, zamiast skoku do niej. Czyni to program nieco szybszym za cenę niewielkiego zwiększenia jego rozmiarów. Wadą xget(), oprócz działania wyłącznie z atrybutami o wartościach miesczczących się w 32 bitach, jest ignorowanie wyniku metody OM_GET(). Wynik ten jest wartością logiczną i jest równy TRUE, jeżeli klasa obiektu (albo któraś z jej klas nadrzędnych) rozpozna atrybut, w przeciwnym wypadku ma wartość FALSE. Wynik OM_GET() jest zwykle ignorowany, ale może się przydać do sprawdzania atrybutów obsługiwanych przez klasę.

Funkcja xget() nie jest zdefiniowana w systemowych plikach nagłówkowych. Została tu opisana dlatego, że jest powszechnie używana w programach korzystających z MUI. Można również, w razie potrzeby, zdefiniować jej odpowiedniki dla wartości typów nie mieszczących się w 32 bitach.


Konstrukcja obiektu

Posiadając zdefiniowaną klasę, programista może stworzyć nieograniczoną ilość obiektów (zwanych też czasami instancjami klasy). Każdy obiekt posiada własny obszar danych, alokowany i zwalniany automatycznie przez BOOPSI. Oczywiście wyłącznie dane obiektu są przydzielane dla każdej instancji. Kod metod nie jest powielany, więc musi być napisany w sposób pozwalający na jednoczesne wykonywanie go przez wiele procesów (ang. reentrant), oznacza to m. in. nieużywanie zmiennych statycznych i samomodyfikującego się kodu.

Obiekty są tworzone i niszczone za pomocą dwóch specjalnych metod, odpowiednio konstruktora (OM_NEW()) i destruktora (OM_DISPOSE()).

Konstruktor nie może być, rzecz jasna, wywołany na obiekcie, ponieważ dopiero stworzy ten obiekt. Potrzebuje więc wskaźnika na klasę obiektu do stworzenia. Z tego wynika, że nie można wywołać konstruktora poprzez DoMethod(). Do tego celu biblioteka intuition.library ma w swoim obfitym API funkcje NewObjectA() i NewObject(). Różnica między nimi to sposób podania konstruktorowi początkowych wartości atrybutów. NewObjectA() przyjmuje wskaźnik na taglistę zawierającą wartości początkowe. NewObject() pozwala na zbudowanie tej listy tagów na stosie z kolejnych agrumentów.

Funkcja NewObject[A]() posiada dwa alternatywne argumenty podające klasę obiektu do stworzenia. Klasę prywatną podaje się poprzez wskaźnik na strukturę Class. Klasę publiczną podaje się poprzez wskaźnik nazwę, która jest łańcuchem tekstowym (zakończonym, jak to zwykle bywa, bajtem 0). Jeżeli podajemy wskaźnik na klasę prywatną, wskaźnik na nazwę powinien mieć wartość NULL i odwrotnie, jeżeli używamy nazwy, wskaźnik na klasę powinien być NULL-em. Poniższe cztery przykłady demonstrują tworzenie obiektów klasy publicznej i prywatnej z użyciem bądź to NewObjectA(), bądź to NewObject():

Object *obj;
Class *private;

struct TagItem initial = {        /* wartości początkowe atrybutów */
  { MUIA_Attribute1, 4 },
  { MUIA_Attribute2, 46 },
  { TAG_END, 0 }
};

Klasa prywatna, NewObjectA():

obj = NewObjectA(private, NULL, initial);

Klasa prywatna, NewObject():

obj = NewObject(private, NULL,
  MUIA_Attribute1, 4,
  MUIA_Attribute2, 46,
TAG_END);

Klasa publiczna, NewObjectA():

obj = NewObjectA(NULL, "some.class", initial);

Klasa publiczna, NewObject():

obj = NewObject(NULL, "some.class",
  MUIA_Attribute1, 4,
  MUIA_Attribute2, 46,
TAG_END);

Funkcja NewObject[A]() zwraca wskaźnik zerowy w przypadku nieudanej konstrukcji obiektu. Typowe przyczyny to: nieprawidłowy wskaźnik na klasę lub nazwę klasy, brak wolnej pamięci, błędne lub brakujące wartości początkowe atrybutów. Wartość zwracana przez konstruktor powinna być zawsze sprawdzana w kodzie.


Destrukcja obiektu

Do zniszczenia obiektu służy metoda OM_DISPOSE(). W przeciwieństwie do konstruktora, destruktor może być wywołany poprzez DoMethod():

DoMethod(obiekt, OM_DISPOSE);

W bibliotece intuition.library znajdziemy nakładkę na tę metodę, nazwaną DisposeObject():

DisposeObject(obiekt);


Jak MUI rozszerza BOOPSI

Magic User Interface nie tylko bazuje na BOOPSI, ale także go rozszerza. Oprócz postawienia do dyspozycji programisty szerokiej palety klas standardowych, MUI modyfikuje także co nieco sposób działania BOOPSI. W tym rozdziale omówione są dwie modyfikacje: rozszerzenie struktury Class oraz własne funkcje do konstrukcji i destrukcji obiektów.

MUI używa struktury MUI_CustomClass do reprezentowania swoich klas. Struktura ta zawiera w sobie standardową strukturę Class. W czasie tworzenia obiektów klas prywatnych MUI za pomocą NewObject(), należy "wydobyć" strukturę Class z MUI_CustomClass:

struct MUI_CustomClass *priv_class;
Object *obj;

obj = NewObject(priv_class->mcc_Class, NULL, /* ... */ TAG_END);

Druga różnica między BOOPSI a MUI to funkcje używane do wywoływania konstruktora i destruktora. Używając MUI należy używać funkcji MUI_NewObject[A]() i MUI_DisposeObject(), ale tylko do klas publicznych. Obiekty klas prywatnych są po staremu tworzone przez NewObject(), jak w przykładzie powyżej. Główną zaletą MUI_NewObject() jest automatyczne otwieranie i zamykanie klas publicznych. Oto przykład:

Object *text;

text = MUI_NewObject(MUIC_Text, MUIA_Text_Contents, "foobar", TAG_END);

'MUIC_Text jest makrem zdefiniowanym w pliku nagłówkowym <libraries/mui.h> i jest rozwijane do łańcucha tekstowego "Text.mui". Do nazw publicznych klas MUI powinno się odwoływać poprzez makra MUIC_, bezpośrednie używanie nazw nie jest zalecane. Makra pozwalają uniknąć błędów w nazwach klas, takie błędy dzięki użyciu makr zostaną wychwycone na etapie kompilacji. Na początek MUI sprawdza, czy klasa Text.mui nie znajduje się czasem na systemowej liście klas publicznych. Jeżeli nie, klasa jest ładowana z dysku i otwierana. Następnie tworzony jest obiekt. Zamykanie klasy jest również wykonane automatycznie. Wszystkie obiekty MUI powinny być usuwane funkcją MUI_DisposeObject(), której jedynym argumentem jest wskaźnik na obiekt do usunięcia, podobnie jak w przypadku DisposeObject().

MUI_DisposeObject(text);