Vytvárame funkčné triedy

<< Vytvárame prvé triedy | Obsah | Dedičnosť >>

Predchádzajúce kapitoly nám rôznorodým spôsobom pomohli vytvoriť objekty typu Dvd a nastaviť im hodnoty inštančných premenných. Po vytvorení týchto objektov sme si uložili referencie na ne do premenných typu Dvd.

public class SkusanieDvd {
  public static void main(String[] args) {
    ...
   Dvd matrix = ...
   ...
   Dvd shawshank = ...
   ...
   Dvd fontana = ...
   ...
   Dvd pacho = ...
   ...
  }
}

Mať každé DVD v jednej premennej je veľmi nepraktické, preto by sme potrebovali dať tieto objekty radšej do poľa Dvdčiek.

public class SkusanieDvd {
  public static void main(String[] args) {
    ...
   Dvd[] filmy = new Dvd[4];
   filmy[0] = matrix;
   filmy[1] = shawshank;
   filmy[2] = fontana;
   filmy[3] = pacho;
   ...
  }
}

Teraz by sme už vedeli napríklad jednoduchým cyklom vypísať všetky filmy v našej zbierke. Pokiaľ by nám išlo iba o uloženie dát tak sme hotoví. Zadanie sa však skladá z dvoch množín požiadaviek:

  • s akými dátami bude náš program pracovať
  • aké služby má poskytovať resp. akú funkcionalitu má mať.

Pozrime sa teraz na druhú množinu požiadaviek - funkcionalitu. V našom zadaní sú nasledovné požiadavky na funkcionalitu.

  • vložiť nové DVD
  • vymazať DVD (napríklad sa poškodilo alebo stratilo)
  • vypísať všetky filmy vo vašej zbierke
  • vypísať tie filmy, ktoré zodpovedajú danému žánru (napr. komédie)
  • vypísať tie filmy, ktoré sa dajú pozrieť do nejakého času (napr. < 90 minút)
  • vypísať všetkých filmy, kde hral daný herec
  • vypísať filmy, ktoré sú podľa vášho hodnotenia na stupnici od 7 do 10.

Ako pretaviť tieto požiadavky do kódu? Z každej požiadavky na funkcionalitu programu sa stane metóda, ktorú zavoláme, keď budeme chcieť vykonať niektorú z požadovaných akcií.

V Jave musí každá metóda bývať v nejakej triede. Už sme sa naučili, že je dobrým zvykom dávať main metódu do samostatnej triedy (nazvanej napríklad Spustac), kde sa nič iné ako metóda main nenachádza. Takže do tejto triedy ich nedáme.

Do triedy Dvd je ich tiež nevhodné dávať z toho dôvodu, že ľubovoľné Dvd by malo schopnosti spravovať všetky ostatné DVD-čka, vznikla by anarchia. Naviac Dvd nemá potrebné dáta z iných DVD-čiek a museli by sme mu ich stále dodať. Spomínané požiadavky funkcionality nášho programu nie sú požiadavky, ktoré by malo spĺňať každé DVD-čko, ale mal by ich spĺňať akýsi správca či inteligentný zoznam DVD-čiek.

Zapúzdrenosť

Zapúzdrenosť je základné pravidlo OOP, ktoré by sa malo dodržiavať. Zapúzdranosť nám hovorí, že všetky dôležité dáta by mali mať svojho zodpovedného správcu, ktorý má plnú kontrolu nad obsahom týchto dát.

Inštančné premenné majú svojho správcu - triedu, ktorá jediná má právo pristupovať k svojim premenným pomocou svojich metód. Lepšie povedané objekty, t.j. inštancie tried, sú zodpovedné za obsah svojich premenných a iba oni by mali mať v dobrom návrhu programu plnú kontrolu nad zmenami obsahu svojich inštančných premnných.

V našom príklade predstavuje dôležité dáta pole DVD-čiek. Vyrobíme teda tomuto poľu jeho správcu, triedu ZoznamDvd, ktorá bude zodpovedná za všetky zmeny v tomto poli a tiež bude poskytovať vyhľadávacie služby nad týmto poľom.

Keďže všetky metódy zo zadania pracujú hlavne s poľom DVD-čiek, všetky umiestnime do triedy, ktorá toto pole spravuje. Na začiatok si napíšeme prvý nástrel hlavičiek týchto metód:

public class ZoznamDvd {
  private Dvd[] filmy;

  public ZoznamDvd() {
    filmy = new Dvd[0]; //na začiatku máme nula filmov, ale máme istotu, že v premnnej filmy nebude null
  }

  public void vlozNoveDvd(Dvd dvd) {
  }

  public void vymazDvd(Dvd dvd) {
  }

  public void vypisVsetko() {
  }

  public void vypisPodlaZanru(String zaner) {
  }

  public void vypisPodlaCasu(int maximalnyCas) {
  }

  public void vypisPodlaHerca(String menoHerca) {
  }

  public void vypisPodlaHodnotenia(double odHodnota, double doHodnota) {
  }
}

Poďme si doplniť telá našich metód. Prvou metódou je vlozNoveDvd. Budeme klasicky nafukovať pole tak, že vytvoríme väčšie pole do ktorého pôvodné skopírujeme a pridáme nový prvok.

public class ZoznamDvd {
  ...
  public void vlozNoveDvd(Dvd dvd) {
    Dvd[] novePole = new Dvd[filmy.length+1];
    System.arraycopy(filmy,0,novePole,0,filmy.length);
    filmy = novePole;
    filmy[filmy.length-1] = dvd;
  }
  ...
}

Metódae vymazDvd má opačný postup, teda zmenšuje pole.

public class ZoznamDvd {
  ...
  public void vymazDvd(Dvd dvd) {
    int indexDvd = -1;
    for (int i = 0; i < filmy.length; i++) {
      if (dvd.equals(filmy[i])) {
        indexDvd = i;
        break;
      }
    }
    if (indexDvd >= 0) { // ak sme našli dané DVD-čko
      Dvd[] novePole = new Dvd[filmy.length-1];
      System.arraycopy(filmy, 0, novePole, 0, indexDvd); //skopírujeme všetky prvky naľavo od indexDvd
      System.arraycopy(filmy, indexDvd+1, novePole, indexDvd, novePole.length - indexDvd); //skopírujeme všetky prvky napravo od indexDvd
      filmy = novePole;
    }
  }
  ...
}

Na tejto metóde je blbé to, že má na vstupe referenciu na inštanciu Dvd, ktorá sa má vymazať. My však od používateľa nášho programu dostaneme skôr názov filmu. Vyrobme si teda inú verziu tejto metódy, ktorá má na vstupe názov filmu. Budeme mať teda dve metódy s rovnakým názvom t.j. preťažené metódy.

Preťažené metódy

Preťažené metódy sa od seba musia líšiť

  • počtom parametrov,
  • alebo ak majú rovnaký počet parametrov, musí sa líšiť typ aspoň jedného z parametrov.

Pozor! Neplatí, že stačí že sa líšia návratové typy metód. Musia sa líšiť typy vstupných premenných. Napríklad nasledovné sa nedá skompilovať, lebo nie je možné na základe typov vstupných parametrov rozhodnúť, ktorá z týchto metód sa má zavolať:

...
public int vypocet(int vstup1, double vstup2){
  ...
}

public double vypocet (int prva, double druha) {
  ...
}
...

Preťažené metódy sa môžu líšiť aj poradím parametrov ak sa na nejakej pozícii líšia typy, v praxi sa to však neodporúča.

Vráťme sa k nášmu príkladu korektne preťaženej metódy vymazDvd. Na skracovanie poľa využijeme už hotovú metódu s rovnakým názvom, ale iným typom parametra.

public class ZoznamDvd {
  ...
  public void vymazDvd(String nazovFilmu) {
    for (int i = 0; i < filmy.length; i++) {
      if (nazovFilmu.equals(filmy[i].getNazovFilmu())) {
        this.vymazDvd(filmy[i]);
      }
    }          
  }
  ...
}

Nasleduje plejáda metód ktoré majú vypisovať filmy, ktoré spĺňajú nejakú podmienku. Povedzme, že pri výpise chceme o každom DVD-čku vypísať nielen názov filmu, ale aj ostatné parametre: dĺžku, žánre, hercov aj hodnotenie. Opäť si spomenieme na princíp zapúzdrenosti. O premenné nazovFilmu, dlzkaFilmu a tak ďalej sa starajú objekty triedy Dvd. Naučíme teda triedu Dvd nech nám vráti reťazec, ktorý budeme chcieť vypísať pri výpise DVD-čiek.

public class Dvd {
...
  String retazecPreVypis() {
    String vystup = nazovFilmu + "\n";
    vystup = vystup + dlzkaFilmu + " minút\n";
    vystup = vystup + "žánre: ";
    for (int i = 0; i < zanre.length; i++) {
      vystup = vystup + zanre[i] + ", ";
    }
    vystup = vystup + "\n" + "herci: ";
    for (int i = 0; i < herci.length; i++) {
      vystup = vystup + herci[i] + ", ";
    }
    vystup = vystup + "hodnotenie: " + hodnotenie + "\n";
    return vystup;
  }
...
}

poznámka pre optimalizáciu: namiesto zreťazovania je vhodnejšie používať triedu StringBuilder.

Keď už sme triedu Dvd naučili vytvárať výpis o sebe, metóda na výpis všetkých DVD-čiek je už malina.

public class ZoznamDvd {
  ...
  public void vypisVsetko() {
    for (int i=0; i < filmy.length; i++) {
      System.out.println(filmy[i].retazecPreVypis());
    }
  }
  ...
}

V metóde vypisPodlaZanru by sme o každom DVD-čku potrebovali zistiť, či daný žáner obsahuje. Túto robotu opäť necháme na triedu Dvd keďže ide o jej pole.

public class Dvd {
...
  boolean mamZaner(String zaner) {
    for (int i = 0; i < zanre.length; i++) {
      if (zaner.equals(zanre[i]))
        return true;
      }
    return false;
  }
...
}

V triede ZoznamDvd to potom bude vyzerať nasledovne:

public class ZoznamDvd {
  ...
  public void vypisPodlaZanru(String zaner) {
    for (int i=0; i < filmy.length; i++) {
      if (filmy[i].mamZaner(zaner))
        System.out.println(filmy[i].retazecPreVypis());
    }
  }
  ...
}

Posledné tri metódy zo zadania sú už iba ľahkým cvičením využívajúcim naučené veci, ktoré určite zvládnete aj sami.