Zadatak 011 Razmotrite sljedeći primjer. package hr.fer.oopj.primjeri.p011; public class Main { public static void main(string[] args) { obrada(2.7182818284590452354); private static void obrada(double d) { // Kako ograničiti pozivatelja da zada nešto // što nije 0, 1, PI ili E? System.out.println("Dobio sam broj: " + d+"."); Pretpostavimo da imamo metodu obrada(broj) koja kao argument smije primiti jednu od četiri konstante: 0, 1, e ili pi. Kako pozivatelju takve metode nametnuti takvo ograničenje? Prikazani primjer kao argument metode deklarira varijablu tipa double. Uz taj tip argumenta, pozivatelj metodi može poslati bilo kakav decimalni broj i prevoditelj neće prijaviti pogrešku. U metodi obrada tada bismo morali na početku imati provjeru je li vrijednost argumenta jedna od dozvoljenih pa ako nije, nešto učiniti (baciti iznimku ili slično). Imamo li više takvih metoda, kod provjere bismo morali ponavljati (ili izlučiti u novu metodu pa pozivati) više puta. Također, pogrešku bismo mogli utvrditi tek pri pokretanju programa, a ne već pri prevođenju. Jedno rješenje je definirati javni razred čiji primjerci predstavljaju pojedine konstante. Primjerak takvog razreda kroz konstruktor prima i u članskoj varijabli pamti vrijednost konstante koju predstavlja te nudi getter za dohvat vrijednosti. Kako nitko ne bi mogao mijenjati ponašanje primjeraka ovog razreda nasljeđivanjem, razred ćemo zaključati za nasljeđivanje (final class). Konstruktor ćemo definirati kao privatan tako da ga nitko izvana ne može pozivati a u definiciji razreda sami ćemo deklarirati nekoliko javnih statičkih konstanti. Evo koda. package hr.fer.oopj.primjeri.p011; public final class MathematicalConstant { // public constants: // ----------------- public static final MathematicalConstant PI = new MathematicalConstant(3.14159265358979323846); * Constant E (base of natural logarithm). public static final MathematicalConstant E = new MathematicalConstant(2.7182818284590452354); * First natural number. Neutral element for addition. public static final MathematicalConstant ZERO = new MathematicalConstant(0.0); * Second natural number. Neutral element for multiplication. public static final MathematicalConstant ONE = new MathematicalConstant(1.0);
// private final member variables: // ------------------------------- * The value of this constant. private final double value; * Constructor. * @param value constant's value public MathematicalConstant(double value) { super(); this.value = value; * Getter for value. * * @return value of this constant public double getvalue() { return value; Uočite: kako je konstruktor privatan, nitko ne može stvarati primjerke ovog razreda osim koda u tom razredu. Razred definira četiri statičke konstante i inicijalizira ih stvarajući jedina četiri primjerka tog razreda koja će ikada postojati u memoriji virtualnog stroja. Sada klijentski kod možemo prepisati tako da kažemo da kao argument prima referencu na jedan objekt razreda MathematicalConstant. Na taj način pozivatelj neće klijentu moći poslati ništa što nije jedna od postojećih konstanti (ili referenca null). Evo popravljenog koda. package hr.fer.oopj.primjeri.p011; public class Main { public static void main(string[] args) { obrada(mathematicalconstant.e); private static void obrada(mathematicalconstant e) { System.out.println("Dobio sam broj: "+ e.getvalue()); Da ne bismo sami morali pisati konačne razrede i eksplicitno definirati i stvarati statičke konstante, Java za to nudi pokratu: ključnu riječ enum kojom se definira enumeracija. Najjednostavniji primjer enumeracije dobivamo nabrajanjem imena statičkih konstanti koje bismo deklarirali pri pisanju odgovarajućeg razreda (iz upravo danog primjera). Tako je sljedeći kod: public enum X { A, B, C konceptualno zamjena za: public final class X { public static final X A = new X();
public static final X B = new X(); public static final X C = new X(); private X() { Međutim, enumeracija jest "posebna" vrsta razreda koji uz neka ograničenja može imati gotovo sve što može i klasičan razred: može imati konstruktore (ali modifikator ili ne smije pisati, ili može biti samo private), može imati nestatičke članske varijable (trebale bi biti final), može imati javne metode (primjerice, gettere za dohvat vrijednosti nestatičkih članskih varijabli). Svaka enumeracija implicitno nasljeđuje vršni razred java.lang.enum. Ako imamo definiran konstruktor koji prima parametre, onda pri deklaraciji svake od konstanti odmah otvaramo zagradu i predajemo parametre koji idu konstruktoru ne možemo eksplicitno pozivati konstruktor uporabom new. Matematičke konstante iz ovog zadatka, kao enumeraciju bismo definirali na sljedeći način. package hr.fer.oopj.primjeri.p011.b; public enum MathematicalConstant { // public constants: // ----------------- PI(3.14159265358979323846), * Constant E (base of natural logarithm). E(2.7182818284590452354), * First natural number. Neutral element for addition. ZERO(0.0), * Second natural number. Neutral element for multiplication. ONE(1.0); * The value of this constant. private final double value; * Constructor. * @param value constant's value private MathematicalConstant(double value) { this.value = value; * Getter for value. * * @return value of this constant public double getvalue() { return value; Pažljivo proučite kod; uočite da smo pojedine konstante razdvojili zarezom, a popis svih konstanti od ostatka razreda (deklaracije nestatičkih članskih varijabli, konstruktora i metoda) razdvojili znakom točka-zareza.
Usporedite u razredu MathematicalConstant redak: public static final MathematicalConstant PI = new MathematicalConstant(3.14159265358979323846); i u enumeraciji istovjetan redak: PI(3.14159265358979323846), Uočavate li koji su dijelovi koda ispali? Uz ovu modifikaciju, kod klijenta (metodu obrada) uopće ne treba mijenjati: ona i dalje očekuje kao argument referencu na objekt. Konstante enumeracije jesu objekti koji su stvoreni pozivom operatora new i možemo ih slati okolo. Vrijednost konstante PI sada bismo dobili ovako: double pi = MathematicalConstant.PI.getValue(); Također, konstante enumeracije ne moramo uspoređivati pozivom metode equals s obzirom da za svaku konstantu u memoriji virtualnog stroja postoji upravo jedan primjerak tog razreda (što jamči specifikacija jezika Java), možemo ih izravno uspoređivati operatorom ==. Isprobajte ovaj primjer on je priprema za sljedeći zadatak.
Zadatak 012 (zadatak s prošlogodišnjeg labosa) U okviru ovog zadatka potrebno je modelirati jednostavan cjelobrojni kalkulator. Kalkulator podržava rad s cjelobrojnim dekadskim brojevima. Nudi dvije operacije: zbrajanje i oduzimanje te omogućava: brisanje i dohvat rezultata. Kalkulator ima tri registra: display (broj na zaslonu), memory (memorija prethodnog broja) i operator (operacija). Dva kalkulatora su ista ako imaju iste sadržaje registara. Ispis objekta se svodi na ispis registara, u obliku "(D=*, M=*, O=*)", gdje se umjesto zvjezdica nalaze brojevi, odnosno simbol operacije (npr. "(D=12, M=15, O=+)"). Ako je neki registar prazan, ne mora se ispisivati. Kalkulator je modeliran sučeljem ICalculator. Za interakciju s korisnikom sučelje definira dvije porodice metoda: prvu porodicu čine metode pressdigit(znamenka), pressequals(), pressplus(), pressminus(), pressclear(), koje implementiraju funkcionalnosti pritisaka na pojedine tipke; znamenka je pri tome '0' do '9'. drugu porodicu čini općenita metoda press(gumb) koja prima referencu na apstraktni gumb (u nastavku ćemo pobliže objasniti) te s obzirom na vrstu predanog gumba poziva jednu od specifičnih metoda iz prve porodice. Kako bismo podržali općenitu metodu press(), potrebno je definirati apstraktni razred Button te iz njega naslijediti razrede DigitButton te OperationButton. Također, potrebno je definirati enumeraciju Digits čiji su članovi pojedine znamenke (ZERO, ONE,, NINE) i svaka konstanta nudi dohvat numeričke vrijednosti same znamenke (int podatak) te enumeraciju Operations čiji su članovi pojedine operacije (PLUS, MINUS, EQUALS, CLEAR). Primjerci razreda DigitButton trebaju imati člansku varijablu koja pohranjuje element enumeracije Digits, a primjerci razreda OperationButton trebaju imati člansku varijablu koja pohranjuje element enumeracije Operations. Sučelje ICalculator dodatno treba imati i metodu String getdisplay() koja vraća sadržaj zaslona kalkulatora. Prije početka rada na programu kalkulatora, pokušajte skicirati dijagram razreda predloženog rješenja na kojem bi bili vidljivi svi prethodno definirani razredi, sučelja i enumeracije. Napišite razred SimpleCalc koji implementira sučelje ICalculator i sadrži registre. Registre modelirajte razredom Register. Primjerci ovog razreda u kalkulatoru služe za memoriranje podataka. Kalkulator poznaje samo operacije "+" i "-" te naredbe "=" i "C". Ako se pritisne = prije nego što je zadana operacija, metoda press() uzrokuje završetak rada programa. U nastavku je dan detaljniji opis prethodno navedenih metoda: public String getdisplay() vraća sadržaj zaslona kalkulatora public void pressdigit( int ) dojavljuje kalkulatoru da je pritisnuta jedna od brojevnih tipki (dekadske znamenke od do "9") public void pressequals() pritisnut je "=" (rezultat). Treba obaviti operaciju spremljenu u O nad brojevima u D i M. Rezultat treba staviti u D, a M poništiti. public void pressplus() pritisnut je "+" (zbrajanje). D M, "+" O, poništi D. public void pressminus() pritisnut je "-" (oduzimanje) public void pressclear() pritisnut je "C" (brisanje cijele memorije)
public void pressbutton(button btn) dojavljuje kalkulatoru da je pritisnut predani gumb Razred Register predstavlja registar koji pamti jednu vrijednost. Može sadržavati neku vrijednost ili biti prazan. Vrijednost je jedan općeniti Object. Razred treba deklarirati i implementirati barem sljedeće metode: public /* odabrati tip povratne vrijednosti getvalue(): vrati pohranjenu vrijednost ili null ako je prazan. public void setvalue( /* potrebni parametri ): posprema vrijednost. public void clear(): isprazni se U nastavku je dan primjer korištenja implementiranog kalkulatora te su navedene povratne vrijednosti metoda getdisplay() i tostring() nakon pritiska svake pojedine tipke. Naredba SimpleCalc c = new SimpleCalc(); c.pressdigit( 1 ); c.pressdigit( 0 ); c.pressminus(); c.pressdigit( 2 ); c.pressdigit( 9 ); c.pressplus(); c.pressdigit( 3 ); c.pressequals(); c.pressclear(); Povratne vrijednosti metoda nakon izvedenih naredbi lijevog stupca "()" "1" "(P=1)" "10" "(P=10)" "(S=10, O=-)" "2" "(P=2, S=10, O=-)" "29" "(P=29, S=10, O=-)" "(S=-19, O=+)" "3" "(P=3, S=-19, O=+)" "-16" "(P=-16)" "()" Isprobajte rad implementiranog kalkulatora pomoću kratkog ispitnog programa sličnog prethodno navedenom primjeru korištenja. Nakon svake naredbe ispišite povratne vrijednosti metoda i kako biste se uvjerili da vaš kalkulator radi ispravno.
Prethodno opisan primjer uporabom primjeraka definirane porodice gumbi prikazan je u nastavku: Button [] buttons = { new DigitButton(Digits.ONE), new DigitButton(Digits.ZERO), new OperationButton(Operations.MINUS), new DigitButton(Digits.TWO), new DigitButton(Digits.NINE), new OperationButton(Operations.PLUS), new DigitButton(Digits.THREE), new OperationButton(Operations.EQUALS), new OperationButton(Operations.CLEAR) ; ICalculator c = new SimpleCalc(); for (Button b : buttons) { c.press(b); System.out.println(); Stavite li ovaj kod u metodu main, rezultat izvođenja mora biti jednak rezultatu izvođenja koda danog u prethodnoj tablici. Prilikom ostvarenja rješenja nigdje ne smijete imati eksplicitne pitalice koje provjeravaju koja je znamenka pritisnuta. To dakle isključuje kodove poput: if(digit=='0') { else if(digit=='1') { else if() { else { ili pak: switch(digit) { case '0': case '1': case '2': case '9': ili: if(digit==digits.zero) { else if(digit==digits.one) { else if() { else { i slično. Oslonite se na podatke koje sa sobom donose konstante enumeracija koje ste napravili.