Posts tagged ‘Java’

Pola i akcesory wewnątrz klasy

2009-10-26 23:53

Zgodnie z zasadami programowania obiektowego pola klas nie powinny być bezpośrednio dostępne na zewnątrz. Należy jest zawsze opakowywać w akcesory: właściwości lub krótkie metody typu get i set. Z nich właśnie korzysta potem kod zewnętrzny, dzięki czemu nie może on (w dobrze napisanej klasie) niczego zepsuć poprzez – chociażby – ustawienie jakiegoś pola na nieprzewidzianą wartość.
Taka praktyka jest powszechnie przyjęta i raczej nie budzi wątpliwości. Inaczej jest z używaniem tychże pól lub akcesorów wewnątrz klasy, a więc w jej własnych metodach. Tutaj często mamy wybór: czy odwołać się do “gołego” pola, czy też poprzez odpowiednią właściwość/metodę.

Które podejście jest właściwsze? C#/.NET od wersji 3.0 zdaje się to rozstrzygać, umożliwiając półautomatyczne tworzenie właściwości:

  1. public class SomeClass
  2. {
  3.     public int Foo { get; set; }
  4. }

Nie ma tutaj nie tylko bloków get i set, ale i ukrytą pod tą właściwością pola. Przy korzystaniu z tego feature‘a żadnego dylematu więc nie ma.
Wydaje mi się jednak, że wybór nie jest taki oczywisty i że niekoniecznie należy używać akcesorów wewnątrz klasy. Argumentem przeciw, który od razu przychodzi do głowy, jest troska o wydajność – zwykle jednak przesadzona, bo proste gettery i settery są bez problemu rozwijane w miejscu użycia. Drugim ‘ale’ jest wygląd kodu w językach bez właściwości; zwłaszcza dotyczy to Javy, w której odpowiednik C#-owego:

  1. Foo.Bar.Baz.Qux.Thud = 5;

roiłby się od getów. W końcu można by się jeszcze pokusić o uzasadnienie na wpół merytoryczne: skoro bądź co bądź prywatne pole jest składnikiem klasy do jej wyłącznej dyspozycji, to dlaczego metody miałyby obchodzić je dokoła zamiast odwoływać się doń bezpośrednio? A może jednak lepiej jest skorzystać z tej dodatkowej warstwy pośredniczącej (mogącej np. wykrywać jakieś błędy)?…

Na razie – mimo całkiem przyzwoitego doświadczenia w programowaniu w językach wszelakich – trudno jest mi na te pytania odpowiedzieć. Ostatnio aczkolwiek skłaniam się ku bezpośredniemu dostępowi do pól w metodach klas. Chętnie poznałbym jednak opinie innych koderów na ten temat.

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

Ściąga z modyfikatorów dostępu

2009-10-16 16:08

W prawie wszystkich językach obiektowych istnieją tak zwane specyfikatory dostępu, przykładem których jest choćby public czy private. Pozwalają one ustalać, jak szeroko dostępne są poszczególne składniki klas w stosunku do reszty kodu. Modyfikatory te występują m.in. w trzech najpopularniejszych, kompilowalnych językach obiektowych: C++, C# i Javie.

Problem polega na tym, że w każdym z nich działają one inaczej. Mało tego: każdy z tych języków posiada inny zbiór tego rodzaju modyfikatorów. Dla kogoś, komu zdarza się pisać regularnie w przynajmniej dwóch spośród nich, może to być przyczyną pomyłek i nieporozumień.
Dlatego też przygotowałem prostą ściągawkę w postaci tabelki, którą prezentuję poniżej. Symbol języka występujący w komórce oznacza tutaj, że dany modyfikator (z wiersza) powoduje widoczność składnika klasy w określonym miejscu (z kolumny).

Specyfikator Klasa Podklasa Pakiet Reszta
public C++ C# Java C++ C# Java C# Java C++ C# Java
protected C++ C# Java C++ C# Java Java
protected internal C# C# C#
internal C# Java C# Java
private C++ C# Java

Parę uwag gwoli ścisłości:

  • Znaczek C++ nie występuje w kolumnie Pakiet nawet dla modyfikatora public, jako że w tym języku w ogóle nie istnieje pojęcie pakietu.
  • Sam termin ‘pakiet’ jest poza tym właściwy dla Javy. W .NET (C#) jego odpowiednikiem jest assembly, jakby ktoś nie wiedział :)
  • Z kolei internal jest słowem specyficznym dla C#, a jego ekwiwalentem w Javie jest po prostu pominięcie modyfikatora w deklaracji (np. int x; zamiast internal int x;).
  • W końcu, protected internal jest modyfikatorem występującym wyłącznie w C#/.NET.
Tags: , , , , , ,
Author: Xion, posted under Programming » 10 comments

Sortowanie dla zaawansowanych

2009-07-02 18:06

Gdy trzeba posortować jakąś kolekcję w sposób – nazwijmy to – standardowy, to zwykle nie ma z tym problemu. Chyba każdy język posiada coś na takie okazje: mamy funkcję qsort, std::sort (i std::list::sort), System.Array.Sort, java.util.Arrays.sort, i tak dalej. Zwykle wystarczy podać im tablicę lub kolekcję i już mamy ją posortowaną.
Istnieją jednak przynajmniej dwie sytuacje, gdzie taka standardowa procedura nie jest wystarczająca. Jest tak na przykład wtedy, gdy:

  • zwykły sposób porównywania obiektów nie jest tym, którego chcielibyśmy używać przy sortowaniu. Ów “zwykły sposób” jest wyznaczany najczęściej przez operatory relacyjne (<, >=, itp.), które nie muszą być zdefiniowane (np. jeśli sortujemy obiekty własnego typu złożonego) lub mogą być określone inaczej niż byśmy w danym przypadku sortowania chcieli.
  • życzymy sobie odwrotnego porządku sortowania, czyli np. malejącego zamiast domyślnego rosnącego

W powyższych przypadkach rozwiązanie sprowadza się zwykle do zdefiniowania własnego kryterium porównawczego obiektów, które sortujemy. Spotyka się tu najczęściej dwie formy takiego kryterium:

  • W C++ możemy zdefiniować predykat binarny (funkcję przyjmującą dwa argumenty i zwracającą wartość binarny), który określa dla obiektów tzw. ścisłe uporządkowanie słabe (strict weak ordering). Mówiąc w skrócie, funkcja ta powinna mieć identyczne właściwości jak zwykły operator mniejszości <, czyli zwracać true, jeśli pierwszy obiekt jest “mniejszy” niż drugi. Jak nietrudno zauważyć, taka informacja jest wystarczająca do jednoznacznego uporządkowania dwóch obiektów. W szczególności a == b zachodzi wtedy i tylko wtedy, gdy !(a < b) && !(b < a), zatem taki predykat może też służyć do sprawdzania, czy dwa obiekty są sobie równe.
    Domyślnym predykatem sortowania w C++ jest std::less, który działa identycznie jak operator <. std::greater pozwala natomiast na odwrócenie porządku sortowania.
  • Wynik Relacja
    < 0 x < y
    == 0 x == y
    > 0 x > y

    W C, C# i w Javie korzysta się z kolei z funkcji porządkujących, które przyjmują dwa obiekty i zwracają w wyniku liczbę całkowitą. Liczba ta określa jednoznacznie uporządkowanie tych dwóch obiektów: czy jeden jest mniejszy niż drugi, równy mu czy większy od niego - tak, jak to pokazano w tabelce obok.
    Interesującą cechą takiego sposobu porządkowania jest to, że tak naprawdę używanie jakichkolwiek porównań (<=, > itp.) często nie jest w nim potrzebne. Zazwyczaj bowiem ową funkcję porównującą można zaimplementować następująco:

    1. int compare(x, y) { return val(x) - val(y); }

    gdzie val(x) oznacza tutaj pewną wartość liczbową przyporządkowaną obiektowi x, określającą jego miejsce w danym porządku sortowania. Wartość ta powinna być tak dobrana, aby posortowanie wszystkich takich liczb rosnąco dało nam w wyniku pożądaną kolejność uporządkowania naszych obiektów. Jeśli więc, przykładowo, chcemy posortować ciągi znaków w C# tak, by najdłuższe znalazły się na początku (czyli malejąco względem długości), to val(x) oznaczać tu będzie -x.Length().

Uff, jak widać sortowanie czasami nie jest wcale taką prostą sprawą :) Cieszmy się jednak, że nawet w bardziej skomplikowanych sytuacjach do napisania pozostają nam jedynie proste predykaty porównujące, nie zaś... całe algorytmy ;]

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

Funkcje jako dane wejściowe

2009-05-29 15:56

Sporo algorytmów jako swoje parametry przyjmuje różnego typu funkcje, które potem są wykorzystywane w trakcie ich działania. Prostym przykładem są tu wszelkiego rodzaju sortowania czy wyszukiwania, umożliwiające często podanie własnego predykatu (funkcji zwracającej wartość logiczną). W bardziej skomplikowanej wersji może chodzić chociażby o algorytm genetyczny lub przeszukujący drzewo gry, który wykorzystuje do działania jakąś funkcję oceniającą (np. osobników w populacji).

Na takie okazje różne języki programowania oferują różne narzędzia, lecz w większości przypadków sprowadzają się one do jednego z poniższych:

  • Wskaźniki lub delegaty. Przekazywanie funkcji przez wskaźnik jest proste i naturalne, ale ma pewne ograniczenia. W C(++) na przykład nie da się zwykłym wskaźnikiem pokazać na metodę konkretnego obiektu; docelowo może to być tylko funkcja globalna lub statyczna. Ten problem nie występuje zwykle w przypadku delegatów, którymi często można pokazać właściwie na wszystko – również na anonimową funkcję zdefiniowaną ad hoc:
    1. // sortowanie listy w oparciu o własny predykat (C# 2.0 wzwyż)
    2. objects.Sort (delegate (object a, object b)
    3.     { return a.ToString().Length - b.ToString().Length; });

    Niektóre języki nie mają jednak ani wskaźników, ani delegatów – w nich zwykle stosuje się sposób następny.

  • Interfejsy i metody wirtualne. Ta technika polega na zdefiniowaniu interfejsu (lub klasy abstrakcyjnej) z metodą wirtualną, którą docelowo będzie wywoływać algorytm. Korzystający z niego programista implementuje po prostu wskazany interfejs (lub definiuje klasę pochodną) i nadpisuje wspomnianą metodę własną wersją. Ten sposób jest szczególnie popularny w Javie, gdzie wspomagają go inne mechanizmy językowe – jak choćby niestatyczność klas wewnętrznych lub możliwość ich definiowania inline – np. w wywołaniu funkcji.
    Zauważmy też, że implementacja naszej ‘funkcji’ w postaci klasy pozwala też na dodatkowe możliwości, jak na przykład posiadanie przez nią stanu (co aczkolwiek w wielu przypadkach, np. predykatów sortowania, jest wysoce niezalecane).
  • Funktory. To rozwiązanie jest specyficzne dla C++ i opiera się na dwóch występujących w tym języku feature‘ach: szablonach i przeciążaniu operatorów. Dzięki temu algorytm korzystający z “czegoś co przypomina funkcję” może wyglądać choćby tak:
    template < typename T, typename SearchFunc >
    const T find(const std::vector& v, SearchFunc pred)
    {
    for (int i = 0; i < v.size(); ++i) if (pred(v[i])) return v[i]; // znaleziono element spełniający predykat return T(); }[/cpp] Określenie "coś co przypomina funkcję" jest jak najbardziej na miejscu, gdyż tutaj predykatem może być cokolwiek, co da się jako funkcję potraktować - czyli wywołać operatorem (). Może więc to być zwykły wskaźnik, jakaś ładna zewnętrzna implementacja mechanizmu delegatów (np. FastDelegate) lub jakikolwiek obiekt z przeciążonym operatorem nawiasów (). Właśnie te ostatnie nazywa się zwykle funktorami, a jeśli ktoś nieco bardziej zagłębił się w bibliotekę STL, na pewno nie raz się z nimi spotkał.

Trzeba też powiedzieć, że właściwie istnieje też inny sposób: zamiast samej funkcji przekazywanie jej… nazwy. “I niby jak ją potem wywołać?”, można zapytać. Ano to już indywidualna kwestia każdego języka – w tym przypadku zwykle interpretowanego :)

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

Prawie jak DirectX

2008-09-14 23:28

Przedwczoraj w końcu udało mi się skończyć pewien “drobny” projekt, który – pomimo tego że był ściśle związany z programowaniem grafiki – nie należał wcale do lekkich, łatwych i przyjemnych. To pewnie dlatego, że chodzi tu o software’owy renderer, który (na)pisałem w Javie (!) jako projekt uczelniany. Niewykluczone, że niektórzy pamiętają jeszcze, o co chodzi ;-)
W wymaganiach przewidziana była na szczęście tylko jedna, za to konkretna scena – mianowicie pewien znany skądinąd most. W porównaniu z oryginałem nie wyszedł on może specjalnie imponująco, ale nie zapominajmy, że każdy z jego pikseli musiał zostać pieczołowicie wyliczony przez biedny procesor ;) Rezultaty prezentują się zaś następująco:

 

I wprawdzie tego nie widać, ale manipulowanie tą sceną (złożoną z kilkuset trójkątów) mogło się odbywać w sposób całkiem płynny – o ile oczywiście powiększenie nie było zbyt duże :) Tym niemniej nie wróżę jednak temu rozwiązaniu zbyt wielu praktycznych zastosowań. Co oczywiście ani na jotę nie zmienia faktu, że jego implementacja okazała się całkiem pouczająca. Jednym z praktycznych wniosków jest chociażby to, że modelowanie za pomocą ręcznego opisywania sceny we własnym formacie opartym na XML to zdecydowanie nie jest dobry pomysł ;]

Tags: ,
Author: Xion, posted under Programming, Studies » 8 comments

Statyczne składowe i takież konstruktory

2008-06-04 23:30

Wiadomo, że konstruktor to specjalna metoda, która jest wywoływana przy tworzeniu nowego obiektu i ma za zadanie go zainicjalizować. Okazuje się, że w innej wersji “konstruktor” (albo coś, co go przypomina) może służyć do inicjalizacji nie obiektu, lecz samej klasy. A co takiego ma klasa, że należy czasem zadbać o jej prawidłową inicjalizację? Ano chociażby składowe statyczne – wspólne dla wszystkich obiektów.
W C# możemy sobie zdefiniować statyczny konstruktor, który może się tym zająć – jak również (niemal) czymkolwiek innym. Taki konstruktor, podobnie jak zwykły ma postać metody o tej samej co klasa nazwie, lecz jest opatrzony modyfikatorem static. Zostaje on wywołany przed stworzeniem pierwszego obiektu klasy lub próbą dostępu do jakiejkolwiek składowej statycznej. Całkiem praktyczne, prawda?
Coś podobnego ma również Java, a zwie się to oryginalnie ‘statycznym inicjalizatorem’. W odróżnieniu od wariantu z C#, inicjalizator ten jest wywoływany wcześniej – już w momencie załadowania klasy przez maszynę wirtualną. Właściwie jednak nie ma to wielkiego znaczenia, bo efekt jest analogiczny: jeśli ów inicjalizator ma zrobić coś przed pierwszym użyciem klasy, na pewno zostanie to zrobione. Jego kod pisze się w definicji klasy, w bloku otoczonym nawiasami klamrowymi i opatrzonym – a jakże – słówkiem static.

A jak pod tym względem wypada C++? No cóż, jak zwykle blado :) Kruczkiem standardu jest fakt, że wszelkie składowe statyczne klas mają niezdefiniowaną kolejność inicjalizacji, jeśli są przypisane do odrębnych modułów (plików .cpp). Jeśli więc takie składowe zależą od siebie, to wynikiem mogą być tylko kłopoty.
Poradzić na to można dwojako – zależnie od tego, którą zaletę statycznych konstruktorów chcemy wykorzystać. Jeżeli na przykład chcemy, aby jakaś zmienna statyczna lub globalna była zainicjowana niezależnie od tego, gdzie i kiedy chcemy ją użyć, najlepiej uczynić ją lokalną statyczną zmienną w funkcji:

  1. Foo& GetFoo() { static Foo foo; return foo; }

Jeśli z kolei musimy mieć pewność, że przed stworzeniem obiektu naszej klasy zostaną wykonane jakieś dodatkowe czynności, to możemy to częściowo osiągnąć za pomocą statycznej flagi:

  1. class Foo
  2. {
  3.     public:
  4.         Foo()
  5.         {
  6.             if (!ms_bInitialized)
  7.             {
  8.                   // (czynności konieczne przed użyciem klasy)
  9.                   ms_bInitialized = true;
  10.             }
  11.         }
  12. };
  13. Foo::ms_bInitialized = false;

Niezależnie jednak od tego, co jest nam akurat potrzebne, nie powinniśmy nigdy zapominać o rozważeniu zależności między składowymi statycznymi klas i/lub zmiennymi globalnymi w naszym kodzie, jeśli chcemy uniknąć niezdefiniowanego zachowania. Bo wtedy po prostu wszystko się zdarzyć może :]

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

O układaniu kontrolek

2008-05-19 20:57

Najprostszym sposobem na rozmieszczanie kontrolek w oknie programu jest oczywiście… podanie ich pozycji i rozmiarów dosłownie. Ta metoda sprawdza się dobrze jednak tylko w prostych przypadkach: gdy mamy stałą (i raczej niewielką) liczbę obiektów, gdy rozmiary okna są stałe, gdy nie dostosowujemy rozmiaru interfejsu do rozdzielczości ekranu, itd. W wielu przypadkach to jednak nie wystarcza i dlatego systemy GUI oferują też inne narzędzia pozycjonowania elementów interfejsu. Niektóre z nich są całkiem pomysłowe. Oto kilka przykładów:

  • Przykład dokowania kontrolek
    Przykład dokowania

    Dokowanie (docking), zwane też wyrównywaniem (align). Polega to na “przyklejeniu” kontrolki do którejś z krawędzi nadrzędnego pojemnika (np. okna) w jednym wymiarze oraz do obu krawędzi w wymiarze prostopadłym. Komponentowi pozostaje wówczas jeden stopień swobody. Przykładowo, kontrolka zadokowana po lewej stronie może się jedynie rozszerzać w prawo (lub zmniejszać w lewo), zaś jej górna i dolna krawędź pokrywają się zawsze z odpowiednimi krawędziami zawierającego ją pojemnika.
    Ten sposób pozycjonowania jest idealny chociażby dla pasków stanu (status bar), które zawsze umiejscowione są w dolnej krawędzi okna.

  • Ustawianie zakotwiczania
    Ustawianie zakotwiczania
    w Visual Studio

    Zakotwiczanie (anchoring) polega z kolei na ustawieniu stałej odległości pewnych krawędzi kontrolki od odpowiednich krawędzi kontrolki nadrzędnej. Domyślnie na przykład każdy komponent jest zakotwiczony z lewej i z góry. Gdy zmieniamy rozmiar zawierającego go okna, dystans między lewą krawędzią tegoż okna i lewą krawędzią kontrolki pozostaje stały; podobnie jest z górnymi brzegami. Zmieniając ustawienia zakotwiczania możemy uzyskać efekt kontrolki “wyrównanej” w podobny sposób jak tekst w edytorze, np. do prawej strony okna zamiast do lewej.
    Anchoring można uznać za nieco lepszą wersję dokowania, ale w istocie różni się on od niego dość znacznie (zwłaszcza jeśli dokujemy wiele kontrolek po jednej stronie). Jest on jednak przydatny wtedy, gdy kontrolka ma w określony sposób zmieniać rozmiar wraz z oknem.

  • Układ typu flow
    Układ typu flow

    Układy (layouts) kontrolek. Są to bardziej wyspecjalizowane narzędzia, których jedynym zadaniem jest układanie elementów interfejsu w określony sposób. Do najbardziej przydatnych należy układ typu flow oraz układ tabelkowy. Ten pierwszy polega na rozmieszczaniu kontrolek po kolei (w ustalonym porządku) tak, jak wypisuje się tekst w kolejnych wierszach. (Jak widać, analogie tekstowe w projektowaniu interfejsu pojawiają się całkiem często ;]). Z kolei układ tabelkowy, jak nazwa wskazuje, dzieli zadany obszar na wiersze i kolumny tak, że w każdej z powstałych w ten sposób komórek mieści się dokładnie jedna kontrolką i wypełnia ją całkowicie.
    Układy mają spore zastosowanie zwłaszcza wtedy, gdy rozmieszczane kontrolki są tworzone dynamicznie w trakcie działania programu. Potrafią wówczas zaoszczędzić dużo pracy z ręcznym przeliczaniem ich pozycji i/lub rozmiarów.

To stwierdzenie odnosi się zresztą do każdego z tych trzech sposobów na określanie wymiarów elementu interfejsu. Obecnie bardzo często okazuje się, że nawet stosunkowo zaawansowany układ kontrolek, który musi dostosowywać się do zmian rozmiaru okna, da się stworzyć bez obsługiwania zdarzeń typu OnResize i ręcznego rozmieszczania komponentów. A to może oszczędzić kilku kłopotów i pozwala łatwiej skoncentrować się na tych częściach programu, których wprawdzie nie widać, ale które są o wiele ważniejsze :)

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


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