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 .

rys


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ć:


Po co jest prywatność? 

1. Ochrona przed zepsuciem (pola powinny być prywtane) 2.  Zapewnienie właściwego interfejsu  (metody "robocze" winny być prywatne) 3. Ochrona przed konsekwencjami zmiany implementacji


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.

rys


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:
  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:

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):


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:

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:
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:
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.

r


Tak samo możemy mieć kilka wersji konstruktorów:

r


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