IMP – Uitwerking week 13 Opgave 1 Nee. Anders moet bijvoorbeeld een venster applicatie een subklasse zijn van zowel Frame en WindowListener. Als de applicatie ook een button of een menu heeft, dan moet het ook een subklasse van ActionListener. In Java is dit niet mogelijk: een klasse kan slechts ´e´en (directe) superklasse hebben. Een klasse kan echter meerdere interfaces implementeren.
Opgave 2 (a) De klasse StdFrame is een eigenlijk een Frame met een eenvoudige implementatie van de interface WindowListener, namelijk die de applicatie sluit bij een venstersluit event. Als je meerdere venster applicaties hebt (dwz, meerdere subklassen van Frame) die WindowListener op diezelfde wijze implementeren, dan is het handig om ze een subklasse van StdFrame te maken; want dan krijg je natuurlijk de implementatie van WindowListener zoals in StdFrame ge¨erfd. (b) Nee. Je kunt het niet eens als een interface defini¨eren, want in een interface kun je de body van een methode niet defini¨eren. Je kunt alleen de header van methoden defini¨eren. (c) Dit is afhankelijk van hoe je StdFrame wilt gebruiken. Als de bedoeling van StdFrame slechts om een bepaalde default implementatie van een interface te defini¨eren is, dan zou je in de praktijk StdFrame altijd moeten uitbreiden (bijvoorbeeld om de paint van Frame te herdefini¨eren). In dit geval is het zinnig om StdFrame als een abstract klasse te declareren.
Opgave 3 (a) Hoeveel klassen je nodig hebt is afhankelijk van je ontwerp, maar in dit geval lijkt het redelijk om minimaal Aap, Krokodil, en Banaan als klassen te hebben. Daarnaast wil je misschien een paar superklassen introduceren, zodat klassen variabelen en methoden van zijn supperklassen kunnen erven (en dus code duplicatie verminderen). Hier is een mogelijk ontwerp:
In dit ontwerp is het mogelijk om bijvoorbeeld de methode slaap slechts een keer in Dier te defini¨eren; Aap en Krokodil erven het gewoon van Dier. Echter, krijgen alle dieren (dus ook krokodillen) in dit ontwerp alle methoden van Voer. Dit is minder mooi, en zelfs misschien ongewenst, omdat in dit verhaal een krokodil geen voer is. We zullen straks een paar alternatieven zien. (b) Voor het bovenstaande ontwerp (ge¨erfde methoden worden niet genoemd):
1
• klasse DrawKlasse: – public void draw (Graphics g) • klasse Voer: – public int calorieWaarde () • klasse Banaan: geen • klasse Dier: – public void slaap() • klasse Aap: – public void eten(Banaan v) • klasse Krokodil: – public void eten(Banaan v) – public void eten(Aap v) – public void zwem() (c) Het staat al in het diagram in (a). (d) Als de applicatie eigenlijk alleen maar apen, krokodillen, of bananen cre¨eert, dan kunnen we ook Dier en Voer abstract maken. We kunnen ook kiezen om Voer als een interface te defini¨eren:
Let op dat in dit ontwerp is een krokodil geen voer meer. Echter van Voer kunnen Banaan en Aap nu slechts de headers van de methoden van Voer ’erven’. De definitie van een methode kan uit een interface niet worden ge¨erfd (omdat een interface geen volledige definitie van methoden aanbiedt). Een ander alternatief is het volgende:
2
Hier blijft Voer een klasse, maar het geen superklasse (of subklasse) meer van Dier. Dit is goed voor Krokodil, maar niet voor Aap, omdat in dit verhaal een aap ook een voer is. Om dit op te lossen wordt een nieuwe klasse AapV toegevoegd die het voer gedrag van een aap implementeert. In een Aap object voegen wij een variabele (bijvoorbeeld eetmij) die een AapV object bevat. Dus via deze variabele kunnen we achterhalen hoe een aap zich gedraagt als voer.
Opgave 4 (a) In deze oplossing wordt voor de volgende indeling van klassen gekozen:
De variabelen naam en inwoners zijn aanwezig in alle klassen in het originele ontwerp. Daarom introduceren we in het nieuwe ontwerp een klasse Regio en verplaatsen we deze variabelen naar Regio. Omdat Land, Stad, en HoofdStad nu subklassen van Regio krijgen ze nu gratis deze variabelen mee. De constructoren van Stad en HoofdStad doen exact dezelfde. We kunnen hun gedrag nemen als de definitie van de constructor van Regio; dus krijgen Stad en HoofdStad deze (bijna) gratis —zie de code onder. De methode code was aanwezig en identiek in Stad en HoofdStad. Deze verplaatsen we naar Stad; dus krijgt HoofdStad deze nu gratis. De methode totInwoners is in HoofdStad en Land aanwezig, maar zij doet in de ene klasse anders dan in de andere. Er is dus geen sprake van code duplicatie. Je kunt ze zo laten. Echter, met het oog op opgave (b) introduceren we ook een interface HeeftTotInwoners met totInwoners als zijn (enige) methode. Nu kun je bijvoorbeeld een array van HeeftTotInwoners objecten hebben; van deze objecten weet je dus dat ze de methode totInwoners hebben. abstract public class Regio{ public String naam; // naam van de regio public int inwoners; // aantal inwoners public Regio(String n, int i){naam=n; inwoners=i;} } import java.util.*; public class Land extends Regio implements HeeftTotInwoners { LinkedList<Stad> steden; // de steden in het land HoofdStad hstad; public Land(String n, int i, HoofdStad h){ super(n,i); hstad=h; steden = new LinkedList<Stad>(); voegStad(h); 3
} // aanname: s is niet in steden public void voegStad(Stad s){ steden.add(s); s.land = this; inwoners = inwoners + s.inwoners; } public int totInwoners(){ return inwoners; } } public class Stad extends Regio { public Land land;
// zoals Nederland is het land waar Utrecht is
public Stad(String n, int i){ super(n,i); } public String code(){ return naam.substring(0,3); } } public class HoofdStad
extends Stad implements HeeftTotInwoners {
public HoofdStad(String n, int i){ super(n,i); } // Retourneer het aantal inwoners van het land waarvan // deze stad de hoofdstad is. // Aanname: land is niet null. public int totInwoners() { return land.inwoners; } }
(b)
public class InwonersApp { static public void printTotInwoners(HeeftTotInwoners[] w){ int s; for (int i=0; i<w.length; i++) { s = w[i].totInwoners(); if (s>=0) System.out.print("" + s + "\n"); else System.out.print("-\n"); }; } // een demo van deze applicatie public static void main(String[] args){ Stad utrecht = new Stad("Utrecht",100000); Stad delft = new Stad("Delft",100000); HoofdStad ams = new HoofdStad("Amsterdam",1000000); Land nl = new Land("Nederland",19000000,ams); nl.voegStad(utrecht); nl.voegStad(delft);
4
HeeftTotInwoners[] w = new HeeftTotInwoners[2]; w[0] = ams; w[1] = nl; printTotInwoners(w); } }
5