1. Obiektowość Javy I
(repetytorium i synteza)
Wykład poświęcony jest powtórzeniu podstawowych pojęć, związanych
z programowaniem obiektowym w Javie. Przedstawione koncepcje będą ilustrowane
na przykładzie budowy konkretnej klasy, opisującej pojazdy.
1.1. Podstawowe definicje
Obiekt – to konkretny lub abstrakcyjny byt, wyróżnialny w modelowanej rzeczywistości,
posiadający określone granice i atrybuty (właściwości) oraz mogący świadczyć
określone usługi.
Usługa – to określone działanie (zachowanie) obiektu, które jest on zobowiązany przejawiać.
Obiekty współdziałają ze sobą wymieniając komunikaty.
Komunikat - to wąski i dobrze określony interfejs, opisujący współzależność działania obiektów.
Komunikaty zwykle żądają od obiektów wykonania określonych (właściwych im) usług.
Klasa - to opis takich cech grupy podobnych obiektów, które są dla nich
niezmienne (np. zestaw atrybutów i metod czyli usług, które mogą świadczyć)
Definicje powyższe stanowią abstrakcyjne odzwierciedlenie cech rzeczywistości.
Na podstawie:
Kazimierz Subieta. Słownik pojęć obiektowych , FAQ na liście pl.comp.object
, P. Coad, E.Yourdon. Analiza obiektowa, Oficyna Wydawnicza READ ME, W-wa
1994 P. Coad, E.Yourdon. Projektowanie obiektowe, Oficyna Wydawnicza - READ
ME, W-wa 1994
1.2. Abstrakcja obiektowa. Klasy i obiekty
A.
język programowania wyposażony w podobne pojęcia ==>
==> projekt rozwiązania rzeczywistego problemu i jego oprogramowanie w języku
adekwatnym do problemu.
To zapewniają języki obiektowe. Jest to ich bardzo
ważna cecha – abstrakcja obiektowa, znacznie ułatwiająca tworzenie oprogramowania.
z drugiej strony
B. Programowanie polega na przetwarzaniu danych. Dane zawsze są określonych
typów, a typ to nic innego jak rodzaj danych i działania które na nich można
wykonać.
Z pragmatycznego punktu widzenie możemy więc powiedzieć, że klasa to typ,
jej definicja opisuje właściwości typu danych (również funkcjonalne tzn.
jakie są dostępne operacje na danych tego typu).
Języki obiektowe pozwalają na definiowanie własnych klas – własnych typów
danych, co właśnie oznacza programowanie w języku problemu.
O obiektach możemy myśleć jako o egzemplarzach określonych klas.
Możemy mieć np. klasę pojazdów o następujących atrybutach: szerokość, wysokość,
długość, ciężar, właściciel, stan (stoi, jedzie, zepsuty itp.) oraz udostępniających
usługi: ruszania, zatrzymywania, zmiany właściciela (sprzedaż pojazdu) itp
.
Gdy mamy np. dwa obiekty – egzemplarze klasy pojazdów, oznaczane przez zmienne
a i b, to możemy symulować w programie sekwencję działań: uruchomienie pojazdu
a, uruchomienie pojazdu b, zatrzymanie obu pojazdów - za pomocą komunikatów
posyłanych do obiektów, np.:
a.start(); // komunikat do pojazdu a: ruszaj!
b.start(); // komunikat do pojazdu b: ruszaj!
a.stop(); // komunikat do pojazdu a: zatrzymaj się!
b.stop(); // komunikat do pojazdu b: zatrzymaj się!
Mówi się również: a.start() - to użycie metody start() na rzecz obiektu oznaczanego przez zmienną a.
1.3. Hermetyzacja
Oprócz odzwierciedlenia w programie "języka problemu" abstrakcja obiektowa
ma jeszcze jedną ważną przewagę nad ujęciami nieobiektowymi.
Mianowicie, atrybuty obiektu nie powinny być bezpośrednio dostępne. W programie
z obiektami "rozmawiamy" za pomocą komunikatów, obiekty same "wiedzą najlepiej"
jak zmieniać swoje stany. Dzięki temu nie możemy nic nieopatrznie popsuć,
co więcej nie możemy zażądać od obiektu usługi, której on nie udostępnia.
Dane (atrybuty) są ukryte i są traktowane jako nierozdzielna całość z usługami.
Nazywa się to hermetyzacją i oznacza znaczne zwiększenie odporności programu na błędy.
Sama koncepcja klasy jako zestawu pól i metod już zapewnia określony poziom hermetyzacji.
Nie możemy np. do obiektów klasy Vehicle posłać komunikatu sing(), bowiem metoda sing() nie występuje jako składowa tej klasie.
Dodatkowo języki obiektowe (w tym Java) pozwalają ukrywać dane (i metody) przed powszechnym dostępem.
Dostęp do składowych klasy regulują tzw. specyfikatory dostępu, których używamy w deklaracjach zmiennych, stałych i metod.
Każda składowa klasy może być:
- prywatna – dostępna tylko w danej klasie (specyfikator private),
- zaprzyjaźniona – dostępna ze wszystkich klas danego pakietu; mówi się tu też o dostępie pakietowym
lub domyślnym - domyślnym dlatego, iż ten rodzaj dostępności występuje wtedy,
gdy w deklaracji składowej nie użyjemy żadnego specyfikatora,
- chroniona lub zabezpieczona – dostępna z danej klasy i wszystkich klas ją dziedziczących (specyfikator protected),
- publiczna – dostępna zewsząd (specyfikator public).
Po co jest prywatność?
1. Ochrona przed zepsuciem (pola powinny być prywtane)
- użytkownik klasy nie ma dostępu do prywatnych pól i nic nie popsuje (nieświadomie).
2. Zapewnienie właściwego interfejsu (metody "robocze" winny być prywatne)
- użytkownik klasy ma do dyspozycji tylko niezbędne (klarowne) metody, co ułatwia mu korzystanie z klasy.
3. Ochrona przed konsekwencjami zmiany implementacji
- twórca klasy może zmienić zestaw i implementację prywatnych metod,
nie zmieniając interfejsu publicznego: wszystkie programy napisane przy wykorzystaniu
tego interfejsu nie będą wymagały żadnych zmian.
Klasy publiczne a klasy pakietowe
Klasa pakietowa jest dostępna tylko z klas
pakietu.
Klasa publiczna jest dostępna zewsząd (z innych pakietów).
Klasę
publiczną deklarujemy ze specyfikatorem public
Stosując regułę ukrywania danych i specyfikatory dostępu możemy teraz przedstawić
przykładową definicję klasy Person, a następnie zmodyfikować definicję klasy
Vehicle.
Atrybuty obiektów klasy Person przedstawiono jako pola prywatne.
Spoza klasy nie ma do nich dostępu.
Przy tworzeniu obiektu jego elementy odpowiadające tym polom są inicjalizowane
za pomocą wywołania konstruktora.
Później zmiany tych elementów danych nie
są już możliwe, możemy tylko uzyskać dane za pomocą publicznych metod getName()
i getPesel().
Modyfikując i rozbudowując klasę Vehicle w myśl reguł hermetyzacji:
- uczynimy atrybuty pojazdów prywatnymi.
- dostarczymy – jako publicznego – tylko niezbędnego interfejsu.
- ukryjemy "roboczą" metodę zmieniającą stan obiektu.
- wprowadzimy publiczne stałe oznaczające stany pojazdu oraz odpowiadające
im nazwy stanów (które uczynimy prywatnymi, by ew. zmiany nazw nie wpływały
na oprogramowanie wykorzystujące klasę Vehicle) [ UWAGA: stałe specyfikujemy za pomocą final ].
public class Vehicle {
// stany
public final int BROKEN = 0, STOPPED = 1, MOVING = 2;
private final String[] states = { "ZEPSUTY", "STOI", "JEDZIE" };
private int width, height, length, weight;
private Person owner;
private int state;
public Vehicle(Person p, int w, int h, int l, int ww) { // konstruktor
owner = p; width = w; height = h;
length = l; weight = ww; state = STOPPED;
}
public void start() { setState(MOVING); }
public void stop() { setState(STOPPED); }
// Prywatna metoda robocza wykorzystywana w metodach start() i stop().
// Arbitralne ustalenie stanu spoza klasy nie jest możliwe
private void setState(int newState) {
if (state == newState || state == BROKEN)
System.out.println("Nie jest mozliwe przejscie ze stanu " +
states[state] + " do stanu " + states[newState]);
else state = newState;
}
public void repair() {
if (state == MOVING)
System.out.println("Nie można reperować jadącego pojazdu");
else if (state != BROKEN)
System.out.println("Pojazd sprawny");
else state = STOPPED;
}
// Sprzedaż pojazdu
public void sellTo(Person p) {
owner = p;
}
public String toString() {
return "Pojazd, właścicielem którego jest "
+ owner.getName() + " - " + states[state];
}
}
Zwróćmy uwagę na:
- atrybuty pojazdu (inicjacja, późniejsz zmiana)
- jak można zmienić stan pojazdu i czy zapewniona jest spójność?
- właściwość metody toString
1.4. Statyczne składowe klasy
Stałe (oznaczające stany i ich nazwy) są teraz zawarte w każdym tworzonym
obiekcie klasy Vehicle. Czy to dobrze?
Składowe klasy mogą być
statyczne i niestatyczne.
Niestatyczne zawsze wiążą się z istnieniem jakiegoś obiektu (pola
- odpowiadają elementom obiektu, metody muszą być wywoływane na rzecz obiektu,
są komunikatami do obiektu)
Składowe statyczne (pola i metody):
- są deklarowane przy użyciu specyfikatora static
- mogą być używane nawet wtedy, gdy nie istnieje żaden obiekt klasy
Uwaga: ze statycznych metod nie wolno odwoływać się do niestatycznych
składowych klasy (obiekt może nie istnieć). Możliwe są natomiast odwołania
do innych statycznych składowych.
Spoza klasy do jej statycznych składowych możemy odwoływać się na dwa sposoby:
- NazwaKlasy.NazwaSkładowej
- gdy istnieje jakiś obiekt (dodatkowo): tak samo jak do niestatycznych składowych
Zastosowanie pól i metod statycznych:
public class Vehicle {
public final static int BROKEN = 0, STOPPED = 1, MOVING = 2;
private final static String[] states = { "ZEPSUTY", "STOI", "JEDZIE" };
private static int count; // ile obiektów dotąd utworzyliśmy
private int currNr; // bieżący numer pojazdu
//...
public Vehicle(Person p, int w, int h, int l, int ww) {
// ....
// Każde utworzenie nowego obiektu zwiększa licznik o 1
// bieżąca wartość licznika nadawana jest jako numer pojazdu
// numer pojazdu jest niestatycznym polem klasy, a więc elementem obiektu
currNr = ++count;
}
//.....
// zwraca unikalny numer pojazdu
public int getNr() { return currNr; }
// zwraca liczbę dotąd utworzonych obiektów
// metoda jest statyczna, by móc zwrócić 0
// gdy nie ma jeszcze żadnego obiektu
static int getCount() { return count; }
// zwraca tablicę nazw stanów
// Metoda jest statyczna, bo dopuszczalne stany
// nie zależą od istnienia obiektów
static String[] getAvailableStates() {
return states;
}
// ...
}
Wykorzystanie:
// jakie są dopuszczalne nazwy stanów
// pojazdów?
String[] stany = Vehicle.getAvailableStates();
// ile obiektów dotąd utworzyliśmy?
int n =Vehicle.getCount()
1.5. Inicjacje
Przy tworzeniu obiektu:
- pola klasy mają zagwarantowaną inicjację na wartości ZERO (0, false
– dla typu boolean, null – dla referencji, co oznacza referencję "pustą",
nie odnosząca się do żadnego obiektu),
- zwykle w konstruktorze dokonuje się reinicjacji pól,
- ale można również posłużyć się jawną inicjacją przy deklaracji pól.
Obiekt klasy Vehicle składa się z następujących elementów (odpowiadają one polom klasy):
private int currNr;
private int width, height, length, weight;
private Person owner;
private int state;
W trakcie tworzenia obiektu ( new Vehicle(...) ) uzyskają one wartości 0
(dla elementów typu int) oraz null dla elementu, odpowiadającego referencji
owner.
Następnie zostanie wywołany konstruktor, w którym dokonujemy właściwej inicjacji.
Można by było napisac np.
private int state = STOPPED; // jawna inicjacja pola
i usunąć z konstruktora instrukcję state = STOPPED.
A co z polami statycznymi? I jaka jest kolejność inicjacji?
Reguły inicjacji
- Każde pierwsze odwołanie do klasy inicjuje najpierw pola statyczne.
Pierwszym odwołaniem do klasy może być odwołanie do składowej statycznej
(np. Vehicle.getCount()) lub utworzenie obiektu ( np. Vehicle v – new Vehicle());
- Tworzenie obiektu (new) inicjuje pola niestatyczne, po czym wykonywany jest konstruktor,
- Kolejność inicjacji pól - wg ich kolejności w definicji klasy (w podziale na statyczne i niestatyczne, najpierw statyczne)
|
Gdybyśmy np. zmienili interpretację statycznego pola count (niech będzie
to teraz pierwszy dopuszczalny numer identyfikacyjny) i jawnie je zainicjowali
wielkością 100, to w kontekście:
class Vehicle {
private int currNr = ++count;
private static count = 100;
...
public int getNr() { return currNr; }
public static int getCount() { return count; }
}
po:
Vehicle.getCount()
Vehicle v = new Vehicle(...);
wyrażenie:
v.getNr()
miałoby wartość 101.
Z inicjacją wiąże się również pojęcie bloków inicjacyjnych.
Jak pamiętamy - w Javie w zasadzie nie można używać instrukcji wykonywalnych
(m.in. sterujących) poza ciałami metod. Od tej zasady istnieją jednak dwa wyjątki:
- użycie niestatycznego bloku inicjacyjnego,
- oraz użycie statycznego bloku inicjacyjnego.
Niestatyczny blok inicjacyjny wprowadzamy ujmując kod wykonywalny w nawiasy
klamrowe i umieszczając taką konstrukcję w definicji klasy poza cialem jakiejkolwiek
metody. Kod bloku zostanie wykonany na etapie inicjacji obiektu, czyli przy
tworzeniu obiektu, przed wywołaniem konstruktora.
Taka możliwość może okazać się przydatna, gdy mamy kilka konstruktorów (o
wielu różnych konstruktorach definiowanych w jednej klasie - zob. następny
punkt) i chcemy wyróżnić pewien kod, który będzie inicjował obiekt niezależnie
od użytego konstruktora i przed użyciem jakiegikolwiek z nich.
Np. możemy w bloku inicjacyjnym wyodrębnić nieco bardziej zaawansowaną postać
inicjacji zmiennej state, opisująceje stan pojazdu.
public class Vehicle {
// ...
private int state;
// Niestatyczny blok inicjacyjny
// -- w niedzielę wszystkie samochody inicjalnie stoją
// -- w poniedziałek te o parzystuych numerach inicjalnie jadą,
// inne - stoją
// -- w pozostałe dni tygodnia: pierwszy stworzony inicjalnie stoi,
// inne jadą
{ // początek bloku
int dayOfWeek = Calendar.getInstance().get(Calendar.DAY_OF_WEEK);
switch (dayOfWeek) {
case Calendar.SUNDAY : state = STOPPED; break;
case Calendar.MONDAY : state = (currNr % 2 == 0 ? MOVING : STOPPED);
break;
default : state = (currNr == 1 ? STOPPED : MOVING);
break;
}
} // koniec bloku
// Konstruktory i metody klasy
public Vehicle(...) {
// ...
}
// ...
}
Uwaga: dla uzyskania aktualnego dnia tygodnia stosujemy klasę Calendar.
Wady - rozprosozny kod.
Ciekawszym rozwiązaniem wydaje się statycznyblok inicjacyjny
. Czasem pojawia się potrzeba wykonania jakiegoś kodu jeden raz, przy pierwszym
odwołaniu do klasy. Przy inicjacji pól statycznych możemy skorzystać z dowolnych
wyrażeń, składających się ze zmiennych i stalych statycznych oraz z wywołań
statycznych metod, ale - oczywiście - nie sposób użyć instrukcji wykonywalnych
(np. sterujących).
Statycznym blok inicjacyjny wprowadzamy słowem kluczowym static z
następującym po nim kodem ujętym w nawiasy klamrowe. Kod ten będzie wykonywany
jeden raz przy pierwszym odwołaniu do klasy (np. użyciu metody statycznej
lub stworzeniu pierwszego obiektu). Oczywiście, z takiego bloku możemy odwoływać
się wyłącznie do zmiennych statycznych (obiekt jeszcze nie istnieje).
Przykład:
wyobraźmy sobie, że numeracja pojazdów zależy od domyślnej lokalizacji aplikacji
(inna jest jeśli lokalizacją jest USA - inną dla, powiedzmy, Włoch). Będziemy
więc inicjować zmienną statyczną count w zależności od lokalizacji (w tym
przypadku oczywiście count nie będzie oznaczać liczby utworzonych w programie
obiektów, ale inicjalny numer pojazdu). Taki problem można rozwiązac właśnie
za pomocą statycznego bloku inicjacyjnego.
public class Vehicle {
public final static int BROKEN = 0, STOPPED = 1, MOVING = 2;
private final static String[] states = { "ZEPSUTY", "STOI", "JEDZIE" };
private static int count;
// Statyczny blok inicjacyjny
// za jego pomocą inicjujemy zmienną count w taki sposób,
// by numery pojazdów zaczynały się w zależności
// od domyślnej lokalizacji aplikacji
// np. jeśli aplikacja jest wykonywana w lokalizacji włoskiej
// numery zaczynają się od 10000.
static {
// Locale.setDefault(Locale.ITALY);
Locale[] loc = { Locale.UK, Locale.US, Locale.JAPAN, Locale.ITALY, };
int[] begNr = { 1, 100, 1000, 10000, };
count = 300; // jeżeli aplikacja działa w innej od wymienionych
// w tablicy lokalizacji, zaczynamy numery od 300
Locale defLoc = Locale.getDefault(); // jaka jest domyślna lokalizacja?
for (int i=0; i < loc.length; i++)
if (defLoc.equals(loc[i])) {
count = begNr[i];
break;
}
} // koniec bloku
// pola niestatyczne
private int currNr = ++count;
private int width, height, length, weight;
// ...
// Konstruktory i metody
// ...
} // koniec klasy
1.6. Przeciążanie metod i konstruktorów
W klasie (i/lub jej klasach pochodnych) możemy zdefiniować metody o tej samej
nazwie, ale różniące się liczbą i/lub typami argumentów.
Nazywa się to przeciążaniem metod.
Tak samo możemy mieć kilka wersji konstruktorów:
Klasa wcale nie musi mieć jawnie zdefiniowanego konstruktora.
W tym przypadku do definicji klasy automatycznie dodawany jest konstruktor domyślny
, bezparametrowy (tj. nie mający żadnych parametrów). Konstruktor domyślny
"nic nie robi" (jego ciało jest puste), a wtedy niestatyczne pola klasy są
inicjowane według podanych w ich deklaracjach inicjatorów lub (gdy inicjatorów
brak) na gwarantowane wartości ZERO (0, false, null).
Jeśli zdefiniowaliśmy jakikolwiek konstruktor, to konstruktor bezparametrowy
NIE jest automatycznie dodawany i musimy go sami zdefiniować
Przypomnienie
|
Słowo kluczowe this w ciele konstruktorów i metod niestatycznych oznacza
referencję do danego obiektu (tzn. tego, który jest inicjowany przez konstruktor
lub tego na rzecz którego użyto metody).
Zwykle nie używamy this: odwołania do składowych klasy wewnątrz konstruktora lub metody są jednoznaczne – dotyczą tego obiektu (inicjowanego przez konstruktor lub na rzecz którego wywołano metodę). Np.
public void start() { setState(MOVING); }
=
public void start() { this.setState(Vehicle.MOVING); }
i
public int getState() { return state; }
=
public int getState() { return this.state; }
|
Czasem musimy użyć słowa this.
1. Przy przesłonięciu nazw zmiennych oznaczających pola.
Np.:
class Vehicle {
private int width, height, length, weight;
private Person owner = null;
private int state;
public Vehicle(Person owner, int width, int height, int length, int weight) {
this.owner = owner; this.width = width; this.height = height;
this.length = length; this.weight = weight;
}
2. Drugi - częsty - przypadek koniecznego użycia słowa this występuje
wtedy, gdy metoda musi zwrócić TEN obiekt (na rzecz którego została wywołana).
Możemy np. zdefiniować metodę repair() tak, by zwracała ten pojazd (który został nareperowany)
Vehicle repair() {
// ...
return this;
}
i pisać szybko:
Vehicle v .... ;
...
v.repair().start();
3. Trzeci przypadek użycia słowa this - to wywołanie z konstruktora innego konstruktora (o czym mowa była przed chwilą).
1.7. Pełny przykład klasy Vehicle i jej wykorzystania
public class Vehicle {
public final static int BROKEN = 0, STOPPED = 1, MOVING = 2; // stany
private final static String[] states = { "ZEPSUTY",
"STOI",
"JEDZIE" }; // nazwy stanów
private static int count; // liczba dotąd utworzonych obiektów,
// inicjalnie 0
private int currNr = ++count; // przy tworzeniu obiektu count zwiększa się o 1
// i staje się numerem pojazdu
private int width, height, length, weight; // atrybuty pojazdu
private Person owner; // ... właściciel
private int state = STOPPED; // ... stan
public Vehicle() { } // konstruktor bezparametrowy
// konstruktor; tworzy pojazdy bez właściciela
public Vehicle(int w, int h, int l, int ww) {
this(null, w, h, l, ww);
}
// pełny konstruktor
public Vehicle(Person p, int w, int h, int l, int ww) {
owner = p; width = w;
height = h; length = l;
weight = ww;
}
public void start() { // start pojazdu
setState(MOVING);
}
public void stop() { // zatrzymanie pojazdu
setState(STOPPED);
}
// Robocza metoda zmiany stanu, wykorzystywana w stop() i start()
// Uwzględnia możliwe zmiany stanów (sprawdza to)
// Jest prywatna: arbitralna zmiana stanu pojazdu
//nie jest możliwa spoza klasy
private void setState(int newState) {
if (state == newState || state == BROKEN)
System.out.println("Nie jest mozliwe przejscie ze stanu "
+ states[state] + " do stanu " + states[newState]);
else state = newState;
}
// Reperowanie pojazdu:
// zawsze zakończone sukcesem, jeśli tylko można przystąpić do naprawy :-)
public Vehicle repair() {
if (state == MOVING)
System.out.println("Nie można reperować jadącego pojazdu");
else if (state != BROKEN) System.out.println("Pojazd sprawny");
else state = STOPPED;
return this;
}
// zwraca stan jako liczbę;
// można ją porównać ze stałymi Vehicle.BROKEN, Vehicle.STOPPED itp.
public int getState() { return state; }
// Zwraca nazwę stanu podanego jako argument
public static String getState(int state) { return states[state];}
// czy pojazd stoi?
public boolean isStopped() { return state == STOPPED; }
// Kolizja: w wyniku kolizji oba pojazdy w niej uczestniczące
//stają się niesprawne
// Nie może być kolizji, jeśli oba pojazdy stoją
public void crash(Vehicle v) {
if (state != MOVING && v.state != MOVING)
System.out.println("Nie ma kolizji");
else {
setState(BROKEN);
v.setState(BROKEN);
}
}
public void sellTo(Person p) { // sprzedaż pojazdu osobie p
owner = p;
}
public int getNr() { return currNr; } // zwraca unikalny numer pojazdu
// Czy pojazd może przejechać pod konstrukcją o podanej wysokości?
public boolean isTooHighToGoUnder(int limit) {
return height > limit ? true : false;
}
// Metoda toString uwzględnia teraz,
// że pojazd może jeszcze nie mieć właściciela
public String toString() {
String s = (owner == null ? "sklep" : owner.getName());
return "Pojazd " + currNr + " ,właścicielem którego jest "
+ s + " - " + states[state];
}
// ile pojazdów utworzyliśmy dotąd?
static int getCount() { return count; }
// zwraca tablicę nazw dostępnych stanów pojazdu
static String[] getAvailableStates() {
return states;
}
}
class VehTest { // klasa testująca
// Metoda raportująca stan tablicy pojazdów
// przekazanych jako argument
static void report(String msg, Vehicle[] v) {
System.out.println(msg);
for (int i=0; i < v.length; i++)
System.out.println("" + v[i]);
}
public static void main(String[] args) {
System.out.println("Dopuszczalne stany pojazdow");
String[] states = Vehicle.getAvailableStates();
for (int i = 0; i < states.length; i++) System.out.println(states[i]);
System.out.println("W programie mamy teraz pojazdow: " + Vehicle.getCount());
Vehicle[] v = { new Vehicle(new Person("Jan Piesio", "010268246"),
200, 150, 500, 900),
new Vehicle(new Person("Stefan Kot", "010262241"),
210, 250, 800, 1900),
new Vehicle(200, 230, 300, 600),
};
System.out.println("W programie mamy teraz pojazdow: " + Vehicle.getCount());
report("Na początku", v);
System.out.println("Pojazd 3 zostaje sprzedany Ambrożemu");
v[2].sellTo(new Person("Ambroży", "000000"));
for (int i=0; i < v.length; i++) v[i].start();
report("Po wyruszeniu", v);
// Czy mogą przejechać pod wiaduktem 220 cm?
for (int i=0; i < v.length; i++)
if (v[i].isTooHighToGoUnder(220)) v[i].stop();
report("Komu udało się przejechać pod wiaduktem ?", v);
// Zatrzymane wyruszaja w objazd
for (int i=0; i < v.length; i++)
if (v[i].isStopped()) v[i].start();
report("Po wyruszeniu w objazd", v);
// Kolizja
v[0].crash(v[1]);
report("Po kolizji", v);
System.out.println("Piesio próbuje ruszyć");
v[0].start();
System.out.println("Piesio sprzedaje pojazd Krówce. Ta go reperuje i jedzie");
v[0].sellTo(new Person("Anna Krówka", "121212908"));
v[0].repair().start();
report("Ostatecznie",v);
System.out.println("Stan pojazdu 3 " + Vehicle.getState(v[2].getState()));
}
}
Po uruchomieniu programu otrzymamy wydruk:
Dopuszczalne stany pojazdow
ZEPSUTY
STOI
JEDZIE
W programie mamy teraz pojazdow: 0
W programie mamy teraz pojazdow: 3
Na początku
Pojazd 1 ,właścicielem którego jest Jan Piesio - STOI
Pojazd 2 ,właścicielem którego jest Stefan Kot - STOI
Pojazd 3 ,właścicielem którego jest sklep - STOI
Pojazd 3 zostaje sprzedany Ambrożemu
Po wyruszeniu
Pojazd 1 ,właścicielem którego jest Jan Piesio - JEDZIE
Pojazd 2 ,właścicielem którego jest Stefan Kot - JEDZIE
Pojazd 3 ,właścicielem którego jest Ambroży - JEDZIE
Komu udało się przejechać pod wiaduktem ?
Pojazd 1 ,właścicielem którego jest Jan Piesio - JEDZIE
Pojazd 2 ,właścicielem którego jest Stefan Kot - STOI
Pojazd 3 ,właścicielem którego jest Ambroży - STOI
Po wyruszeniu w objazd
Pojazd 1 ,właścicielem którego jest Jan Piesio - JEDZIE
Pojazd 2 ,właścicielem którego jest Stefan Kot - JEDZIE
Pojazd 3 ,właścicielem którego jest Ambroży - JEDZIE
Po kolizji
Pojazd 1 ,właścicielem którego jest Jan Piesio - ZEPSUTY
Pojazd 2 ,właścicielem którego jest Stefan Kot - ZEPSUTY
Pojazd 3 ,właścicielem którego jest Ambroży - JEDZIE
Piesio próbuje ruszyć
Nie jest mozliwe przejscie ze stanu ZEPSUTY do stanu JEDZIE
Piesio sprzedaje pojazd Krówce. Ta go reperuje i jedzie
Ostatecznie
Pojazd 1 ,właścicielem którego jest Anna Krówka - JEDZIE
Pojazd 2 ,właścicielem którego jest Stefan Kot - ZEPSUTY
Pojazd 3 ,właścicielem którego jest Ambroży - JEDZIE
Stan pojazdu 3 JEDZIE