Warning
This page is located in archive.

3 - Polymorfismus, delegace, enum a šablony

  • pro vyučující: 03

Náplň cvičení

Pokračujme na příkladu z minulé hodiny. Osobák vozí různé typy kol, ale na každý typ kola potřebuje speciální držák. Kapacita vozu je 4 kola.

Úkol 1.

Navrhněte třídu držák, ta bude držet instanci daného typu kola (delegace).

  • BicycleHolder
  • MountainBikeHolder extends BicycleHolder
  • RoadBikeHolder extends BicycleHolder

Nápověda

public class MountainBikeHolder extends BicycleHolder {
 
	public MountainBikeHolder(MountainBike bicycle) {
		super(bicycle);
	}
}

Úkol 2.

Navrhněte vozidlo Car, které bude přijímat kola různých typů a dle jejich typu bude vybírat vhodný držák, a to podobně jako jako se kola předávala do servisu (v minulém cvičení), tedy metodami:

  • accept(Bicycle)
  • accept(MountainBike)
  • accept(RoadBike)

Kola se ve vozidle budou ukládat jen s pomocí držáku a to do seznamu/listu:

List<BicycleHolder> carHolders = new ArrayList<BicycleHolder>(4);

Nápověda

Generalizujte visitor pro auto i opravnu přes rozhraní Visitor, upravte kola ať je může navštívit toto rozhraní

public interface BicycleVisitor {
	public void accept(Bicycle b);
	public void accept(MountainBike b);
	public void accept(RoadBike b);
} 
public class Bicycle {
..
	public void visit(BicycleVisitor servis) {
		servis.accept(this); // i v dalsich tridach
	}
}

Úkol 3.

Každé kolo bude mít danou barvu, evidujme jen dobře definovaný počet barev výčtovým typem. Výčtovému typu přidejme pro každou barvu též atributy: název barvy a identifikační číslo.

Úkol 4.

Navrhněte generickou třídu AnyHolder<T>, která odstraní hierarchii z úkolu 1. Použijte ji ve druhém autě AnyHolderCar (původní ne-generickou implementaci ponechte, ať máte srovnání před očima).

Řešení

Řešení celého cvičení ke stažení: zde

Pomůcky

ENUM

Pro účely karetních her zkusme porovnat reprezentaci barev karet pomocí konstant a pomocí enum. Všechny úkoly zkuste nejprve pomocí konstant:

  1. Vytvořte třídu karta, která bude mít konstruktor obsahující hodnotu a barvu karty,
  2. bude obsahovat metodu toString, která textově vypíše hodnotu a barvu karty.

A nyní pomocí ENUM, pro načtení hodnot můžete použít static import. Napište metody pro výpis a výpis v angličtině (diamonds (♦), spades (♠), hearts (♥) and clubs (♣)). Kde jsou nebezpečí používání konstant?

import java.util.*;
 
public class Card {
    public enum Rank { DEUCE, THREE, FOUR, FIVE, SIX,
        SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE }
 
    public enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES }
 
    private final Rank rank;
    private final Suit suit;
    private Card(Rank rank, Suit suit) {
        this.rank = rank;
        this.suit = suit;
    }
 
    public Rank rank() { return rank; }
    public Suit suit() { return suit; }
    public String toString() { return rank + " of " + suit; }
 
    private static final List<Card> protoDeck = new ArrayList<Card>();
 
    // Initialize prototype deck
    static {
        for (Suit suit : Suit.values())
            for (Rank rank : Rank.values())
                protoDeck.add(new Card(rank, suit));
    }
 
    public static ArrayList<Card> newDeck() {
        return new ArrayList<Card>(protoDeck); // Return copy of prototype deck
    }
}

Enum jak ho neznáme

Řekněme, že chceme pracovat s elementy, které je možné řadit vzestupně nebo sestupně. Mějme následující výchozí Enum:

public enum Ordering {
    ASCENDING,
    DESCENDING;
}

Atributy

Enum může mít atributy, stejně jako jakákoliv jiná třída. Tyto atributy lze nastavit v konstruktoru, který lze definovat. Deklarace jednotlivých prvků jsou vlastně volání konstruktoru (akorát když žádný není, vynecháme závorky) a lze tak předat atributům hodnoty. Uložme si pro každé seřazení jeho zkratku:

public enum Ordering {
    ASCENDING("ASC"),
    DESCENDING("DESC");
 
    public final String acronym;
 
    private Ordering(String acronym) {
        this.acronym = acronym;
    }
}
Atribut můžete použít stejně jako u jakékoli jiné třídy, např.:
Ordering[] ords = Ordering.values();
for (Ordering o : ords) {
    System.out.println(o.acronym + " - " + o);
}
Produkuje:
ASC - ASCENDING
DESC - DESCENDING

Metody

Enum může mít metody, stejně jako jakákoliv jiná třída. A jelikož i Enum je potomkem třídy Object (stejně jako všechny třídy v javě), ukažme to na metodě toString():

public enum Ordering {
    ASCENDING("ASC"),
    DESCENDING("DESC");
 
    public final String acronym;
 
    private Ordering(String acronym) {
        this.acronym = acronym;
    }
 
    @Override
    public String toString() {
        return this.name() + "(" + acronym + ")";
    }
}
Následující kód
for (Ordering o : Ordering.values()) {
    System.out.println(o);
}
pak produkuje
ASCENDING(ASC)
DESCENDING(DESC)

Rozhraní

Stejně jako jakákoliv jiná třída, i Enum může implementovat rozhraní (interface). Implementujme na našem Ordering rozhraní java.util.Comparator pro Integer. Máme dvě možnosti.

1. Speciální implementace pro každý prvek

Každý prvek si implementuje vlastní verze metod rozhraní:

public enum Ordering implements Comparator<Integer> {
    ASCENDING {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o1.compareTo(o2);
        }
    },
    DESCENDING {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2.compareTo(o1);
        }
    };
}
Ukázkový kód:
List<Integer> elementsBase = Arrays.asList(3, 1, 40, 42, 42, 51, -134, 0, 0);
List<Integer> sortedAsc = new ArrayList<Integer>(elementsBase);
Collections.sort(sortedAsc, Ordering.ASCENDING);
List<Integer> sortedDesc = new ArrayList<Integer>(elementsBase);
Collections.sort(sortedDesc, Ordering.DESCENDING);
System.out.println(sortedAsc);
System.out.println(sortedDesc);
Produkuje následující výstup:
ascending: [-134, 0, 0, 1, 3, 40, 42, 42, 51]
descending: [51, 42, 42, 40, 3, 1, 0, 0, -134]

2. Jediná společná implementace s vnitřním rozhodováním na základě ''this''

Metody rozhraní jsou implementovány pouze jednou, na stejné úrovni jako např. konstruktor a až uvnitř nich se rozhodneme, co budeme dělat na základě toho, co je vlastně this:

public enum Ordering implements Comparator<Integer> {
    ASCENDING,
    DESCENDING;
 
    @Override
    public int compare(Integer o1, Integer o2) {
        switch (this) {
            case ASCENDING:
                return o1.compareTo(o2);
            case DESCENDING:
                return o2.compareTo(o1);
        }
        throw new IllegalStateException(); // we can never reach this code but without it
                                           // compiler complains that the method might not
                                           // return anything
    }
}
Chová se totožně jako předchozí varianta. Nicméně, tento postup se nedoporučuje. Navíc ani není moc hezký (nedosažitelná výjimka na konci) a není to správně objektové.

Ještě jedna poznámka k metodám

Metody samotné lze také implementovat pro každý prvek zvlášť, jako jsme viděli u rozhraní. Náš toString() by tedy mohl vypadat i takto:

public enum Ordering {
    ASCENDING {
        @Override
        public String toString() {
            return this.name() + "(ASC)";
        }
    },
    DESCENDING {
        @Override
        public String toString() {
            return this.name() + "(DESC)";
        }
    };
}
Kterou ze dvou variant použít, je otázka. Obecné pravidlo by se dalo vyložit asi takto: pokud je chování metody specifické pro každý prvek (dá se zhruba říct i jako “bylo by třeba dělat switch(this)”), je vhodné ji implementovat pro každý prvek zvlášť. Pokud je chování pro všechny prvky víceméně shodné (či “není třeba dělat switch(this)”), je vhodné ji implementovat na úrovni Enumu.

Další studium

Pro maximální porozumění (nejen) Enumům doporučuji Chapter 6, Items 30-34 z knihy Effective Java, 2nd Edition.

Generika

Generika umožňují vytvářet třídy a metody “on demand” parametrizované nějakou jinou třídou (ale nikoliv např. číslem, což je možné např. v C++, kde se to nazývá “templates” neboli šablony).

Generická třída a rozhraní

Jednoduchý příklad:

/**
 * Generic version of the Box class.
 * @param <T> the type of the value being boxed
 */
public class Box<T> {
    // T stands for "Type"
    private T t;
 
    public void set(T t) { this.t = t; }
    public T get() { return t; }
}
A zde volání
Box<Integer> integerBox = new Box<Integer>();
Jiný a lepší příklad
public interface Pair<K, V> {
    public K getKey();
    public V getValue();
}
 
public class OrderedPair<K, V> implements Pair<K, V> {
 
    private K key;
    private V value;
 
    public OrderedPair(K key, V value) {
	this.key = key;
	this.value = value;
    }
 
    public K getKey()	{ return key; }
    public V getValue() { return value; }
}
inicializace OrderedPair class:
Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8);
Pair<String, String>  p2 = new OrderedPair<String, String>("hello", "world");
Java 7 zkrátí na
OrderedPair<String, Integer> p1 = new OrderedPair<>("Even", 8);
OrderedPair<String, String>  p2 = new OrderedPair<>("hello", "world");
Pro ty co se neztratili
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));

Generická metoda

Nemusí být generická celá třída, generickou lze udělat i samotnou metodu:

public class ListCreator {
    public static <T> List<T> createList(T ... elements) {
        List<T> list = new ArrayList<T>(elements.length);
        for (T element : elements) {
            list.add(element);
        }
        return list;
    }
}
Ono <T> před signaturou metody je ekvivalentní stejnému <T> za názvem třídy, akorát negeneralizujeme celou třídu, ale samotnou metodu.

Když generické metody voláme, můžeme buď nechat typ odvodit:

List<Integer> ints = ListCreator.createList(1, 3);
System.out.println(ints);
System.out.println("Types: " + ints.get(0).getClass().getSimpleName() +
        ", " +ints.get(1).getClass().getSimpleName());
List<String> strings = ListCreator.createList("ab", "cd");
System.out.println(strings);
System.out.println("Types: " + strings.get(0).getClass().getSimpleName() +
        ", " +strings.get(1).getClass().getSimpleName());
Produkuje
[1, 3]
Types: Integer, Integer
[ab, cd]
Types: String, String
Nebo jej ručně specifikovat voláním ve tvaru:
List<Integer> ints = ListCreator.<Integer>createList(1, 3);
Zde ale postrádá smysl, protože není jiná možnost než tato.

Ve zbylém čase, nebo práce na doma

Adresář

  • Vytvořte si telefonní seznam obsahující položky ve formátu jméno, příjmení, telefonní číslo, email, ulice, číslo popisné, město, PSČ.
  • Pro snazší implementaci použijte jako datovou strukturu třídu ArrayList. Použijte generiku a najděte výhody tohoto přístupu. Dodržujte zapouzdření a přístup k datům umožněte pouze přes rozhraní své třídy.
  • Vytvořte metody pro vložení záznamu, odebrání záznamu a zjištění, zda tam daný záznam je.
courses/a0b36pr2/labs/lab03.txt · Last modified: 2016/03/10 16:00 by mudromar