Sono uno studente universitario e ho questo incarico su un simulatore di eventi discreti. Attualmente ho completato alla fine della v1.2 qui . In allegato anche una panoramica algoritmica.
Hoscrittoilmiocodiceinquantotaleperrisolvereilproblemainquestione.Questocodicesuperatuttiicasiditestforniti,mapensochecisiasicuramentespaziopermiglioramentinell'aspettoprogettuale.HoinventatoquestocodiceestraendoilproblemaerisolvendoloinmodoproceduraleperpoiriprogettarloinOO.PersonalmentesonovenutodaunbackgrounddiPythoneorastoimparandoJavaperquestomodulo,quindistoancoraprendendolecordesuldesignOOPemistoliberandodituttoil"mindset procedurale" il più possibile.
Nota: ho accesso privato a RandomGenerator
poiché mi è stato fornito un file compilato con cui lavorare nel mio pacchetto.
principale
import cs2030.simulator.DiscreteEventSimulator;
/**
* Contains main method to start the programme.
*/
class Main {
/**
* Starts the Discrete Event Simulator.
*/
public static void main(String[] args) {
DiscreteEventSimulator.start();
}
}
DiscreteEventSimulator
package cs2030.simulator;
import java.util.Scanner;
/**
* Starts off the Discrete Event Simulator
*/
public class DiscreteEventSimulator {
/**
* Creates an EventManager to simulate the execution of events.
* This is done by using Scanner object to take in inputs.
* required for a RandomGenerator, then pass those arguments to EventManager.
* @param args String[]
*/
public static void start() {
Scanner sc = new Scanner(System.in);
int seed = sc.nextInt();
int numServers = sc.nextInt();
int numWaitingSlots = sc.nextInt();
int numCustomer = sc.nextInt();
double arrivalRate = sc.nextDouble();
double svcRate = sc.nextDouble();
sc.close();
double restRate = 1.0;
EventManager eventManager = new EventManager(numServers, numCustomer,
numWaitingSlots,seed,arrivalRate,svcRate,restRate);
eventManager.doService();
}
}
EventManager
package cs2030.simulator;
import java.util.PriorityQueue;
/**
* EventManager class that handles the sequence of Event executions.
*/
class EventManager {
/**
* Array of servers that determines the way ArrivalEvents are processed.
*/
Server [] servers;
/**
* PriorityQueue of events to be cleared by the end of the simulation.
*/
PriorityQueue<Event> events;
/**
* Statistics object to keep track of average waiting time of served customers,
* number of served customers,
* and the number of customers that left without being served.
*/
Statistics statistics = new Statistics();
/**
* RandomGenerator object to randomise arrival time as well as
* service time, which is the time to convert ServedEvent to DoneEvent.
*/
RandomGenerator gen;
/**
* Constructs a EventManager that creates the ArrivalEvents of randomised time.
* from the number of customers, load them into the PriorityQueue.
* @param numServers the number of servers to be created.
* @param numCustomer the number of customers to be served, which is
* also equal to the number of ArrivalEvents preloaded onto PriorityQueue.
* @param seed intialising value where the random values are generated from
* @param arrivalRate arrival rate, mu in RandomGenerator constructor
* @param svcRate service rate, lambda in RandomGenerator constructor
* @param restRate rest rate, rho in RandomGenerator constructor
*/
EventManager(int numServers, int numCustomer, int numWaitingSlots
int seed, double arrivalRate, double svcRate, double restRate) {
this.events = new PriorityQueue<>(new EventComparator());
this.gen = new RandomGenerator(seed, arrivalRate,svcRate,restRate);
double time = 0;
Customer customer = new Customer(time);
ArrivalEvent tempEvent = new ArrivalEvent(customer,time);
events.add(tempEvent);
for (int i = 0;i < numCustomer - 1;i++) {
double x = gen.genInterArrivalTime();
time += x;
customer = new Customer(time);
tempEvent = new ArrivalEvent(customer,time);
events.add(tempEvent);
}
this.servers = new Server [numServers];
for (int i = 0;i < numServers;i++) {
this.servers[i] = new Server(numWaitingSlots);
}
}
/**
* Processes the full sequence of ArrivalEvents to calculate statistics.
* This process is split into a few stages,
* (i)At the start of each loop, get the first event from the PriorityQueue
* (ii)prints the profile of the event to signal that we start processing it
* (iii)current event creates the next event,
* with some information on the available servers as well as RandomGenerator
* in case a DoneEvent can be created from the current event.
* (iv) If applicable, Statistics are also updated after
* the creation of the new event, which will also be added to the PriorityQueue.
* (v) prints the statistics after the PriorityQueue is cleared.
*/
void doService() {
while (events.size() > 0) {
Event firstEvent = getFirstEvent();
System.out.println(firstEvent);
Event newEvent = firstEvent.getNextEvent(servers,gen);
if (newEvent != null) {
newEvent.updateStatistics(statistics);
events.add(newEvent);
}
}
System.out.println(statistics);
}
/**
* Accesses an event as well as remove it from the PriorityQueue.
* @return the first event from the PriorityQueue,
* according to the Comparator object it was created with.
*/
Event getFirstEvent() {
return events.poll();
}
}
EventComparator
package cs2030.simulator;
import java.util.Comparator;
/**
* EventComparator class to create a comparison criteria for events.
*/
class EventComparator implements Comparator<Event> {
/**
* Compares 2 Events and decides which is smaller, equal or greater.
* The first key is to check for the earliest time.
* If there is a tie breaker, customerID is checked instead,
* which also hints on the priority of different type of events.
* @param e1 left event
* @param e2 right event
* @return -1 if left event is prioritised over right event.
* 0 if there isn't a priority, which will not happen in this case.
* 1 if right event is prioritised over left event.
*/
public int compare(Event e1, Event e2) {
if (e1.getTime() < e2.getTime()) {
return -1;
} else if (e1.getTime() > e2.getTime()) {
return 1;
} else if (e1.getCustomerID() < e2.getCustomerID()) {
return -1;
} else if (e1.getCustomerID() > e2.getCustomerID()) {
return 1;
} else {
System.out.println("Bug with code, please check");
return 0;
}
}
}
Clienti
package cs2030.simulator;
/**
* Customer class that holds customer information.
*/
class Customer {
/**
* Counter integer that generates CustomerID in a non-repetitive way.
*/
private static int counter = 1;
/**
* CustomerID that allows distinguishing between 2 customers.
*/
private final int customerID;
/**
* Time when the customer first arrives.
*/
private final double time;
/**
* Creates Customer.
* @param time randomised arrival time of customer
*/
Customer(double time) {
this.customerID = counter;
this.time = time;
counter++;
}
int getCustomerID() {
return this.customerID;
}
double getTime() {
return this.time;
}
}
Server
package cs2030.simulator;
import java.util.ArrayList;
/**
* Server class that redirects the service of customers according to availability.
* The status of customer service is seen in terms of events that involves customers.
*/
class Server {
/**
* Counter integer that generates ServerID in a non-repetitive way.
*/
private static int counter = 1;
/**
* CustomerID that allows distinguishing between 2 servers.
*/
private int serverID;
//TBC to be re-entered
private ArrayList<Event> serverQueue = new ArrayList<>();
private final int maxSize;
/**
* Creates a server.
*/
Server(int numWaitingSlots) {
this.serverID = counter;
this.maxSize = 1 + numWaitingSlots;
counter++;
}
int getServerID() {
return this.serverID;
}
void addToQueue(Event newEvent) {
//assert this.serverQueue.size()<=this.maxSize;
this.serverQueue.add(newEvent);
}
/**
* Checks whether the first slot inside the Server has been taken.
* @return true if first slot has not been taken, false otherwise.
*/
boolean canTakeServedEvent() {
return this.serverQueue.size()==0;
}//its quite special, only 1 value
/**
* Checks whether the second slot inside the Server has been taken.
* @return true if the second slot has not been and the first slot is taken,
* false otherwise.
*/
boolean canTakeWaitEvent() {
int x = this.serverQueue.size();
return (x>0 && x<this.maxSize);
}// == qty means false and cant put
/**
* Clears up the 2nd slot of the server.
* This is done by removing up the 2nd customer to the 1st slot.
* and replace the 2nd slot with null status.
*/
void flushDoneEvent() {
//theDoneEvent should be always at the first slot
//everything will be pushed forward and auto updated
this.serverQueue.remove(0); //no Exception?
//all previous assumptions of the changed state alrdy done, this just qty
}
/**
* Gets the timestamp at which a customer waiting can expect to be served.
* @return earliest possible time at which waiting customer can be served
*/
//TBC this is really hard
double getDoneTime() {
return this.served.getTime();
}
}
Statistiche
package cs2030.simulator;
/**
* Statistics class to keep track of total waiting time of customers.
* the number of customers who left without being served.
* the number of customers who are served.
*/
class Statistics {
private double waitingTime = 0;
private int numLeft = 0;
private int numServed = 0;
/**
* Creates Statistics object using the empty constructor.
*/
Statistics(){}
/**
* Increases the number of customers who are served.
*/
void increaseServed() {
numServed++;
}
/**
* Increases waiting time of customers.
*/
void increaseWaitingTime(double time) {
waitingTime += time;
}
/**
* Increases the number of customers who left without being served.
*/
void increaseLeft() {
numLeft++;
}
/**
* Formats the Statistics to print all information gathered.
*/
public String toString() {
double x = waitingTime / numServed;
return '[' + String.format("%.3f",x) + ' ' +
numServed + ' ' + numLeft + ']';
}
}
Evento
package cs2030.simulator;
/**
* Abstract Event class to enforce polymorphism.
* Forces its subclasses to have the ability to create the next event
* and to update statistics.
*/
abstract class Event {
/**
* Customer that the event is involving.
*/
private final Customer customer;
/**
* Time at which the event is created,
* which may differ from customer arrival time if
* it is only created when it is caused to wait by another preceeding event.
*/
private final double time;
/**
* Creates an Event.
* @param customer customer that the event is involving
* @param time time at which event is created
*/
Event(Customer customer, double time) {
this.customer = customer;
this.time = time;
}
/**
* Creates the next event of parent type based on its original type.
*/
abstract Event getNextEvent(Server [] servers,RandomGenerator gen);
/** Modifies information in statistics if required.
*/
abstract void updateStatistics(Statistics statistics);
Customer getCustomer() {
return this.customer;
}
int getCustomerID() {
return this.customer.getCustomerID();
}
double getTime() {
return this.time;
}
}
ArrivalEvent
package cs2030.simulator;
/**
* ArrivalEvent class to simulate the act of a customer arriving.
*/
class ArrivalEvent extends Event {
/**
* Creates an ArrivalEvent.
* @param customer customer that the event is involving.
* @param time time at which event is created.
*/
ArrivalEvent(Customer customer, double time) {
super(customer, time);
}
/**
* Creates the next event based on the availability of servers.
* The available server will be updated to hold a field of the event
* and be involved in the creation of new event.
* @param servers Array of servers to be checked
* @param gen RandomGenerator, not used in this case.
* @return parent class Event, which could be in the form of
* LeaveEvent, if there are no available servers.
* ServedEvent, if there exists an available server that is completely free.
* WaitEvent, if there exists available server and there are no empty servers.
* null, which won't be reached as it's a Debugging statement.
*/
Event getNextEvent(Server [] servers,RandomGenerator gen) {
Server freeServer = getFreeServer(servers);
if (freeServer == null) {
return createLeaveEvent();
} else if (freeServer.canTakeServedEvent()) {
ServedEvent newEvent = createServedEvent(freeServer);
freeServer.addToQueue(newEvent);
return newEvent;
} else if (freeServer.canTakeWaitEvent()) {
WaitEvent newEvent = createWaitEvent(freeServer);
freeServer.addToQueue(newEvent);
return newEvent;
} else { //I think the type to reference before polymorphing is impt, dk wrapper thingy
System.out.println("Bug in ArrivalEvents");
return null;
}
}
/**
* Creates a LeaveEvent not bounded to any server.
* @return LeaveEvent
*/
LeaveEvent createLeaveEvent() {
return new LeaveEvent(this.getCustomer(),this.getTime());
}
/**
* Creates a ServedEvent bounded to an empty server.
* @param freeServer the server that is empty.
* @return ServedEvent.
*/
ServedEvent createServedEvent(Server freeServer) {
return new ServedEvent(this.getCustomer(),this.getTime(),freeServer);
}
/**
* Creates a WaitEvent bounded to a partially occupied server.
* @param freeServer the server that is partially occupied.
* @return WaitEvent.
*/
WaitEvent createWaitEvent(Server freeServer) {
return new WaitEvent(this.getCustomer(),this.getTime(),freeServer);
}
/**
* Modifies information in statistics if required.
* @param statistics Not used in this case.
*/
void updateStatistics(Statistics statistics) {
return;
}
/**
* Finds the earliest available server based on search results.
* @param servers Array of servers to be checked.
* @return Server if an empty server or partially empty server is found
* null otherwise.
*/
Server getFreeServer(Server[] servers) {
boolean hasFoundSlots = false;
Server choiceServer = null;
for (int i = 0; i < servers.length;i++) {
Server newServer = servers[i];
if (newServer.canTakeServedEvent()) {
return newServer;
} else if (newServer.canTakeWaitEvent() && !hasFoundSlots) {
choiceServer = newServer;
hasFoundSlots = true;
}
}
if (hasFoundSlots == false) {
return null;
} else {
return choiceServer;
}
}
/**
* Formats the ArrivalEvent to print out its profile.
*/
public String toString() {
return String.format("%.3f",this.getTime()) + ' ' +
this.getCustomerID() + " arrives";
}
}
LeaveEvent
package cs2030.simulator;
/**
* LeaveEvent class to simulate the act of customer leaving without being served.
*/
class LeaveEvent extends Event {
private Server server;
/**
* Creates an LeaveEvent.
* @param customer customer that the event is involving.
* @param time time at which event is created.
*/
LeaveEvent(Customer customer, double time) {
super(customer,time);
}
/**
* Creates a null object to signal no actual Event type is created.
* @param servers Array of servers, not used in this case
* @param gen RandomGenerator, not used in this case
* @return null object
*/
Event getNextEvent(Server [] servers, RandomGenerator gen) {
return null;
}
/**
* Increases the customer leave count inside statistics.
* @param statistics statistics to be updated
*/
void updateStatistics(Statistics statistics) {
statistics.increaseLeft();
}
/**
* Formats the LeaveEvent to print out its profile.
*/
public String toString() {
return String.format("%.3f",this.getTime()) +
' ' + this.getCustomerID() + " leaves";
}
}
DoneEvent
package cs2030.simulator;
/**
* DoneEvent class to simulate the completion of service to a customer by a server.
*/
class DoneEvent extends Event {
/**
* Server that the DoneEvent belongs to.
*/
private Server server;
/**
* Creates an DoneEvent.
* @param customer customer that the event is involving.
* @param time time at which event is created.
* @param server server that the DoneEvent belongs to.
*/
DoneEvent(Customer customer, double time, Server server) {
super(customer,time);
this.server = server;
}
/**
* Creates a null object to signal no actual Event type is created.
* Server is being updated with the service time the
* next event should adhere to if any.
* @param servers Array of servers, not used in this case.
* @param gen RandomGenerator, not used in this case.
* @return null object.
*/
Event getNextEvent(Server [] servers,RandomGenerator gen) {
this.server.flushDoneEvent();
return null;
}
/**
* Modifies information in statistics if required.
* @param statistics Not used in this case
*/
void updateStatistics(Statistics statistics) {
return;
}
/**
* Formats the DoneEvent to print out its profile.
*/
public String toString() {
return String.format("%.3f",this.getTime()) +
' ' + this.getCustomerID() + " done serving by " +
server.getServerID();
}
}
WaitEvent
package cs2030.simulator;
/**
* WaitEvent class to simulate the act of customer waiting.
* for another customer to be served by the same server.
*/
class WaitEvent extends Event {
/**
* Server that the WaitEvent belongs to.
*/
private Server server;
/**
* Creates an WaitEvent.
* @param customer customer that the event is involving
* @param time time at which event is created
* @param server server that the WaitEvent belongs to
*/
WaitEvent(Customer customer, double time, Server server) {
super(customer,time);
this.server = server;
}
/**
* Creates a ServedEvent to signal that the current customer can now be served.
* Timestamp the current customer is being served is taken from the server.
* @param servers Array of servers to be checked, not used in this case
* @param gen RandomGenerator, not used in this case
* @return ServedEvent
*/
ServedEvent getNextEvent(Server [] servers,RandomGenerator gen) {
if (!this.server.canTakeWaitEvent()) {
ServedEvent newEvent = new ServedEvent(this.getCustomer(),
this.server.getDoneTime(), this.server);
this.server.setWaitEvent(newEvent);
return newEvent;
}
return null;
}
/**
* Modifies information in statistics if required.
* @param statistics Not used in this case
*/
void updateStatistics(Statistics statistics) {
return;
}
/**
* Formats the WaitEvent to print out its profile.
*/
public String toString() {
return (String.format("%.3f",this.getTime()) +
' ' + this.getCustomerID() + " waits to be served by " +
server.getServerID());
}
}
ServedEvent
package cs2030.simulator;
/**
* ServedEvent class to simulate the start of service to a customer by a server.
*/
class ServedEvent extends Event {
/**
* Server that the ServedEvent belongs to.
*/
private Server server;
/**
* Creates an ServedEvent.
* @param customer customer that the event is involving
* @param time time at which event is created
* @param server server that the ServedEvent belongs to
*/
ServedEvent(Customer customer, double time, Server server) {
super(customer,time);
this.server = server;
}
/**
* Creates a DoneEvent to signal that the service has been completed.
* Time taken to complete the service is randomised by RandomGenerator.
* DoneEvent is created at the new timestamp of current time of ServedEvent
* added to the randomised service time.
* @param servers Array of servers to be checked, not used in this case
* @param gen RandomGenerator, to randomise service time
* @return DoneEvent
*/
DoneEvent getNextEvent(Server [] servers,RandomGenerator gen) {
double x = gen.genServiceTime();
DoneEvent newEvent = new DoneEvent(this.getCustomer(),
this.getTime() + x,this.server);
this.server.setServedEvent(newEvent);
return newEvent;
}
/**
* Increases the customer served count in statistics.
* and increase the waiting time by the customer if any
* @param statistics statistics to be updated
*/
void updateStatistics(Statistics statistics) {
statistics.increaseServed();
statistics.increaseWaitingTime(this.getTime() - this.getCustomer().getTime());
}
/**
* Formats the ServedEvent to print out its profile.
*/
public String toString() {
return (String.format("%.3f",this.getTime()) + ' ' +
this.getCustomerID() + " served by " + server.getServerID());
}
}
Il dilemma principale:
- Ho creato 2 classi
Event
eServer
per completare l'assegnazione (mostrato nel link sopra). Sono stato costretto a inserire una proprietà diServer
inEvent
in modo che gli eventi potessero stampare facilmente il profilo come parte dei criteri di assegnazione, ma sono stato anche costretto a inserire una proprietà diEvent
inServer
, trovo che mi risparmierebbe molto lavoro per comunicare aWaitEvent
in Server di che ora passare a quando si diventaServedEvent
. Si tratta di una forma di dipendenza ciclica (una cattiva pratica), e se lo è, è giustificabile farlo?
Altri dilemmi:
-
Informazioni sul collegamento tra le responsabilità di classe e qual è il modo migliore di richiamare una chiamata di metodo tra oggetti diversi in ambiti diversi.
EventManager
è ritenuto responsabile del mantenimento di un campoStatistics
e di unPriorityQueue
diEvents
, poiché è il Manager. Nella mia implementazione avrei potuto scrivere uno dei seguenti:A:
newEvent.updateStatistics(statistics)
B:
statistics.updateStatistics(newEvent)
In A, per rendere un metodo per la classe Event è più facile e più accessibile in termini di lunghezza del codice. Ogni sottoclasse di Event avrà una propria versione di aggiornamento delle statistiche e quindi eviterà alcuni dispacci sul tipo delle diverse classi, che credo sia il principio di OOP se non mi sbaglio in questo.
D'altra parte, in B sembra che sia più soddisfacente per la responsabilità. L'oggetto
newEvent
è il fornitore delle informazioni, quindi dovrebbe essere inserito negli argomenti.Ho scelto A alla fine poiché durante il tempo ho trovato B logico, mi sono reso conto che devo fare brutti dispatching e sono tentato di restituire le statistiche come argomenti agli eventi, sotto forma di
newEvent.f(this)
, nel metodo Statistica %codice%. Questo mi impedisce di inviareupdateStatistics
s come argomenti dall'inizio. Qual è la tua opinione su questo dilemma del design della responsabilità rispetto alla facilità di codifica per il programmatore? -
Il mio compito mi richiede di utilizzare
Event
object e ho deciso di implementarlo come un singleton, creato inRandomGenerator
. Tuttavia, questo oggetto viene utilizzato in istanze molto specifiche inEventManager
, che in realtà è il tipo di riferimentoEvent newEvent = firstEvent.getNextEvent(servers,gen)
per gestire tutti i casi in base al polimorfismo e in realtà solo 2 su 5 devono richiamare i metodi di questoEvent
. Attualmente, se il problema è semplice, ho solo bisogno di passare questo oggetto singletonRandomGenerator
una volta in un metodo e il 3/5 degli altri casi non lo userà. Ma se questo 2/5 casi che lo usano ha un'ulteriore ramificazione su sotto-casi, diventerebbe brutto continuare a passare questo oggetto singleton e fare in modo che i casi non coinvolti ottengano accesso ai metodiRandomGenerator
? C'è un modo per aggirare questo problema?