Realizzazione di Phone Plan Università degli Studi di Bologna Facoltà di Ingegneria • Fare riferimento alla progettazione vista in aula Fondamenti di Informatica T2 Modulo 2 • Partire implementando prima le classi con meno dipendenze che, di conseguenza, sono più semplici, nell’ordine: SimpleTime, Band, Operator, PhonePlan. Corso di Laurea in Ingegneria Informatica Anno accademico 2008/2009 2 Realizzazione… e testing! Soluzione - SimpleTime package tlc; • Usare Eclipse come ambiente di sviluppo public class SimpleTime { private int hours; private int minutes; • Verificare di volta in volta quanto fatto fino alla verifica completa del funzionamento del sistema public static final SimpleTime MIN_VALUE = new SimpleTime(0, 0); public static final SimpleTime MAX_VALUE = new SimpleTime(23, 59); – Per ogni classe creata, verificarne il corretto funzionamento – Non eliminare i metodi di test: possono sempre servire! public SimpleTime(int hours, int minutes) { this.hours = hours % 24; Ore valide se comprese fra 0 a 23. Minuti validi se compresi fra this.minutes = minutes % 60; 0 a 59. } … • Usare il DayOfWeekEnum di cui al laboratorio precedente! – Naturalmente deve essere stato realizzato (!) e testato in modo adeguato (!!) 3 Soluzione - SimpleTime SimpleTime modella un orario in modo semplice in termini di ore e minuti. … public int getHours() { Metodi Metodi accessor. accessor return hours; } public int getMinutes() { return minutes; } Restituisce un nuovo public SimpleTime addMinutes(int mins) orario con minuti pari alla { somma fra i minuti int newHours; dell’oggetto corrente ed i minuti passati come int newMinutes; parametro. newMinutes = this.minutes + mins; newHours = this.hours + (newMinutes > 59 ? 1 : 0); newMinutes %= 60; return new SimpleTime(newHours, newMinutes); }… 5 } 4 Soluzione - SimpleTime Verifica se l’orario corrente è … valido controllando la public boolean isValid() consistenza del suo stato. { return this.compareTo(MIN_VALUE) >= 0 && this.compareTo(MAX_VALUE) <= 0; } public boolean equals(SimpleTime time) { if (time == null) Verifica l’uguaglianza fra due oggetti di tipo SimpleTime. { return false; } return this.hours == time.hours && this.minutes == time.minutes; } … Università di Bologna – A.A. 2008/2009 6 Soluzione - SimpleTime Soluzione - DayOfWeekEnum … public int compareTo(SimpleTime time) { Confronta due oggetti di tipo if (this.hours > time.hours) SimpleTime. return 1; else if (this.hours == time.hours) { if (this.minutes > time.minutes) return 1; else if (this.minutes == time.minutes) return 0; else return -1; } else return -1; } 7 … package tlc; import java.util.GregorianCalendar; Modella un giorno della settimana. public class DayOfWeekEnum { public static final DayOfWeekEnum SUNDAY = new DayOfWeekEnum(GregorianCalendar.SUNDAY); public static final DayOfWeekEnum MONDAY = new DayOfWeekEnum(GregorianCalendar.MONDAY); public static final DayOfWeekEnum TUESDAY = new DayOfWeekEnum(GregorianCalendar.TUESDAY); public static final DayOfWeekEnum WEDNESDAY = new DayOfWeekEnum(GregorianCalendar.WEDNESDAY); public static final DayOfWeekEnum THURSDAY = new 8 DayOfWeekEnum(GregorianCalendar.THURSDAY); … Soluzione - DayOfWeekEnum Soluzione - DayOfWeekEnum … … public static final DayOfWeekEnum FRIDAY = new DayOfWeekEnum(GregorianCalendar.FRIDAY); public static final DayOfWeekEnum SATURDAY = new DayOfWeekEnum(GregorianCalendar.SATURDAY); public int value; Valore intero associato al giorno della settimana. public DayOfWeekEnum(int gregoriaCalendarDay) { this.value = gregorianCalendarDay; } … 9 Soluzione - DayOfWeekEnum public boolean isContainedIn(DayOfWeekEnum[] days) { for (DayOfWeekEnum day : days) { if (day == this) Controlla se il giorno { corrente è contenuto return true; nell’insieme di giorni } passato come parametro. } return false; } public int toGregorianCalendarDayOfWeek() { return value; } … package tlc; Soluzione - Band import java.util.Date; import java.util.GregorianCalendar; … public static DayOfWeekEnum[] values() { return new DayOfWeekEnum[] {SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY}; } public class Band { private DayOfWeekEnum[] combineDays; private SimpleTime startTime; private SimpleTime endTime; private double costPerInterval; public static DayOfWeekEnum fromGregorianCalendarDayOfWeek(int gregorianCalendarDay) { for (DayOfWeekEnum day : DayOfWeekEnum.values()) if (day.value == gregorianCalendarDay) return day; return null; } 11 10 Sfruttiamo le classi per la gestione delle date e dei calendari. Ora fine e inizio validità come SimpleTime e non come Date. public Band(SimpleTime startTime, SimpleTime endTime, DayOfWeekEnum[] days, double costPerInterval) { Oggetto Band caratterizzato da: this.startTime = startTime; ora inizio / fine e this.endTime = endTime; giorni della this.combinedDays = days; settimana in cui la this.costPerInterval = costPerInterval; fascia oraria è } … valida, e costo dell’intervallo.12 } Università di Bologna – A.A. 2008/2009 Soluzione - Band Soluzione - Band … public SimpleTime getStartTime() { return startTime; } public SimpleTime getEndTime() { return endTime; } … public boolean isInBand(Date startCall) { GregorianCalendar calendar = new GregorianCalendar(); calendar.setTime(startCall); Oggetto calendar per estrarre il Verifica se la fascia è giorno, l’ora, eper i minuti dalla data applicabile una data/ora /orapassata passata come parametro. come parametro int day = calendar.get(GregorianCalendar.DAY_OF_WEEK); int hour = calendar.get(GregorianCalendar.HOUR_OF_DAY); int minute = calendar.get(GregorianCalendar.MINUTE); Metodi accessor. public DayOfWeekEnum[] getCombinedDays() { return combinedDays; } public double getCostPerInterval() { return costPerInterval; } … SimpleTime startCallTime = new SimpleTime(hour, minute); return DayOfWeekEnum.fromGregorianCalendarDayOfWeek(day). isContainedIn(combinedDays) && startTime.compareTo(startCallTime) <= 0 && endTime.compareTo(startCallTime) >= 0; 13 } … Soluzione - Band Soluzione - Band … Verifica se la fascia corrente è public boolean isValid() valida controllando la consistenza del suo stato. { if (combinedDays != null && combinedDays.length > 0 && startTime != null && endTime != null ) { for (DayOfWeekEnum day : combinedDays) { if (day == null ) { return false; } } return startTime.compareTo(endTime) < 0; } return false; } 15 … Soluzione - Operator … public static void sortByStartTime(Band[] bands) { for (int i = 0; i < bands.length; i++) { for (int j = i + 1; j < bands.length; j++) { if (bands[i].startTime.compareTo(bands[j].startTime) > 0) { Band temp = bands[i]; bands[i] = bands[j]; Ordina l’array di fasce orarie bands[j] = temp; } passate come parametro in } ordine crescente per ora di } inizio validità. } } … 16 Soluzione - Operator … public String getName() { return name; Metodi Metodi accessor. accessor } public Band[] getBands() { return bands; } public double getCostPerInterval(Date callStart) { Fornisce il costo per for (Band b : bands) una certa data / ora. { package tlc; import java.util.Date; public class Operator { private String name; private Band[] bands; 14 Un operatore telefonico è caratterizzato da un nome e da un insieme di fasce applicabili. public Operator(String name, Band[] bands) { this.name = name; this.bands = bands; } … if (b.isInBand(callStart)) return b.getCostPerInterval(); } return -1; } 17 } … Università di Bologna – A.A. 2008/2009 18 Soluzione - Operator … public boolean isValid() { for (Band band : bands) { if (!band.isValid()) return false; } Soluzione - Operator Verifica se l’operatore corrente è valido controllando la consistenza del suo stato. In particolare, controlla ... ... che le bande siano tutte valide e … for (DayOfWeekEnum day : DayOfWeekEnum.values()) { ... che le bande coprino if (!validateDay(day)) tutte le ore di tutti i return false; giorni della settimana. } return true; } … 19 … sfruttando private boolean validateDay(DayOfWeekEnum day) l’ordinamento fra bande, { Seleziona le bande relative ad un controlla che if (day != null) particolare giorno della settimana e ... queste { coprano tutte Band[] bandsInDay = selectBandInDay(day); le ore del Band.sortByStartTime(bandsInDay); giorno. for (int i = 0; i < bandsInDay.length - 1; i++) { SimpleTime firstBandEndTime = bandsInDay[i].getEndTime(); SimpleTime secondBandStartTime = bandsInDay[i + 1]. getStartTime(); if (!firstBandEndTime.addMinutes(1). equals(secondBandStartTime)) return false; }… 20 } … Soluzione - Operator Soluzione - Operator Se l’insieme di bande coprono tutte le ore del giorno, il metodo ritorna true, altrimenti ... … return bandsInDay.length > 0 && bandsInDay[0].getStartTime(). equals(SimpleTime.MIN_VALUE) && bandsInDay[bandsInDay.length - 1]. getEndTime().equals(SimpleTime.MAX_VALUE); … private Band[] selectBandInDay(DayOfWeekEnum day) { Fornisce l’insieme di bande relative ad int count = 0; un particolare giorno della settimana. for (Band band : bands) if (day.isContainedIn(band.getCombinedDays())) count++; Band[] result = new Band[count]; int j = 0; } else return false; } … for (int i = 0; i < bands.length; i++) if (day.isContainedIn(bands[i].getCombinedDays())) result[j++] = bands[i]; … il metodo ritorna false. return result; 21 Soluzione - Plan 22 } Soluzione - Plan package tlc; import java.util.Date; Un piano telefonico è caratterizzato public class Plan dal nome del piano, dall’intervallo { “standard”, costo dello scatto alla risposta, e insieme degli operatori. private String name; private int interval; private double startCallCost; private Operator[] operators; public Plan(String name, int interval, int startCallCost, Operator[] operators) { this.name = name; this.interval = interval; this.startCallCost = startCallCost; this.operators = operators; } … } … public String getName() { return name; } public int getInterval() { return interval; } Metodi accessor. public double getStartCallCost() { return startCallCost; } … 23 Università di Bologna – A.A. 2008/2009 24 Soluzione - Plan Soluzione - Plan … public double getCallCost(Date callStart, Date callEnd, String destOperator, String destNumber) Fornisce il costo di una chiamata { date le sue caratteristiche. Operator selected = getOperatorByName(destOperator); if (selected == null) return -1; double costPerInterval = selected.getCostPerInterval(callStart); if (costPerInterval == -1) return -1; long difference = callEnd.getTime() callStart.getTime(); int intervalCount = (int)(difference / interval); 25 return startCallCost + intervalCount * costPerInterval; } … Soluzione - Plan … public double getCallCost(PhoneCall call) { return getCallCost(call.getCallStart(), call.getCallEnd(), call.getDestOperator(), call.getDestNumber()); Fornisce il costo di una } chiamata dato un oggetto di public boolean isValid() tipo PhoneCall. { for (Operator operator : operators) { if (!operator.isValid()) Verifica se il piano telefonico { corrente è valido controllando return false; la validità degli operatori } telefonici. } return true; } 26 … package tlc; … private Operator getOperatorByName(String operatorName) { Operator selected = null; import java.util.Date; import java.text.DateFormat; public class PhoneCall { private Date callStart; private Date callEnd; private String destOperator; private String destNumber; for (Operator o : operators) { if (o.getName().equals(operatorName)) { selected = o; Fornisce l’oggetto Operatore break; corrispondente al nome } passato come parametro. } return selected; } 27 28 Soluzione - PhoneCall … public String toString() { DateFormat shortFormatter = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.FULL); Metodi accessor. return shortFormatter.format(callStart) + " -- " + shortFormatter.format(callEnd) + " --> " + public String getDestOperator() { return destOperator; } public String getDestNumber() { return destNumber; } … Una chiamata telefonica è caratterizzata dalla data / ora di inizio e fine, dall’operatore di destinazione, e dal numero di destinazione. public PhoneCall(Date callStart, Date callEnd, String destOperator, String destNumber) { this.callStart = callStart; this.callEnd = callEnd; this.destOperator = destOperator; this.destNumber = destNumber; }… Soluzione - PhoneCall … public Date getCallStart() { return callStart; } public Date getCallEnd() { return callEnd; } Soluzione - PhoneCall destNumber + “(" + destOperator + ”)"; } Rappresentazione a stringa di una chiamata telefonica. 29 Università di Bologna – A.A. 2008/2009 30 Soluzione - PhonePlanMain Soluzione - PhonePlanMain … Come prima cosa public static void main(String[] args) imposta la cultura { locale, cioè quella Locale.setDefault(Locale.ITALY); italiana. Plan plan = createPlan(); PhoneCall[] calls = getTestPhoneCalls(); NumberFormat formatter = NumberFormat.getCurrencyInstance(); package tlc; import import import import java.text.NumberFormat; java.util.Date; java.util.GregorianCalendar; java.util.Locale; public class PhonePlanMain { … } Classe per testare l’applicazione. 31 Soluzione - PhonePlanMain 32 Crea le fasce telefoniche relative ai giorni della … settimana Band[] timBands = new Band[]{ new Band(new SimpleTime(0,0), new SimpleTime(23,59), weekEnd, 0.10), new Band(new SimpleTime(0,0), new SimpleTime(7,59), workWeek, 0.10), new Band(new SimpleTime(8,0), new SimpleTime(17,59), workWeek, 0.20) Crea l’operatore “TIM” con le }; fasce appena create DayOfWeekEnum[] weekEnd = new DayOfWeekEnum[]{ DayOfWeekEnum.SATURDAY, DayOfWeekEnum.SUNDAY}; Crea i giorni della settimana suddivisi in due insiemi: workWeek e weekEnd. piano plan. Soluzione - PhonePlanMain … public static Plan createPlan() { DayOfWeekEnum[] workWeek = new DayOfWeekEnum[]{ DayOfWeekEnum.MONDAY, DayOfWeekEnum.TUESDAY, DayOfWeekEnum.WEDNESDAY, DayOfWeekEnum.THURSDAY, DayOfWeekEnum.FRIDAY}; … } … Crea un nuovo piano for (PhoneCall call : calls) telefonico e un insieme di chiamate telefoniche di { test. System.out.print(call); System.out.print(" --- "); System.out.println(formatter.format( plan.getCallCost(call) / 100)); Stampa ciascuna chiamata ed } il costo della chiamata con il Operator tim = new Operator("TIM", timBands); … 33 Soluzione - PhonePlanMain … Crea un nuovo insieme di fasce Band[] vodafoneBands = new Band[] telefoniche relative ai giorni della settimana { new Band(new SimpleTime(0,0), new SimpleTime(23,59), weekEnd, 0.15), new Band(new SimpleTime(0,0), new SimpleTime(7,59), workWeek, 0.15), new Band(new SimpleTime(8,0), new SimpleTime(17,59), workWeek, 0.30) Crea l’operatore “Vodafone” con le }; fasce appena create Operator vodafone = new Operator("Vodafone", vodafoneBands); return new Plan("SuperConveniente", 1000, 10, new Operator[] {tim,vodafone}); Crea un nuovo piano } … “SuperConveniente” con gli 35 operatori “TIM” e “Vodafone”. 34 Soluzione - PhonePlanMain Crea e restituisce un insieme di chiamate telefoniche di test. … public static PhoneCall[] getTestPhoneCalls() { GregorianCalendar cal = new GregorianCalendar(); Date callStart, callEnd; Crea una prima chiamata verso cal.set(2009, 02, 10, 15, 21, 21); l’operatore callStart = cal.getTime(); “TIM”, effettuata il 10/02/2009, cal.set(GregorianCalendar.HOUR_OF_DAY, 15); dalle 15:21:21 cal.set(GregorianCalendar.MINUTE, 25); alle 15:25:23. cal.set(GregorianCalendar.SECOND, 23); callEnd = cal.getTime(); PhoneCall call1 = new PhoneCall(callStart, callEnd, "TIM", "+39339123455679"); 36 … Università di Bologna – A.A. 2008/2009 Soluzione - PhonePlanMain … Soluzione - PhonePlanMain Crea una seconda chiamata verso l’operatore “Vodafone”. … cal.set(2009, 02, 10, 16, 35, 55); callStart = cal.getTime(); cal.set(GregorianCalendar.HOUR_OF_DAY, 16); cal.set(GregorianCalendar.MINUTE, 41); cal.set(GregorianCalendar.SECOND, 23); callEnd = cal.getTime(); PhoneCall call2 = new PhoneCall(callStart, callEnd, ”Vodafone”, "+39349987654321"); Crea una terza chiamata verso l’operatore “TIM”. cal.set(2009, 02, 10, 17, 21, 21); callStart = cal.getTime(); cal.set(GregorianCalendar.HOUR_OF_DAY, 18); cal.set(GregorianCalendar.MINUTE, 25); cal.set(GregorianCalendar.SECOND, 23); callEnd = cal.getTime(); PhoneCall call3 = new PhoneCall(callStart, callEnd, ”TIM”, "+39339123455679”); … return new PhoneCall[] {call1, call2, call3}; } 37 Università di Bologna – A.A. 2008/2009 38