Monthly archive for August, 2008

Długie ciągi odwołań

2008-08-11 22:12

Chociaż obiekty powinny być jak najbardziej autonomiczne i możliwie niezależne od innych, to bardzo często zdarza się sytuacja, gdy trzeba “zrobić coś” używając czegoś “powszechnie dostępnego”. W praktyce chodzi też o to, by obiekty nie miały kilometrowej długości konstruktorów, do których trzeba by przekazywać wszystko, z czego ewentualnie będą one chciały kiedyś skorzystać.
Dlatego też stosuje się raczej inne rozwiązania, pozwalające w razie potrzeby ‘skoczyć’ w inne miejsce całej sieci powiązań między obiektami, jaka zwykle występuje w programie.

Sieć ta ma zresztą często formę drzewka, wychodzącego od wspólnego korzenia (jakiegoś ‘superobiektu’, reprezentującego całą aplikację), zawierającego w sobie wszystkie obiekty niższych rzędów. Logiczne jest więc zapewnienie odpowiednich metod, pozwalających na dostęp do nich. Wówczas można zawsze rozpocząć od samej góry i, schodząc coraz niżej, dojść w końcu do interesującego nas obiektu.
Z drugiej strony można też rozpoczynać wędrówkę zawsze od obiektu, w którym aktualnie jesteśmy, i w razie potrzeby poruszać się też w górę. Aby było to możliwe, należy jednak dla każdego obiektu jasno określić jego obiekt nadrzędny (parent) i dać możliwość dostępu do niego (np. poprzez przekazanie w konstruktorze odpowiedniego wskaźnika).

Niestety, obie te metody mogą w niektórych sytuacjach zaowocować długimi ciągami wywołań w rodzaju:

  1. ParentObject()->ParentObject()->SubObject3()->Objects(56)->SubObject1()

Według mnie jest to jednak żadna wada. Podobne potworki świadczą tak naprawdę, że struktura programu jest nieprzemyślana i niezgodna z potrzebami. Nic zatem dziwnego, że korzystanie z niej jest trudne i niewygodne. W tym przypadku można wręcz powiedzieć, że język programowania wykazuje się pewną inteligencją, jednoznacznie wskazując nam, że coś robimy źle ;-)

Tags:
Author: Xion, posted under Programming » 11 comments

Obiektowy szowinizm

2008-08-08 22:00

Od jakiegoś czasu najpopularniejszymi językami programowania są albo te stricte obiektowe, albo chociaż oferujące przynajmniej najważniejsze cechy tego modelu programowania (czyli dziedziczenie, polimorfizm i metody wirtualne). Prawdopodobnie większość powstającego kodu realizuje więc założenia OOP-u co najmniej w takim stopniu, że można go uznać za spełniający paradygmat obiektowy – albo chociaż podpadający pod obiektowość bardziej niż pod cokolwiek innego.
Ano właśnie: warto od czasu do czasu zauważyć, że oprócz OOP-u istnieje też “coś innego”, i że obiektowy model programowania wcale nie jest jedynym. Nie musi być on też ani ostatecznym, ani najbardziej uniwersalnym. A już tym bardziej nie jest on jedynie słusznym.

Jako koderzy podążający za wytycznymi programowania obiektowego często jednak tak uważamy – mniej lub bardziej świadomie. Zwłaszcza, że obecnie można zajmować się programowaniem całkiem długo i nawet nie słyszeć o innych paradygmatach niż obiektowy. A jeśli nawet ktoś na podobne “ciekawostki” lub na “dziwaków” opowiadających się na innym podejściem do kodowania, to najpewniej nie będzie to miało żadnego wpływu na jego dobre samopoczucie i przekonanie o wyższości OOP-u nad czymkolwiek innym.
Bo przecież łatwo znaleźć programistów myślących bardzo podobnie i bez wysiłku wyciągnąć całe mnóstwo silnych argumentów na poparcie swoich racji. Bodaj najczęściej wykorzystywanym jest ten mówiący o odpowiednim poziomie abstrakcji, który jest jakoby immanentną cechą programowania obiektowego. Z jednej strony jest on bowiem wyższy od brzydkiego programowania strukturalnego, dzięki czemu kod obiektowy łatwiej jest napisać i zrozumieć. Jednocześnie nie jest to też poziom zbyt wysoki, przez co nadal wiadomo, jak nasz program działa i jak z grubsza sprawuje się pod względem wydajnościowym. Tego samego nie można rzecz jasna powiedzieć wtedy, gdy kodujemy funkcyjnie czy deklaratywnie.

Wszystko to brzmi rozsądnie i wydaje się słuszne. W rzeczywistości jednak posługiwanie się takimi kryteriami jest zwykłym nadużyciem. Na takiej zasadzie można by narzekać na to, że nie da się przygotować omletu przy pomocy samego widelca (że pozwolę sobie posłużyć się analogią kulinarną ;]). Należy bowiem zawsze mieć na uwadze to, o jakim zastosowaniu mówimy. Programowanie nie jest przecież dziedziną abstrakcyjną, ale jak najbardziej praktyczną, niezależnie od tego, jak bardzo usiłowalibyśmy ją steoretyzować.
Dlatego też absurdem jest twierdzenie o wyższości programowania obiektowego nad innymi paradygmatami. Bo czy bawilibyśmy się w tworzenie klas w przypadku takiego oprogramowania jak firmware procesora czy karty graficznej, działającego w ścisłym powiązaniu ze sprzętem? I czy odpowiadałoby nam, gdyby zamiast określania za pomocą znaczników wyglądu fragmentów strony WWW bylibyśmy zmuszeni tworzyć dla każdego z nich osobny obiekt i ustawiać jego właściwości?…

Nie zamykajmy się więc w swoim obiektowym światku, nawet jeśli czujemy się w nim nadzwyczaj dobrze. Znajomość innych sposobów kodowania może nam bowiem tylko pomóc.

Tags: ,
Author: Xion, posted under Programming, Thoughts » 4 comments

Czy void jest typem

2008-08-05 17:21

Jak wiadomo, w C i C++ nie ma procedur. Są tylko funkcje, czyli podprogramy obliczające i zwracające jakąś wartość. Jednak możliwe jest zadeklarowanie takiej funkcji, która w istocie nic nie zwraca; służy do tego słowo kluczowe void, rzecz jasna. Pełni ono wówczas rolę nazwy typu zwracanego przez funkcję:

  1. void Procedure { /* ... */ }

Ktoś mniej doświadczony (lub o mniej schematycznym podejściu do C++) mógłby wobec tego spytać: Czy void jest wobec tego rzeczywiście typem, takim jak chociażby int czy char, czy jednak jest to tylko dziwne słowo na oznaczenie procedur w C++?

Odpowiedzią na powyższe pytanie może być tylko stanowcze… i tak, i nie :)
Z jednej strony wiemy na przykład, że void może występować w nagłówkach funkcji, i to nie tylko w miejscu typu wartości zwracanej, ale też na liście parametrów. Jednakże w tym drugim przypadku oznacza to jedynie to, iż funkcja tak naprawdę nie przyjmuje żadnych argumentów. W C++ można spokojnie pominąć to użycie void, osiągając dokładnie ten sam efekt – funkcji bezparametrowej. (Warto aczkolwiek zaznaczyć, że w C jest inaczej).
Inną rolą void sugerującą jego “typowość” jest istnienie wskaźników typu void*. Do tego typu można niejawnie skonwertować dowolny wskaźnik:

  1. void* p = new CFoo;

Operacja odwrotna nie jest jednak możliwa (wymaga co najmniej static_cast), a sam void* nie jest tak naprawdę pełnoprawnym wskaźnikiem – nie pozwala bowiem na dereferencję. Ponadto nie istnieje też typ referencyjny void&.
Wreszcie, chyba najbardziej egzotycznym użyciem void jest rzutowanie na niego wyrażenia innego typu. Jest to jak najbardziej możliwe i dotyczy zwykle sytuacji podobnych do tej:

  1. // Pokaż okno komunikatu tylko z przyciskiem OK
  2. static_cast<void>(MessageBox(NULL, _TEXT("Błąd!"), NULL, MB_OK | MB_ICONERROR));

Używamy tutaj funkcji, która zwraca rezultat (int), ale on nas nie interesuje. Pokazujemy przecież komunikat z jednym przyciskiem OK (więc nie musimy pobierać decyzji użytkownika), a ponadto nie ma tutaj żadnej wyobrażalnej możliwości wystąpienia błędu, o którym mógłby nas poinformować rezultat funkcji. Niektóre kompilatory z włączonym maksymalnym poziomem ostrzeżeń mogą aczkolwiek ostrzegać o ignorowaniu potencjalnie przydatnego rezultatu funkcji; rzutowanie na void pozwala takich ostrzeżeń uniknąć. Jednak sam fakt, iż takie rzutowanie jest możliwe, nie implikuje od razu możliwości zadeklarowania zmiennej typu void. To w jest rzecz jasna niemożliwe.

Cóż więc z tym fantem zrobić? No cóż, sprawa jest raczej śliska. Zapewne dałoby się jeszcze znaleźć przynajmniej po kilka argumentów zarówno za tym, że void typem jest, jak i że nie jest. Ale od rozstrzygnięcia ważniejsze jest raczej to, aby z tego… słowa kluczowego (pozostańsmy przy neutralnej wersji) umieć korzystać. Co, swoją drogą, nie jest takie trudne ;)

Tags: ,
Author: Xion, posted under Programming » Comments Off on Czy void jest typem

Prawie jak mapa

2008-08-04 17:12

Każda porządna biblioteka pojemników posiada kontener w typie mapy lub słownika, który służy do przechowywania par klucz-wartość, czyli odwzorowywania jednych na drugie. Zwykle pojemnik taki jest zaimplementowany przy pomocy odpowiednio zarządzanej struktury drzewiastej. I tak np. w C++ mamy od tego klasę std::map, w .NET – System.Collections.Generic.Dictionary, a w Javie cały zestaw klas implementujących interfejs java.util.Map.

Czasami jednak korzystanie z tego typu rozwiązań może być strzelaniem z armaty do komara. Jeśli bowiem:

  • nasze klucze nie są typami złożonymi (np. łańcuchami znaków), a raczej wartościami wyliczeniowymi
  • nie jest ich zbyt dużo, zwłaszcza w stosunku do ilości pamięci, którą możemy zająć
  • przypisane kluczom wartości nie są dużymi obiektami
  • istnieją dla nich rozsądne ustawienia domyślne

to możemy zastosować o wiele prostsze rozwiązanie. Polega ono na użyciu typu wyliczeniowego dla kluczy:

  1. enum KEYS { KEY_0, KEY_1, /*... */ KEY_COUNT };

oraz zwykłej tablicy do przechowywania wartości (tutaj typu bool):

  1. bool dictionary[KEY_COUNT];

Proste i skuteczne, a i nieco wygodniejsze składniowo niż standardowe std::map. A także wybitnie mało odkrywcze; podejrzewam, że każdy średnio zaawansowany programista miał okazję zetknąć się z podobną “sztuczką”. Co jednak nie znaczy, że nie warto o niej czasem wspomnieć i zrobić dobry uczynek w postaci propagowania dobrych i sprawdzonych rozwiązań ;-)

Tags: , ,
Author: Xion, posted under Programming » 1 comment

Triki z PowerShellem #9 – Potokowanie

2008-08-03 17:33

Jedną z głównych zalet powłok tekstowych w rodzaju PowerShella jest to, że pozwalają one na łączenie ze sobą małych, elementarnych poleceń w jedno duże zadanie. Gdy odbywa się to na zasadzie przekazywania wyników jednego programu na wejście następnego, mówimy o potokowaniu (pipelining). Znawcy basha mogliby rzucić np. takim przykładem:

  1. cat < program.cpp | grep int | wc --lines[/code]
  2. w którym to najpierw odczytywana jest zawartość pliku (<kbd>cat</kbd>), wyszukiwane są wszystkie linijki zawierające ciąg "int" (<kbd>grep</kbd>) i liczona jest ich ilość (<kbd>wc</kbd>). Całkiem oczywiste, prawda? ;-)
  3.  
  4. W PowerShellu przetwarzanie potokowe jest o tyle lepsze niż w innych powłokach, że między poleceniami wymienia się nie tekst, a obiekty .NET-owe. Dobrze byłoby więc wiedzieć, jak należy pisać własne skrypty, aby mogły one działać w potokach. Na szczęście nie jest to specjalnie trudne; prezentuje to ten oto skrypt:
  5. [csharp]# pokemonize.ps1
  6. # Zmienia litery w tekście na losowo duże lub małe
  7.  
  8. begin
  9. {
  10.     # Inicjujemy generator liczb losowych
  11.     $rand = New-Object Random
  12. }
  13.  
  14. process
  15. {
  16.     $str = $_.ToString()
  17.     $result = New-Object Text.StringBuilder
  18.    
  19.     # Zmieniamy znaki
  20.     for ($i = 0; $i -lt $str.Length; ++$i)
  21.     {
  22.         if ($rand.NextDouble() -lt [float]0.5)
  23.             { $newChar = [Char]::ToLower($str[$i]) }
  24.         else
  25.             { $newChar = [Char]::ToUpper($str[$i]) }
  26.            
  27.         $result.Append($newChar) | Out-Null
  28.     }
  29.    
  30.     # Wyświetlamy przetworzony ciąg
  31.     $result.ToString()
  32. }[/csharp]
  33. Tym, co on robi, jest losowa zmiana wielkości liter w ciągu stanowiącym tekstową reprezentację obiektu (lub obiektów), jaki dostaje na wejściu. Jednym słowem, dokonuje jego <em>pokemonizacji</em>... Pewnie dla niektórych byłoby to przydatne, ale powiedzmy, że to tylko taki przykład ;P
  34. Najważniejsze jest to, że podany skrypt działa dobrze na obiektach otrzymanych poprzez potok, czyli np. linijkach tekstu odczytanego z pliku poprzez <kbd>Get-Content</kbd>:
  35. [code]Get-Content ./Plik.txt | . ./pokemonize.ps1 | Out-Host

Dla każdego takiego obiektu (tutaj: wiersza z pliku) wykonywany jest blok process, w którym to ów obiekt jest reprezentowany jako zmienna $_. Można to sobie wyobrazić jako wnętrze pętli foreach, przelatującej po wszystkich obiektach z wejścia skrypt. Dodatkowo możemy jeszcze określić bloki: begin (uruchamiany na samym początku) i end (na końcu). W nich powinniśmy umieścić ewentualny kod inicjalizujący i/lub kończący pracę skryptu.

Tags: ,
Author: Xion, posted under Applications » Comments Off on Triki z PowerShellem #9 – Potokowanie
 


© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.