Add new design choice regarding the use of Strings instead of Enums
- Author
- Vngngdn
- Date
- Dec. 15, 2016, 11:51 p.m.
- Hash
- f68e848416b19091f2f20166cbd19ebec9fa4bc3
- Parent
- 70c4f4fde97168ddd55ad491ce96bd3731314269
- Modified files
- Challenge 6/Reservation.java
- Challenge 6/ReservationController.java
- Challenge 6/ReservationView.java
- Challenge 6/Room.java
- Challenge 6/ontwerpkeuzes2.md
Challenge 6/Reservation.java ¶
1 addition and 1 deletion.
View changes Hide changes
1 |
1 |
import java.util.HashSet; |
2 |
2 |
import java.util.Date; |
3 |
3 |
|
4 |
4 |
/** |
5 |
5 |
* Represents a reservation in a hostel. |
6 |
6 |
* Reservation is a simple class that allows one to store reservation |
7 |
7 |
* information, request it when necessary, and so on. |
8 |
8 |
* Certain methods are provided for interaction, which use contracts to assert |
9 |
9 |
* proper functioning. |
10 |
10 |
* |
11 |
11 |
* Note: There is a form of redundancy in this class. |
12 |
12 |
* Reservation holds a Set of Beds, and an integer, indicating the amount of |
13 |
13 |
* people that are in this Reservation. |
14 |
14 |
* Normally, the amount of people in the Reservation is determined by the amount |
15 |
15 |
* of reserved Beds, because the amount of Beds implied the amount of people to |
16 |
16 |
* facilitate. |
17 |
17 |
* Because of the design of this program, I've weighed the advantages and |
18 |
18 |
* disadvantages of holding this implication, of using a seperate member |
19 |
19 |
* explicitely indicating the amount of people. |
20 |
20 |
* |
21 |
21 |
* I've chosen the latter option. It scales better in terms of weird |
22 |
22 |
* afterthoughts ("I also want to make people sleep on the floor"), but being |
23 |
23 |
* explicit is also a tad easier to manage. It also allows the other classes to |
24 |
24 |
* be far more decoupled from Reservation (otherwise, they'd have to check for |
25 |
25 |
* errors every time themselves, hindering cohesiveness). |
26 |
26 |
* |
27 |
27 |
* To overcome the possible difference, I've made it so that, every time, the |
28 |
28 |
* relation between the values is changed (the people in the Reservation |
29 |
29 |
* changes, for example), the class automatically checks if the values are |
30 |
30 |
* corresponding. If they're not, a warning is printed to system.out.err, |
31 |
31 |
* informing about the fact that there's a discrepancy. |
32 |
32 |
* |
33 |
33 |
* I think that's the best way to overcome this problem. |
34 |
34 |
* @author Maarten Vangeneugden - 1438256 |
35 |
35 |
*/ |
36 |
36 |
public class Reservation { |
37 |
37 |
|
38 |
38 |
// Members |
39 |
39 |
private String groupName; |
40 |
40 |
private Date begin; |
41 |
41 |
private Date end; |
42 |
42 |
private Set<Bed> reservedBeds; |
43 |
43 |
private int people; // The amount of people in this Reservation |
44 |
44 |
private int reservationID; |
45 |
45 |
private String roomType; // Room type requested by group |
46 |
46 |
private Set<String> roomFacilities; // Requested room facilities |
47 |
47 |
private Set<Date> breakfastDays; // Set of days the group wants breakfast |
48 |
48 |
// Controllers |
49 |
49 |
private RoomController roomController; |
50 |
50 |
private ReservationController reservationController; |
51 |
51 |
|
52 |
52 |
/** |
53 |
53 |
* Create a new Reservation. |
54 |
54 |
* Be aware about the limitations of a Reservation: it's not the |
55 |
55 |
* Reservation's duty to check whether the provided Beds are properly |
56 |
56 |
* reserved; take care of this accordingly. |
57 |
57 |
* |
58 |
58 |
* Some information is implicit. For example: The size of the set of |
59 |
59 |
* reserved Beds implies the amount of people in the group; no breakfast |
60 |
60 |
* days implies no breakfast, ... |
61 |
61 |
* @param groupName The name of the group. |
62 |
62 |
* @param people The amount of people that are reserving. |
63 |
63 |
* @param begin The date that the Reservation begins. |
64 |
64 |
* @param end The date that the Reservation ends. |
65 |
65 |
* @param reservedBeds The set of Beds reserved and assigned to this |
66 |
66 |
* Reservation. |
67 |
67 |
* @param roomType The requested room type. |
68 |
68 |
* @param roomFacilities The set of all requested room facilities. |
69 |
69 |
* @param breakfastDays A set of all days that the group wants breakfast. |
70 |
70 |
* @param reservationID An ID for this reservation, to differentiate from |
71 |
71 |
* other Reservations. |
72 |
72 |
* @pre No parameter must be a null pointer. |
73 |
73 |
* @pre people must be a natural number, different from 0. |
74 |
74 |
* @pre begin must come before end. |
75 |
75 |
* @pre All dates in breakfastDays must fall between begin and end. |
76 |
76 |
* @pre No string parameter may be empty. |
77 |
77 |
* @post The amount of people in the Reservation is determined by the amount |
78 |
78 |
* of reserved Beds. |
79 |
79 |
* @throws NullPointerException if any parameter is a null pointer. |
80 |
80 |
* @throws IllegalArgumentException if any of the other preconditions is not |
81 |
81 |
* met. |
82 |
82 |
*/ |
83 |
83 |
public Reservation(String groupName, int people, Date begin, Date end, Set<Bed> reservedBeds, String roomType, Set<String> roomFacilities, Set<Date> breakfastDays) { |
84 |
84 |
// Contract validation: |
85 |
85 |
if(people < 1) { |
86 |
86 |
throw IllegalArgumentException("The amount of people should be at least 1."); |
87 |
87 |
} |
88 |
88 |
if(!begin.before(end)) { |
89 |
89 |
throw IllegalArgumentException("The begin date occurs after the end date."); |
90 |
90 |
} |
91 |
91 |
if(groupName.isEmpty() || roomType.isEmpty()) { |
92 |
92 |
throw IllegalArgumentException("groupName and/or roomType were empty strings."); |
93 |
93 |
} |
94 |
94 |
for(String roomFacility: roomFacilities) { |
95 |
95 |
if(roomFacility.isEmpty()) { |
96 |
96 |
throw IllegalArgumentException("One of the room facilities was an empty string."); |
97 |
97 |
} |
98 |
98 |
} |
99 |
99 |
for(Date breakfastDay : breakfastDays) { |
100 |
100 |
if(breakfastDay.before(begin) || breakfastDay.after(end)) { |
101 |
101 |
throw IllegalArgumentException("One of the breakfast days occurs before/after the reservation period."); |
102 |
102 |
} |
103 |
103 |
} |
104 |
104 |
// Contract validated, execute constructor |
105 |
105 |
this.groupName = groupName; |
106 |
106 |
this.people = people; |
107 |
107 |
this.beginDate = begin; |
108 |
108 |
this.endDate = end; |
109 |
109 |
this.reservedBeds = reservedBeds; |
110 |
110 |
this.reservationID = reservationID; |
111 |
111 |
this.roomType = roomType; |
112 |
112 |
this.roomFacilities = roomFacilities; |
113 |
113 |
this.breakfastDays = breakfastDays; |
114 |
114 |
} |
115 |
115 |
|
116 |
116 |
/** |
117 |
117 |
* Construct template Reservation. |
118 |
118 |
* Use this constructor if you need to create an empty/new Reservation. |
119 |
119 |
* It omits the standard restrictions (for example, the group name will be |
120 |
120 |
* empty) to accomodate for the expected usage of this Reservation. |
121 |
121 |
*/ |
122 |
122 |
public Reservation() { |
123 |
123 |
this.groupName = ""; |
124 |
124 |
this.people = 1; |
125 |
125 |
this.beginDate = new Date(); |
126 |
126 |
this.endDate = new Date(); |
127 |
127 |
this.reservedBeds = new HashSet<>(); |
128 |
128 |
this.reservationID = 0; |
129 |
129 |
this.roomType = ""; |
130 |
130 |
this.roomFacilities = new HashSet<>(); |
131 |
131 |
this.breakfastDays = new HashSet<>(); |
132 |
132 |
} |
133 |
133 |
|
134 |
134 |
/** |
135 |
135 |
* Checks whether the amount of people corresponds with the reserved Beds. |
136 |
136 |
* Call this method whenever a change in the amount of Beds, or the amount |
137 |
137 |
* of people is made, or, whenever you need to assure consistency. |
138 |
138 |
* |
139 |
139 |
* It also prints a warning to system.out.err to inform about a discrepancy, |
140 |
140 |
* should one be found. |
141 |
141 |
* |
142 |
142 |
* @return True if the counts are consistent, false otherwise. |
143 |
143 |
*/ |
144 |
144 |
private boolean checkPeopleCountConsistency() { |
145 |
145 |
int people = this.getPeople(); |
146 |
146 |
int beds = this.getReservedBeds().size(); |
147 |
147 |
if(people != beds) { |
148 |
148 |
system.out.err("There's a discrepancy in the amount of people in Reservation"+ |
149 |
149 |
this.reservationID +":"); |
150 |
150 |
system.out.err("People: "+String.valueOf(people)); |
151 |
151 |
system.out.err("Reserved Beds: "+String.valueOf(beds)); |
152 |
152 |
return false; |
153 |
153 |
} |
154 |
154 |
return true; |
155 |
155 |
} |
156 |
156 |
|
157 |
157 |
/** |
158 |
158 |
* Set the group name for this Reservation. |
159 |
159 |
* @param groupName The new group name. |
160 |
160 |
* @pre groupName mustn't be empty, or a null pointer. |
161 |
161 |
* @post The group name is changed to the given name. |
162 |
162 |
* @throws NullPointerException if groupName is a null pointer. |
163 |
163 |
* @throws IllegalArgumentException if groupName is an empty String. |
164 |
164 |
*/ |
165 |
165 |
public void setGroupName(String groupName) { |
166 |
166 |
if(groupName.isEmpty()) |
167 |
167 |
throw IllegalArgumentException("groupName is an empty String."); |
168 |
168 |
this.groupName = groupName; |
169 |
169 |
} |
170 |
170 |
|
171 |
171 |
/** |
172 |
172 |
* Retrieve the current group name. |
173 |
173 |
* @return The group name of this Reservation. |
174 |
174 |
*/ |
175 |
175 |
public String getGroupName() { |
176 |
176 |
return this.groupName; |
177 |
177 |
} |
178 |
178 |
|
179 |
179 |
/** |
180 |
180 |
* Get amount of people in this Reservation. |
181 |
181 |
* @post A warning will be printed to system.out.err if the amount of people |
182 |
182 |
* is inconsistent with the amount of reserved Beds. |
183 |
183 |
* @see Reservation.checkPeopleCountConsistency |
184 |
184 |
* @return The amount of people in this Reservation. |
185 |
185 |
*/ |
186 |
186 |
public int getPeople() { |
187 |
187 |
this.checkPeopleCountConsistency(); |
188 |
188 |
return this.people; |
189 |
189 |
} |
190 |
190 |
|
191 |
191 |
/** |
192 |
192 |
* Set the amount of people for this Reservation. |
193 |
193 |
* Note that this method will not notify you if the new amount of people is |
194 |
194 |
* equal to the previous amount. |
195 |
195 |
* |
196 |
196 |
* This method will print |
197 |
197 |
* @param people The new amount of people in this Reservation. |
198 |
198 |
* @pre people must be at least 1. |
199 |
199 |
* @post A warning will be printed to system.out.err if the amount of people |
200 |
200 |
* is inconsistent with the amount of reserved Beds. |
201 |
201 |
* @post The amount of people is changed to the given value. |
202 |
202 |
* @throws IllegalArgumentException if people is less than 1. |
203 |
203 |
*/ |
204 |
204 |
public void setPeople(int people) { |
205 |
205 |
if(people < 1) { |
206 |
206 |
throw IllegalArgumentException("people must be at least 1."); |
207 |
207 |
} |
208 |
208 |
this.people = people; |
209 |
209 |
this.checkPeopleCountConsistency(); |
210 |
210 |
} |
211 |
211 |
/** |
212 |
212 |
* Set the begin date for this Reservation. |
213 |
213 |
* @param begin The new begin date. |
214 |
214 |
* @pre begin mustn't be a null pointer. |
215 |
215 |
* @pre begin must come strictly before the end date. |
216 |
216 |
* @post The begin date is updated accordingly. |
217 |
217 |
* @throws NullPointerException if begin is a null pointer. |
218 |
218 |
* @throws IllegalArgumentException if begin comes after the end date. |
219 |
219 |
*/ |
220 |
220 |
public void setBegin(Date begin) { |
221 |
221 |
if(!begin.before(this.getEnd())) |
222 |
222 |
throw IllegalArgumentException("begin comes after the end date.") |
223 |
- | this.begin = begin; |
+ |
223 |
this.begin = begin; |
224 |
224 |
} |
225 |
225 |
|
226 |
226 |
|
227 |
227 |
|
228 |
228 |
/** |
229 |
229 |
* Set the end date for this Reservation. |
230 |
230 |
* @param end The new end date. |
231 |
231 |
* @pre end mustn't be a null pointer. |
232 |
232 |
* @pre end must come strictly after the begin date. |
233 |
233 |
* @post The end date is updated accordingly. |
234 |
234 |
* @throws NullPointerException if end is a null pointer. |
235 |
235 |
* @throws IllegalArgumentException if end comes after the end date. |
236 |
236 |
*/ |
237 |
237 |
public Date getDate() { |
238 |
238 |
return date; |
239 |
239 |
} |
240 |
240 |
|
241 |
241 |
public void setReservedBeds(Set<Bed> reservedBeds) { |
242 |
242 |
this.reservedBeds = reservedBeds; |
243 |
243 |
this.checkPeopleCountConsistency(); |
244 |
244 |
} |
245 |
245 |
|
246 |
246 |
public Set<Bed> getReservedBeds() { |
247 |
247 |
return reservedBeds; |
248 |
248 |
this.checkPeopleCountConsistency(); |
249 |
249 |
} |
250 |
250 |
|
251 |
251 |
// TODO: Write documentation for all of these, even though it's all mostly |
252 |
252 |
// copy/pasting. pfffff |
253 |
253 |
public void setReservationID(int reservationID) { |
254 |
254 |
this.reservationID = reservationID; |
255 |
255 |
} |
256 |
256 |
|
257 |
257 |
public int getReservationID() { |
258 |
258 |
return reservationID; |
259 |
259 |
} |
260 |
260 |
|
261 |
261 |
public void setRoomType(String roomType) { |
262 |
262 |
this.roomType = roomType; |
263 |
263 |
} |
264 |
264 |
|
265 |
265 |
public String getRoomType() { |
266 |
266 |
return roomType; |
267 |
267 |
} |
268 |
268 |
|
269 |
269 |
public void setRoomFacilities(Set<String> roomFacilities) { |
270 |
270 |
this.roomFacilities = roomFacilities; |
271 |
271 |
} |
272 |
272 |
|
273 |
273 |
public Set<String> getRoomFacilities() { |
274 |
274 |
return roomFacilities; |
275 |
275 |
} |
276 |
276 |
|
277 |
277 |
/** |
278 |
278 |
* Calculates the price of the Reservation, based on its current state. |
279 |
279 |
* Price table: |
280 |
280 |
* - 20/person/day |
281 |
281 |
* - 5 less in low season, 5 more in high season |
282 |
282 |
* - 4/breakfast ordered |
283 |
283 |
* - If room is fully booked by the group --> 10% discount |
284 |
284 |
* @return The price in euros. |
285 |
285 |
*/ |
286 |
286 |
public int getPrice() { |
287 |
287 |
int totalPrice = 0; |
288 |
288 |
// Jan - Apr: Mid |
289 |
289 |
// May - Aug: High |
290 |
290 |
// Sep - Dec: Low |
291 |
291 |
|
292 |
292 |
// Calculate bed prices |
293 |
293 |
int month = this.getDate().getMonth(); |
294 |
294 |
int bedPrice = 20; |
295 |
295 |
if(month >=8) { // From September: |
296 |
296 |
bedPrice -= 5; |
297 |
297 |
} else if(month >=4) { // From May: |
298 |
298 |
bedPrice += 5; |
299 |
299 |
} |
300 |
300 |
totalPrice += (this.getReservedBeds().size() * this.getNights() * bedPrice); |
301 |
301 |
// Calculate price for breakfasts |
302 |
302 |
int breakfasts = this.getBreakfastDays().length; |
303 |
303 |
totalPrice += breakfasts * this.getReservedBeds().size(); |
304 |
304 |
// Check if eligible for discount |
305 |
305 |
for(Room room: roomController.getRooms()) { |
306 |
306 |
Set<Bed> roomBeds = room.getBeds(); |
307 |
307 |
if(roomBeds.containsAll(this.reservedBeds)) { |
308 |
308 |
double discount = (double)totalPrice * 0.1; |
309 |
309 |
totalPrice -= (int)discount; |
310 |
310 |
} |
311 |
311 |
} |
312 |
312 |
return totalPrice; |
313 |
313 |
} |
314 |
314 |
|
315 |
315 |
|
316 |
316 |
|
317 |
317 |
|
318 |
318 |
|
319 |
319 |
|
320 |
320 |
|
321 |
321 |
public void setBreakfastDays(int[] breakfastDays) { |
322 |
322 |
this.breakfastDays = breakfastDays; |
323 |
323 |
} |
324 |
324 |
public int[] getBreakfastDays() { |
325 |
325 |
return this.breakfastDays; |
326 |
326 |
} |
327 |
327 |
|
328 |
328 |
} |
329 |
329 |
Challenge 6/ReservationController.java ¶
1 addition and 0 deletions.
View changes Hide changes
1 |
1 |
import java.util.HashSet; |
2 |
2 |
import java.util.Date; |
3 |
3 |
|
4 |
4 |
/** |
5 |
5 |
* Controller class for the Reservations of this program. |
6 |
6 |
* Since this program handles a youth hostel, it's imperative that it holds a |
7 |
7 |
* number of Reservations. |
8 |
8 |
* |
9 |
9 |
* ReservationController takes the responsibility of handling the addition, |
10 |
10 |
* cancelling, editing, ... of Reservations, and the related tasks inherent to |
11 |
11 |
* these responsibilities, such as reserving beds, generating IDs, ... |
12 |
12 |
* @author Maarten Vangeneugden - 1438256 |
13 |
13 |
*/ |
14 |
14 |
public class ReservationController { |
15 |
15 |
|
16 |
16 |
/* Rationale: |
17 |
17 |
* Normally I'd put this as an interface (Set), but an interface does not |
18 |
18 |
* inherit from Object, and thus, does not provide clone(). This is |
19 |
19 |
* necessary, because otherwise, there's no point in having private |
20 |
20 |
* members. |
21 |
21 |
*/ |
22 |
22 |
private HashSet<Reservation> reservations; // Holds all Reservations |
23 |
23 |
private RoomController roomController; |
24 |
24 |
|
25 |
25 |
/** |
26 |
26 |
* Creates the ReservationController. |
27 |
27 |
* |
28 |
28 |
* WARNING: This program avoids the use of null pointers, but because of |
29 |
29 |
* circular dependency issues, this class requires that the developer (which |
30 |
30 |
* means YOU) set the RoomController reference member manually. |
31 |
31 |
* Be advised that the setRoomController() method will not accept a null |
32 |
32 |
* pointer. |
33 |
33 |
* |
34 |
34 |
* Once again: Set the RoomController reference manually ASAP. Failure to do |
35 |
35 |
* so will cause this program to blow up. You've been warned. |
36 |
36 |
* @see ReservationController.setRoomController |
37 |
37 |
*/ |
38 |
38 |
public ReservationController() { |
39 |
39 |
this.reservations = new HashSet<>(); |
40 |
40 |
} |
41 |
41 |
|
42 |
42 |
/** |
43 |
43 |
* Returns a copy of all Reservations. |
44 |
44 |
* Emphasis on "copy"; There is no setReservations() for a reason, using |
45 |
45 |
* this to edit the pointer variable would omit the use of 'private'. |
46 |
46 |
* @return A verbatim copy of all Reservations. |
47 |
47 |
*/ |
48 |
48 |
@SuppressWarnings("unchecked") |
49 |
49 |
public Set<Reservation> getReservations() { |
50 |
50 |
return (HashSet<Reservation>)this.reservations.clone(); |
51 |
51 |
} |
52 |
52 |
|
53 |
53 |
/** |
54 |
54 |
* Generates a unique ID for a new Reservation. |
55 |
55 |
* This method is to be called when a new Reservation is to be stored. |
56 |
56 |
* As every Reservation carries an ID, this method searches for a unique |
57 |
57 |
* one, and returns it. |
58 |
58 |
* @return An integer, different from all other stored Reservation IDs. |
59 |
59 |
*/ |
60 |
60 |
public int generateReservationID() { |
61 |
61 |
/* Small optimization idea: |
62 |
62 |
* Instead of starting from 0, and incrementing until it's unique, it |
63 |
63 |
* will take the amount of stored Reservations, and test that as an ID. |
64 |
64 |
* This may still overlap (by removing an older Reservation), but may be |
65 |
65 |
* much faster. Consider implemting and testing for effectiveness if |
66 |
66 |
* generating an ID takes too long. |
67 |
67 |
*/ |
68 |
68 |
int ID = 0; |
69 |
69 |
boolean isUnique = false; |
70 |
70 |
do { |
71 |
71 |
ID++; |
72 |
72 |
isUnique = true; |
73 |
73 |
for(Reservation reservation: this.reservations) { |
74 |
74 |
if(ID == reservation.getReservationID()) { |
75 |
75 |
isUnique = false; |
76 |
76 |
} |
77 |
77 |
} |
78 |
78 |
} while(!isUnique); |
79 |
79 |
// Test: |
80 |
80 |
for(Reservation reservation: this.reservations) { |
81 |
81 |
assert reservation.getReservationID() != ID : "Duplicate ID generated!"; |
82 |
82 |
} |
83 |
83 |
return ID; |
84 |
84 |
} |
85 |
85 |
|
86 |
86 |
/** |
87 |
87 |
* Check if Reservation can be made. |
88 |
88 |
* Call this method whenever you're planning on adding a new Reservation. It |
89 |
89 |
* will check the provided requirements (Room facilities, for example), and |
90 |
90 |
* return whether this Reservation can continue. |
91 |
91 |
* @param reservation The Reservation to check for possible addition. |
92 |
92 |
* @pre reservation mustn't be null. |
93 |
93 |
* @throws NullPointerException if reservation is null. |
94 |
94 |
* @return True if reservation can be added, False otherwise. |
95 |
95 |
*/ |
96 |
96 |
public boolean checkReservation(Reservation reservation) { |
97 |
97 |
if(reservation == null) { |
98 |
98 |
throw NullPointerException("reservation was a null pointer."); |
99 |
99 |
} |
100 |
100 |
if(this.getRoomController().getQualifiedRooms(reservation).size() == 0) { |
101 |
101 |
return null; |
102 |
102 |
} |
+ |
103 |
} |
103 |
104 |
|
104 |
105 |
/** |
105 |
106 |
* Adds and confirms the reservation. |
106 |
107 |
* By calling this method, the given reservation will be stored in the |
107 |
108 |
* system, and the given room will be filled. |
108 |
109 |
* You are to collect the necessary data, and assure yourself that all |
109 |
110 |
* preconditions have been met. |
110 |
111 |
* |
111 |
112 |
* The best way to accomplish this, is to only send 'sterile' Reservations |
112 |
113 |
* as the first parameter. That is: Reservations without reserved Beds, |
113 |
114 |
* without prior addition to the active Reservations, etc. |
114 |
115 |
* NOTE: You must check for yourself if the Reservation can take place in |
115 |
116 |
* this Room! If there are not enough Beds available, an exception will be |
116 |
117 |
* thrown. |
117 |
118 |
* @param reservation The Reservation to add. |
118 |
119 |
* @param room The Room in which the people will reside. |
119 |
120 |
* @pre room must have enough empty beds. |
120 |
121 |
* @pre room must accomodate the Reservation's requirements. |
121 |
122 |
* @pre No parameter must be null. |
122 |
123 |
* @pre reservation mustn't already be stored in the active Reservations. |
123 |
124 |
* @post reservation is stored in the active Reservations. |
124 |
125 |
* @post The Beds in the provided Room are reserved for the Reservation's |
125 |
126 |
* period. |
126 |
127 |
* @throws IllegalArgumentException if the given Room can't fulfill the |
127 |
128 |
* requirements of the Reservation, or occupants is less than 1. |
128 |
129 |
* @throws NullPointerException if any parameter is a null pointer. |
129 |
130 |
*/ |
130 |
131 |
public void addReservation(Reservation reservation, Room room) { |
131 |
132 |
// Contract validation. Null pointers are implicitely checked by calling |
132 |
133 |
// methods. |
133 |
134 |
if(reservation.getPeople() > room.getEmptyBeds(reservation.getBegin(), reservation.getEnd()).size()) |
134 |
135 |
throw IllegalArgumentException("The given Room has not enough empty Beds for the Reservation period."); |
135 |
136 |
if(!this.getRoomController().getQualifiedRooms(reservation).contains(room)) |
136 |
137 |
throw IllegalArgumentException("The given Room cannot meet all requirements of the Reservation."); |
137 |
138 |
if(this.getReservations().contains(reservation)) |
138 |
139 |
throw IllegalArgumentException("The given Reservation was already included in the active Reservations."); |
139 |
140 |
// Contract validated |
140 |
141 |
this.reservations.add(reservation); |
141 |
142 |
Bed[] beds = room.getEmptyBeds(reservation.getBegin(), reservation.getEnd()).toArray(new Bed[1]); |
142 |
143 |
Set<Bed> reservedBeds = new HashSet<>(); |
143 |
144 |
for(int i=0; i<reservation.getPeople(); i++) { |
144 |
145 |
beds[i].reservePeriod(reservation.getBegin(), reservation.getEnd()); |
145 |
146 |
reservedBeds.add(beds[i]); |
146 |
147 |
} |
147 |
148 |
reservation.setReservedBeds(reservedBeds); |
148 |
149 |
} |
149 |
150 |
|
150 |
151 |
/** |
151 |
152 |
* Cancels and removes the given Reservation. |
152 |
153 |
* If you want to remove a Reservation, use this method, and provide the |
153 |
154 |
* Reservation up for removal. |
154 |
155 |
* This method will take care of related actions, such as releasing Beds. |
155 |
156 |
* @param reservation The Reservation to be removed. |
156 |
157 |
* @pre reservation mustn't be null. |
157 |
158 |
* @pre reservation must be contained in the active Reservations. |
158 |
159 |
* @post The Reservation is removed from the active Reservations. |
159 |
160 |
* @post The Beds, previously reserved for this Reservation, are now |
160 |
161 |
* released, and can be reserved for another Reservation. |
161 |
162 |
* @throws NullPointerException if reservation is a null pointer. |
162 |
163 |
* @throws IllegalArgumentException if reservation is not contained in the |
163 |
164 |
* active Reservations. |
164 |
165 |
*/ |
165 |
166 |
public void cancelReservation(Reservation reservation) { |
166 |
167 |
// Contract validation |
167 |
168 |
if(!this.getReservations().contains(reservation)) { |
168 |
169 |
throw IllegalArgumentException("The given Reservation was not contained in the active Reservations."); |
169 |
170 |
} |
170 |
171 |
if(reservation == null) { |
171 |
172 |
throw NullPointerException(); |
172 |
173 |
} |
173 |
174 |
// Contract validated, execute method |
174 |
175 |
this.reservations.remove(reservation); // Remove from active Reservations |
175 |
176 |
for(Bed bed: reservation.getReservedBeds()) { // Release reserved Beds |
176 |
177 |
bed.removeReservationPeriod(reservation.getBegin(), reservation.getEnd()); |
177 |
178 |
} |
178 |
179 |
// Asserting post conditions are met |
179 |
180 |
assert !this.getReservation().contains(reservation) : "The reservation is still part of the active Reservations."; |
180 |
181 |
for(Bed bed: reservation.getReservedBeds()) { |
181 |
182 |
assert bed.isFree(reservation.getBegin(), reservation.getEnd()) : "One or more of the Beds are still reserved."; |
182 |
183 |
} |
183 |
184 |
} |
184 |
185 |
|
185 |
186 |
} |
186 |
187 |
Challenge 6/ReservationView.java ¶
12 additions and 1 deletion.
View changes Hide changes
1 |
1 |
import java.util.Set; |
2 |
2 |
import java.util.HashSet; |
3 |
3 |
import java.util.Date; |
4 |
4 |
/** |
5 |
5 |
* Creates a view screen that allows interaction with the reservation. |
6 |
6 |
* This window will place the reservation details in editable fields, so its |
7 |
7 |
* state can be adapted. |
8 |
8 |
* It then also adds a couple of buttons; add/update/cancel, with their |
9 |
9 |
* respective actions. These will then be passed to the ReservationController. |
10 |
10 |
* It will not allow to add/update a reservation if dates overlap, i.e. beds |
11 |
11 |
* can't be reserved for that period. |
12 |
12 |
* @author Maarten Vangeneugden - 1438256 |
13 |
13 |
*/ |
14 |
14 |
public class ReservationView { |
15 |
15 |
|
16 |
16 |
private Reservation reservation; |
17 |
17 |
private Window window; |
18 |
18 |
|
19 |
19 |
// Controllers for communication with the rest |
20 |
20 |
private ReservationController rc; |
21 |
21 |
private RoomController roc; |
22 |
22 |
|
23 |
23 |
// GUI widgets |
24 |
24 |
private JTextField nameField; |
25 |
25 |
private JSpinner amountPeopleField; |
26 |
26 |
private JTextField dateField; |
27 |
27 |
private JSpinner durationField; |
28 |
28 |
private JRadioButton[] typeField; |
29 |
29 |
private JCheckBox showerField; |
30 |
30 |
private JCheckBox bathField; |
31 |
31 |
private JCheckBox minibarField; |
32 |
32 |
private JCheckBox aircoField; |
33 |
33 |
|
34 |
34 |
|
35 |
35 |
/** |
36 |
36 |
* Construct a window to edit a Reservation. |
37 |
37 |
* This window presents its caller with the necessary tools to edit the |
38 |
38 |
* given Reservation, and save it in the system. |
39 |
39 |
* |
40 |
40 |
* By sending it an empty Reservation, you can add a new Reservation to the |
41 |
41 |
* system. |
42 |
42 |
* This class is built in a way that allows it to be used for both creating |
43 |
43 |
* new Reservations, and updating/changing existing Reservations. |
44 |
44 |
* Existing Reservations can also be removed by clicking the appropriate |
45 |
45 |
* button. |
46 |
46 |
* @param reservation The Reservation that will be updated. |
47 |
47 |
* @param rc The ReservationController class. |
48 |
48 |
* @param roc The RoomController class. |
49 |
49 |
* @pre No parameter may be a null pointer. |
50 |
50 |
* @post A window is displayed with GUI widgets, which are filled in |
51 |
51 |
* according to the state of reservation. |
52 |
52 |
* @throws NullPointerException if any parameter is a null pointer. |
53 |
53 |
*/ |
54 |
54 |
public ReservationView(Reservation reservation, ReservationController rc, RoomController roc) { |
55 |
55 |
// Contract validation |
56 |
56 |
if(rc == null || roc == null || reservation == null) |
57 |
57 |
throw NullPointerException("One or more of the given parameters is a null pointer."); |
58 |
58 |
// Contract validated |
59 |
59 |
this.rc = rc; |
60 |
60 |
this.roc = roc; |
61 |
61 |
|
62 |
62 |
this.reservation = reservation; |
63 |
63 |
this.window = new Window("Reservation screen"); |
64 |
64 |
this.addFields(); |
65 |
65 |
} |
66 |
66 |
|
67 |
67 |
public void setReservation(Reservation reservation) { |
68 |
68 |
this.reservation = reservation; |
69 |
69 |
} |
70 |
70 |
|
71 |
71 |
public Reservation getReservation() { |
72 |
72 |
return reservation; |
73 |
73 |
} |
74 |
74 |
|
75 |
75 |
/** |
76 |
76 |
* Add the necessary fields to the window. |
77 |
77 |
* This method inspects the Reservation, and copies the included information |
78 |
78 |
* to the widget's content. |
79 |
79 |
*/ |
80 |
80 |
private void addFields() { |
81 |
81 |
this.window.createLabel("Group name"); |
82 |
82 |
this.nameField = window.createTextField(this.reservation.getGroupName()); |
83 |
83 |
this.window.createLabel("Amount of people"); |
84 |
84 |
this.amountPeopleField = window.createSpinner(1, 20, this.reservation.getPeople(), 1); |
85 |
85 |
// Formatting date for the date field: |
86 |
86 |
this.window.createLabel("Begin date"); |
87 |
87 |
this.beginDateField = window.createTextField(this.reservation.getBegin().toString()); |
88 |
88 |
this.window.createLabel("End date"); |
89 |
89 |
this.endDateField = window.createTextField(this.reservation.getEnd().toString()); |
90 |
90 |
String[] types = {"Male", "Female", "Mixed"}; |
91 |
91 |
this.typeField = this.window.createRadioButtons(types); |
92 |
92 |
// TODO: Add a test to see if the Reservation has indicated which type |
93 |
93 |
// it is, and then already check the corresponding radio button. |
94 |
94 |
this.showerField = this.window.createCheckbox("Shower"); |
95 |
95 |
this.bathField = this.window.createCheckbox("Bath"); |
96 |
96 |
this.minibarField = this.window.createCheckbox("Minibar"); |
97 |
97 |
this.aircoField = this.window.createCheckbox("Airco"); |
98 |
98 |
// TODO: Idem for the facilities, test if already in Reservation, and |
99 |
99 |
// check accordingly. |
100 |
100 |
this.window.createButton("Add/Update reservation", "", "addReservation", this); |
101 |
101 |
this.window.createButton("Remove reservation", "", "removeReservation", this); |
102 |
102 |
} |
103 |
103 |
|
104 |
104 |
/** |
105 |
105 |
* Start the addition of this Reservation to the system. |
106 |
106 |
* This method will check the current fields, and update the Reservation |
107 |
107 |
* with those values. In doing so, the validity of the values is checked |
108 |
108 |
* (formatting, begin date before end date, ...). |
109 |
109 |
* If everything is okay, the method will check if these changes are |
110 |
110 |
* possible in the current system; are there enough empty beds? |
111 |
111 |
* |
112 |
112 |
* If everything is okay, the changes will be propagated to the |
113 |
113 |
* Reservation's state, and (if it's a new Reservation) will be added to the |
114 |
114 |
* system's active Reservations. |
115 |
115 |
*/ |
116 |
116 |
public void addReservation() { |
117 |
117 |
// Collect all data from the fields |
118 |
118 |
String name = this.nameField.getText(); |
119 |
119 |
Date beginDate = null, endDate = null; |
120 |
120 |
try { |
121 |
121 |
beginDate = DataFormat.parse(this.beginDateField.getText()); |
122 |
122 |
endDate = DataFormat.parse(this.endDateField.getText()); |
123 |
123 |
} |
124 |
124 |
catch(ParseException e) { |
125 |
125 |
this.window.messageDialog("Not all date fields were properly formatted."); |
126 |
126 |
return; |
127 |
127 |
} |
128 |
128 |
int people = (Integer)this.amountPeopleField.getValue(); |
129 |
129 |
String type = ""; |
130 |
130 |
for(int i=0; i<this.typeField.length; i++) { |
131 |
131 |
if(this.typeField[i].isSelected()) { |
132 |
132 |
type = this.typeField[i].getText(); |
133 |
133 |
break; |
134 |
134 |
} |
135 |
135 |
} |
136 |
136 |
Set<String> facilities = new HashSet<>(); |
137 |
137 |
if(this.showerField.isSelected()) { |
138 |
138 |
facilities.add("Shower"); |
139 |
139 |
} |
140 |
140 |
if(this.bathField.isSelected()) { |
141 |
141 |
facilities.add("Bath"); |
142 |
142 |
} |
143 |
143 |
if(this.minibarField.isSelected()) { |
144 |
144 |
facilities.add("Minibar"); |
145 |
145 |
} |
146 |
146 |
if(this.aircoField.isSelected()) { |
147 |
147 |
facilities.add("Airco"); |
148 |
148 |
} |
149 |
149 |
|
150 |
150 |
// TODO breakfast days handling |
151 |
151 |
|
152 |
152 |
//Set<Room> possibleRooms = this.roc.getQualifiedRooms(actualDate, duration, type, facilities); |
153 |
153 |
// TODO: Refactor upper method to work with the Reservation. |
154 |
154 |
// TODO: Implement all checks of valid data above this line! |
155 |
155 |
|
156 |
156 |
// Data validated; Try finding appropriate beds |
157 |
157 |
if(possibleRooms.size() == 0) { |
158 |
158 |
boolean tryAgain = this.window.confirmDialog("No rooms met the requirements! Would you like to continue and change the parameters?"); |
159 |
159 |
if(!tryAgain) { |
160 |
160 |
// TODO close window |
161 |
161 |
} |
162 |
162 |
} |
163 |
163 |
else { |
164 |
164 |
Room pickedRoom = null; |
165 |
165 |
for(Room room: possibleRooms) { |
166 |
166 |
if(room.getEmptyBeds(actualDate, actualEndDate).size() < room.getBeds().size()) { |
167 |
167 |
// First, fill the rooms that are partially filled. |
168 |
168 |
pickedRoom = room; |
169 |
169 |
break; |
170 |
170 |
} |
171 |
171 |
} |
172 |
172 |
if(pickedRoom == null) { // If still no room, pick an empty room |
173 |
173 |
for(Room room: possibleRooms) { |
174 |
174 |
if(room.getEmptyBeds(actualDate, actualEndDate).size() >= amount) { |
175 |
175 |
pickedRoom = room; |
176 |
176 |
break; |
177 |
177 |
} |
178 |
178 |
} |
179 |
179 |
} |
180 |
180 |
assert pickedRoom != null; |
181 |
181 |
// TODO: Set reservation fields here! |
182 |
182 |
this.rc.addReservation(reservation, pickedRoom); |
183 |
183 |
|
184 |
184 |
// Confirm and show price: |
185 |
185 |
int price = this.reservation.getPrice(); |
186 |
186 |
this.window.confirmDialog("Reservation confirmed! Price: " +String.valueOf(price)); |
187 |
187 |
} |
188 |
188 |
|
189 |
189 |
} |
190 |
190 |
|
191 |
191 |
public void removeReservation() { |
+ |
192 |
* Remove Reservation and the GUI. |
+ |
193 |
* Calling this method will remove the Reservation, followed by closing this |
+ |
194 |
* window. |
+ |
195 |
* If the Reservation is active (i.e. saved in the ReservationController), |
+ |
196 |
* it will be removed. |
+ |
197 |
* Any changes will be cancelled. |
+ |
198 |
*/ |
+ |
199 |
public void removeReservation() { |
192 |
200 |
// TODO: Close the window. That's all. |
193 |
- | } |
+ |
201 |
this.rc.cancelReservation(this.reservation); |
+ |
202 |
} |
+ |
203 |
this.window.close(); |
+ |
204 |
} |
194 |
205 |
} |
195 |
206 |
Challenge 6/Room.java ¶
1 addition and 1 deletion.
View changes Hide changes
1 |
1 |
import java.util.HashSet; |
2 |
2 |
import java.util.Date; |
3 |
3 |
|
4 |
4 |
/** |
5 |
5 |
* A room in a hostel. |
6 |
6 |
* Room represents just that: A room. |
7 |
7 |
* A room contains a set of Beds, facilities that can be used, etc. |
8 |
8 |
* It's highly decoupled: Apart from holding a set of Beds, the only members |
9 |
9 |
* types consist of those that are available in every OpenJDK implementation. |
10 |
10 |
* @author Maarten Vangeneugden - 1438256 |
11 |
11 |
*/ |
12 |
12 |
public class Room { |
13 |
13 |
|
14 |
14 |
private HashSet<Bed> beds; |
15 |
15 |
private String type; |
16 |
16 |
private HashSet<String> facilities; |
17 |
17 |
|
18 |
18 |
/** |
19 |
19 |
* Create a new Room. |
20 |
20 |
* @param beds The amount of Beds that will be placed in this Room. |
21 |
21 |
* @param type The type of this Room. (for example: Male, Female, Mixed, |
22 |
22 |
* ...) |
23 |
23 |
* @param facilities A Set of facilities this Room provides ("Shower", |
24 |
24 |
* "Radio", "Overpriced WiFi", ...) |
25 |
25 |
* @pre No parameter must be a null pointer. |
26 |
26 |
* @pre beds must be at least 1. |
27 |
27 |
* @pre Type mustn't be an empty String. |
28 |
28 |
* @pre No facility may be an empty String. |
29 |
29 |
* @post The Room will receive the provided amount of Beds, which are all |
30 |
30 |
* completely released. |
31 |
31 |
* @throws IllegalArgumentException if the amount of Beds is less than 1, or |
32 |
32 |
* one of the facilities is an empty String. |
33 |
33 |
* @throws NullPointerException if one of the parameters is a null pointer. |
34 |
34 |
*/ |
35 |
35 |
public Room(int beds, String type, Set<String> facilities) { |
36 |
36 |
// Contract validation happens in the setter methods |
37 |
37 |
this.setFacilities(facilities); |
38 |
38 |
this.setType(type) |
39 |
- | if(beds < 1) { |
+ |
39 |
if(beds < 1) { |
40 |
40 |
throw IllegalArgumentException("Beds was less than 1."); |
41 |
41 |
} |
42 |
42 |
// Contract validated, execute constructor |
43 |
43 |
this.beds = new HashSet<>(); |
44 |
44 |
for(int i=0; i<beds; i++) { |
45 |
45 |
this.beds.add(new Bed()); |
46 |
46 |
} |
47 |
47 |
} |
48 |
48 |
|
49 |
49 |
/** |
50 |
50 |
* Remove a Bed from this Room. |
51 |
51 |
* This method will remove the given Bed from this Room. |
52 |
52 |
* However, make sure that this Bed is free of any reservations, as you |
53 |
53 |
* can't delete a Bed with a reserved period. |
54 |
54 |
* @see Bed |
55 |
55 |
* @param bed The Bed to be removed. |
56 |
56 |
* @pre bed mustn't be null. |
57 |
57 |
* @pre bed mustn't be the last Bed in this Room. |
58 |
58 |
* @pre bed must be in this Room. |
59 |
59 |
* @pre bed mustn't have any reserved periods. |
60 |
60 |
* @post The amount of Beds in this Room is decremented by 1. |
61 |
61 |
* @throws IllegalArgumentException if bed is not in this Room, or has |
62 |
62 |
* reserved periods, or this is the last Bed in the Room. |
63 |
63 |
* @throws NullPointerException if bed is a null pointer. |
64 |
64 |
*/ |
65 |
65 |
public void removeBed(Bed bed) { |
66 |
66 |
// Contract validation |
67 |
67 |
if(!this.getBeds().contains(bed)) { |
68 |
68 |
throw IllegalArgumentException("The given Bed is not in this Room."); |
69 |
69 |
} |
70 |
70 |
if(bed.hasReservations()) { |
71 |
71 |
throw IllegalArgumentException("The given Bed still has reserved periods."); |
72 |
72 |
} |
73 |
73 |
if(this.getBeds().size() == 1) { |
74 |
74 |
throw IllegalArgumentException("Deleting this Bed would empty the Room."); |
75 |
75 |
} |
76 |
76 |
// Contract validated, execute method |
77 |
77 |
this.beds.remove(bed); |
78 |
78 |
// Assert post conditions |
79 |
79 |
assert !this.getBeds().contains(bed) : "The given Bed was not removed from this Room."; |
80 |
80 |
} |
81 |
81 |
|
82 |
82 |
/** |
83 |
83 |
* Add a Bed to this Room. |
84 |
84 |
* @post The amount of Beds in this Room increments with 1. |
85 |
85 |
*/ |
86 |
86 |
public void addBed() { |
87 |
87 |
this.beds.add(new Bed()); |
88 |
88 |
} |
89 |
89 |
|
90 |
90 |
/** |
91 |
91 |
* Returns a copy of all Beds. |
92 |
92 |
* @return A verbatim copy of all Beds. |
93 |
93 |
*/ |
94 |
94 |
@SuppressWarnings("unchecked") |
95 |
95 |
public Set<Bed> getBeds() { |
96 |
96 |
return (HashSet<Bed>) this.beds.clone(); |
97 |
97 |
} |
98 |
98 |
|
99 |
99 |
/** |
100 |
100 |
* Set the type of this Room. |
101 |
101 |
* @param type The new type of the Room. |
102 |
102 |
* @pre type mustn't be an empty String, or a null pointer. |
103 |
103 |
* @post The Room's type is changed to the given type. |
104 |
104 |
* @throws IllegalArgumentException if type is an empty String. |
105 |
105 |
* @throws NullPointerException if type is a null pointer. |
106 |
106 |
*/ |
107 |
107 |
public void setType(String type) { |
108 |
108 |
if(type.isEmpty()) { |
109 |
109 |
throw IllegalArgumentException("type is an empty String."); |
110 |
110 |
} |
111 |
111 |
this.type = type; |
112 |
112 |
} |
113 |
113 |
|
114 |
114 |
/** |
115 |
115 |
* Returns the type of this Room. |
116 |
116 |
* @return The secret launch codes of the USS Nimitz, granting access to |
117 |
117 |
* nuclear warheads so big, you'll lose faith in humanity. |
118 |
118 |
*/ |
119 |
119 |
public String getType() { |
120 |
120 |
return type; |
121 |
121 |
} |
122 |
122 |
|
123 |
123 |
/** |
124 |
124 |
* Set the facilities available in this Room. |
125 |
125 |
* @param facilities The set of facilities in this Room. |
126 |
126 |
* @pre No facility must be an empty String or a null pointer. |
127 |
127 |
* @post The Room will have the newly provided facilities. |
128 |
128 |
* @throws IllegalArgumentException if one of the facilities is an empty String. |
129 |
129 |
* @throws NullPointerException if any given variable is a null pointer. |
130 |
130 |
*/ |
131 |
131 |
public void setFacilities(Set<String> facilities) { |
132 |
132 |
for(String facility : facilities) { |
133 |
133 |
if(facility.isEmpty()) { |
134 |
134 |
throw IllegalArgumentException("A facility was an empty String."); |
135 |
135 |
} |
136 |
136 |
} |
137 |
137 |
this.facilities = facilities; |
138 |
138 |
} |
139 |
139 |
|
140 |
140 |
/** |
141 |
141 |
* Returns a copy of all facilities. |
142 |
142 |
* @return A verbatim copy of all facilities. |
143 |
143 |
*/ |
144 |
144 |
@SuppressWarnings("unchecked") |
145 |
145 |
public Set<String> getFacilities() { |
146 |
146 |
return (HashSet<String>)this.facilities.clone(); |
147 |
147 |
} |
148 |
148 |
|
149 |
149 |
/** |
150 |
150 |
* Search for Beds that can be reserved. |
151 |
151 |
* This method will look through all Beds, and check whether they can be |
152 |
152 |
* reserved in the given period. You will receive all Beds that can be |
153 |
153 |
* reserved. |
154 |
154 |
* @param begin The begin date. |
155 |
155 |
* @param end The end date. |
156 |
156 |
* @pre No parameter must be null. |
157 |
157 |
* @pre begin must strictly come before end. |
158 |
158 |
* @throws IllegalArgumentException if begin comes after end, or both are |
159 |
159 |
* equal. |
160 |
160 |
* @throws NullPointerException if any given parameter is a null pointer. |
161 |
161 |
* @return A Set of all Beds that can be reserved, or an empty Set if no Bed |
162 |
162 |
* can be reserved in the given period. |
163 |
163 |
*/ |
164 |
164 |
public Set<Bed> getEmptyBeds(Date begin, Date end) { |
165 |
165 |
// Contract validation |
166 |
166 |
if(!begin.before(end)) { |
167 |
167 |
throw IllegalArgumentException("begin does not come strictly before end"); |
168 |
168 |
} |
169 |
169 |
// Validated |
170 |
170 |
Set<Bed> emptyBeds = new HashSet<>(); |
171 |
171 |
for(Bed bed: this.getBeds()) { |
172 |
172 |
if(bed.isFree(begin, end)) { |
173 |
173 |
emptyBeds.add(bed); |
174 |
174 |
} |
175 |
175 |
} |
176 |
176 |
return emptyBeds; |
177 |
177 |
} |
178 |
178 |
|
179 |
179 |
} |
180 |
180 |
Challenge 6/ontwerpkeuzes2.md ¶
60 additions and 0 deletions.
View changes Hide changes
1 |
1 |
|
2 |
2 |
## GRASP |
3 |
3 |
Om zoveel mogelijk tegemoet te komen aan de GRASP-richtlijnen, heb ik volgende |
4 |
4 |
ontwerpkeuzes toegepast voor de verschillende richtlijnen. |
5 |
5 |
|
6 |
6 |
### High cohesion & Low coupling |
7 |
7 |
- Veel classes zijn totaal van elkaar losgekoppeld. Een goed voorbeeld hiervan |
8 |
8 |
is Bed; Bed weet niet over dat er Reservaties bestaan, zelfs niet in welke |
9 |
9 |
Room het 'staat'. |
10 |
10 |
- Als er toch gekoppeld moet worden, dan blijft dit tot een minimum beperkt; Een |
11 |
11 |
Room weet wel welke Bedden het heeft, maar niet hoe deze gebruikt worden, een |
12 |
12 |
Reservation weet wel welke Bedden voor hem gereserveerd zijn, maar heeft geen |
13 |
13 |
weet van welke kamer dat deze staat. |
14 |
14 |
- Uitzondering op deze regel vormen de Controller-classes; omwille van hun taken |
15 |
15 |
zijn deze vaak verplicht om van veel verschillende classes op de hoogte te |
16 |
16 |
zijn. |
17 |
17 |
|
18 |
18 |
### Indirectie |
19 |
19 |
- In plaats van functionaliteit toe te wijzen aan class X om met class Y te |
20 |
20 |
interageren, wordt er vaak gebruik gemaakt van een class Z, die deze |
21 |
21 |
verantwoordelijkheid op zich neemt. |
22 |
22 |
- Dit wordt toegepast d.m.v. een MVC-structuur, waarin de \*Controller-classes |
23 |
23 |
als 'tussenpersoon' werken. |
24 |
24 |
- Dit komt ook tegemoet aan de eigenschappen van Information Expertise. |
25 |
25 |
|
26 |
26 |
### Creator |
27 |
27 |
- Controllers staan ook in als zgn. Creators van de models die ze beheren. Ze |
28 |
28 |
voldoen dan ook aan de eigenschappen zoals |
29 |
29 |
[hier](https://en.wikipedia.org/wiki/GRASP_(object-oriented_design)#Creator) |
30 |
30 |
opgesomd wordt. |
31 |
31 |
|
32 |
32 |
|
33 |
33 |
## SOLID |
34 |
34 |
|
35 |
35 |
### Single Responsibility |
36 |
36 |
|
37 |
37 |
## Algemene keuzes ter bevordering codekwaliteit |
38 |
38 |
|
39 |
39 |
### Null pointers |
+ |
40 |
In het programma zijn er sommige members die doen vermoeden dat ze beter als |
+ |
41 |
Enums aangeduid kunnen worden; Het type van een kamer, faciliteiten, ... |
+ |
42 |
|
+ |
43 |
In plaats van een speciale class hiervoor te maken, heb ik gekozen om deze |
+ |
44 |
gewoon te behandelen als simpele Strings in lijsten. |
+ |
45 |
|
+ |
46 |
Het voordeel aan deze werkwijze, is dat Strings in Java SE quasi universeel |
+ |
47 |
voorkomen; data kunnen geconverteerd worden naar Strings (en vice versa), veel |
+ |
48 |
gebruikte GUI-libraries (waaronder Swing, JavaFX, ...) gebruiken Strings voor |
+ |
49 |
tekst voor te stellen in de GUI-widgets, ... |
+ |
50 |
Daarnaast weet iedereen die ook maar een beetje geschoold is in Java, direct hoe |
+ |
51 |
deze members behandeld kunnen worden. Had ik een class gemaakt die bv. |
+ |
52 |
RoomFacilities heette, dan is die parate kennis over de taal zelf nutteloos. |
+ |
53 |
|
+ |
54 |
Strings zijn ook voorzien van extreem veel methods, en zijn dus zeer flexibele |
+ |
55 |
classes om te gebruiken. Het feit dat ze zo makkelijk doorheen het programma te |
+ |
56 |
gebruiken zijn is dus een groot pluspunt. |
+ |
57 |
|
+ |
58 |
Als dat nog niet genoeg is: Het gebruiken van de hulpmiddelen uit de _standard |
+ |
59 |
library_ is niet enkel waarvoor libraries überhaupt bestaan, het beperkt ook |
+ |
60 |
het benodigde aantal classes in de eigen software, waardoor het geheel |
+ |
61 |
uiteindelijk veel overzichtelijker blijft, en bijgevolg, makkelijk uit te |
+ |
62 |
breiden, te testen, te begrijpen, ... |
+ |
63 |
|
+ |
64 |
Al deze voordelen graan grotendeels verloren als ik beslis om zelf |
+ |
65 |
gespecialiseerde classes op te stellen. |
+ |
66 |
Men kan misschien stellen dat "de voordelen die het schrijven van eigen |
+ |
67 |
classes veel beter tegemoet komt aan de essentie van object-georiënteerd |
+ |
68 |
programmeren, dan zich _beperken_ tot slechts een handvol generieke classes, die |
+ |
69 |
misschien niet volledig aan de benodigdheden beantwoorden". |
+ |
70 |
|
+ |
71 |
Toch laat ik mijn afweging overslaan in het voordeel van minder classes, en meer |
+ |
72 |
uniformiteit. _Sometimes, less is more._ Meer classes betekent ook: |
+ |
73 |
- Meer onderhoud |
+ |
74 |
- Meer kans op bugs |
+ |
75 |
- Groter programma |
+ |
76 |
|
+ |
77 |
En al die problemen resulteren op lange termijn in: |
+ |
78 |
- Slechtere uitbreidbaarheid |
+ |
79 |
- Slechtere onderhoudbaarheid |
+ |
80 |
- Slechtere schaalbaarheid |
+ |
81 |
|
+ |
82 |
Zijn al die problemen de moeite van een (zogezegd) "beter object-georiënteerd |
+ |
83 |
design" wel waard? |
+ |
84 |
Veel mensen stellen juist dat OOP net voordelig is als men op zoek is naar |
+ |
85 |
- Uitbreidbaarheid |
+ |
86 |
- Onderhoudbaarheid |
+ |
87 |
- Modulariteit |
+ |
88 |
- Schaalbaarheid |
+ |
89 |
|
+ |
90 |
Als de voordelen van het paradigma verdwijnen als ik dat "beter design" volg, |
+ |
91 |
is dat design dan wel echt beter? |
+ |
92 |
|
+ |
93 |
|
+ |
94 |
**Conclusie: Uniforme classes zijn volgens mij soms beter dan een stel |
+ |
95 |
gespecialiseerde classes. Daarom dat ik mij soms behelp met Strings, intergers, |
+ |
96 |
... i.p.v. zelf een oplossing te schrijven.** |
+ |
97 |
|
+ |
98 |
|
+ |
99 |
### Null pointers |
40 |
100 |
Het valt misschien op dat ik doorheen mijn programma in veel contracten eis dat |
41 |
101 |
geen enkele parameter een zgn. *null pointer* is. |
42 |
102 |
|
43 |
103 |
Het gebruik van null pointers staat garant voor een overvloed aan moeilijk te |
44 |
104 |
vinden bugs die (door het design van objectgeörienteerd programmeren) enorm diep |
45 |
105 |
kunnen doorpropageren. |
46 |
106 |
|
47 |
107 |
Ik maak er een kerntaak van dat, als er aan mijn programma gewerkt wordt, de |
48 |
108 |
programmeur zichzelf steeds kan garanderen dat **alle** data die hij |
49 |
109 |
ontvangt, valide data is. |
50 |
110 |
Op deze manier valt er een hele last van de schouders van de programmeur; een |
51 |
111 |
reeks fouten worden voorkomen, simpelweg door een strikt schema aan te houden. |
52 |
112 |
|
53 |
113 |
Het controleren op null pointers wordt op 2 manieren gedaan: |
54 |
114 |
- Gebruik van *methods* aanwezig in het gegeven type. Als de gegeven variabele |
55 |
115 |
een null pointer is, zal het programma direct crashen, en een |
56 |
116 |
NullPointerException geven. |
57 |
117 |
- Expliciet testen of *var == null*. Wordt toegepast op parameters die direct |
58 |
118 |
als members opgeslagen dienen te worden. |
59 |
119 |
|
60 |
120 |
Deze (contractuele) controle laat toe dat, mocht er een null pointer gebruikt |
61 |
121 |
worden, het programma de programmeur hiervan direct op de hoogte stelt, en dit |
62 |
122 |
op het laagst mogelijke niveau (namelijk de eerste method waar deze waarde |
63 |
123 |
gebruikt wordt). |
64 |
124 |
|
65 |
125 |
### Cloning |
66 |
126 |
members 'private' maken (encapsuleren) is volledig nutteloos als men getters en |
67 |
127 |
setters op deze members toepast; In Java worden references doorgegeven (m.u.v. |
68 |
128 |
primitives), die de hele notie van encapsulatie voorbijgaan (bij sommige types). |
69 |
129 |
Een voorbeeld hiervan is het privatiseren van een Set<T>-member: men kan daar |
70 |
130 |
een 'getSet()'-method op plaatsen, en dan toch de inhoud van deze 'private' |
71 |
131 |
aanpassen. |
72 |
132 |
|
73 |
133 |
Ik heb geopteerd om, waar van toepassing, deze variabelen te 'clonen', om zo |
74 |
134 |
exacte kopieën terug te geven. |
75 |
135 |
Deze manier van werken brengt enkele voordelen teweeg: |
76 |
136 |
- Zeer defensief programmeren; De ontwikkelaar kan geen members aanpassen als |
77 |
137 |
dat niet de bedoeling was |
78 |
138 |
- Duidelijkheid code: getSet().clear() zal de member niet meer leegmaken. Om dat |
79 |
139 |
te doen, moet men gebruikmaken van de method die daarvoor bedoeld is: |
80 |
140 |
setSet(clearedSet) |
81 |
141 |
|
82 |
142 |
### Inheritance |
83 |
143 |
Overerving is een goed concept over het algemeen, maar **niet** in OOP. |
84 |
144 |
De problemen omtrent impliciet gedrag en onnodige *state* zijn al te vaak |
85 |
145 |
beschreven met OOP-inheritance. |
86 |
146 |
|
87 |
147 |
Ik heb in mijn programma geen gebruik gemaakt van inheritance, exact omwille van |
88 |
148 |
de problemen die het voortbrengt, zeker in termen van herbruikbaarheid en |
89 |
149 |
robuustheid, wat toch zware vereisten waren voor deze opdracht. |
90 |
150 |
|
91 |
151 |
Ik heb al mijn problemen makkelijk kunnen oplossen d.m.v. compositie. |
92 |
152 |
|
93 |
153 |
### Benaming variabelen |
94 |
154 |
Doorheen mijn programma maak ik heel veel gebruik van dezelfde benamingen. |
95 |
155 |
Bijvoorbeeld: Een variabele van het type Reservation zal haast altijd |
96 |
156 |
'reservation' heten, een Set van een bepaald type zal de naam van datzelfde type |
97 |
157 |
dragen, in het meervoud, ... |
98 |
158 |
|
99 |
159 |
Sommige programmeurs gebruiken liever afkortingen (bv. 'reservation' --> |
100 |
160 |
'resv'), omdat dit sneller schrijft. |
101 |
161 |
|
102 |
162 |
Naar mijn mening moet men bij deze werkwijze inleveren aan leesbaarheid, vooral |
103 |
163 |
wanneer iemand die nog nooit met de code gewerkt heeft, dit programma moet |
104 |
164 |
overnemen. |
105 |
165 |
|
106 |
166 |
Daarnaast zorgt de consistentie van woordgebruik ervoor dat een andere |
107 |
167 |
programmeur, doorheen het hele programma dezelfde context in zijn/haar gedachten |
108 |
168 |
kan gebruiken. |
109 |
169 |
|
110 |
170 |
|
111 |
171 |
|
112 |
172 |
|
113 |
173 |
------------------------------------------------------------ TODO |
114 |
174 |
## Toepassing types/classes |
115 |
175 |
Doorheen het programma heb ik getracht zoveel mogelijk gebruik te maken van |
116 |
176 |
types/classes die |
117 |
177 |
- Veelvoorkomend zijn in de Java STL (Zoals String, Set, List, ...) |
118 |
178 |
- primitief zijn (ints, ...), omdat deze operatoren hebben en de code |
119 |
179 |
leesbaarder maken |
120 |
180 |
|
121 |
181 |
Een goed voorbeeld hiervan zijn bv. de faciliteiten: |
122 |
182 |
I.p.v. een aparte "Facility"-class te maken, heb ik de verschillende |
123 |
183 |
faciliteiten voorgesteld door een simpele String. De voordelen van deze aanpak |
124 |
184 |
zijn ook direct duidelijk: |
125 |
185 |
- Betekenis is direct duidelijk; de faciliteit letterlijk in de code vernoemd |
126 |
186 |
- Makkelijke interactie met GUI, die sowieso Strings vraagt voor bv. JLabel |
127 |
187 |
- Uitbreidbaarheid wordt bekomen door simpelweg een nieuwe String te |
128 |
188 |
introduceren |
129 |
189 |
|
130 |
190 |
## View en GUI |
131 |
191 |
Werken met GUI's is vaak tijdrovend en veroorzaakt snel errors, zeker met bv. |
132 |
192 |
anonieme methods, exceptions, ... |
133 |
193 |
Alhoewel mijn programma grotendeels in een MVC-stijl is geschreven, maken de |
134 |
194 |
view-classes (RegistrationView, SearchView, ...) achterliggend gebruik van een |
135 |
195 |
zelfgemaakt framework om makkelijk vensters te maken. |
136 |
196 |
Dit kleine framework is een persoonlijk hobbyproject dat ik JSugar noem. |
137 |
197 |
Het biedt een heleboel voordelen, vergeleken met elk GUI-venster zelf opstellen: |
138 |
198 |
- Vaak gebruikte GUI-widgets (zoals een label, textfield) worden aangemaakt en |
139 |
199 |
toegevoegd door slechts 1 method op te roepen |
140 |
200 |
- JSugar maakt gebruik van reflectie om op een leesbare en uitbreidbare manier |
141 |
201 |
knoppen te activeren: |
142 |
202 |
public JButton createButton(String text, String action, String methodName, Object triggerObject) |
143 |
203 |
'methodName' is een simpele String, en 'triggerObject' is het object waar deze |
144 |
204 |
method moet worden opgeroepen. |
145 |
205 |
- Automatische uitlijning van widgets |
146 |
206 |
Voor meer informatie kunt u JSugar-readme.md raadplegen. |
147 |
207 |
|
148 |
208 |
## java.util.Date |
149 |
209 |
|
150 |
210 |
Doorheen het programma maak ik vaak gebruik van de Date-class uit de Java STL, |
151 |
211 |
om de volgende redenen: |
152 |
212 |
- Uitbreidbaarheid: Door overal eenzelfde type te gebruiken, is het gemakkelijk |
153 |
213 |
om nieuwe modules toe te voegen, zonder dat daarvoor abstractielagen etc. |
154 |
214 |
nodig zijn. |
155 |
215 |
- Uniformiteit: Eenzelfde type uit de STL laat de ontwikkelaar toe om door het |
156 |
216 |
hele programma heen hetzelfde denkpatroon aan te houden; een |
157 |
217 |
Stringvoorstelling hier, een integer daar, ... veroorzaken problemen en bugs, |
158 |
218 |
die met deze class voorkomen worden. |
159 |
219 |
|
160 |
220 |
## Bedden |
161 |
221 |
|
162 |
222 |
Ik heb voor bedden een aparte class aangemaakt, omdat deze een bepaalde state |
163 |
223 |
bijhouden, nml. wanneer ze gereserveerd zijn. |
164 |
224 |
|
165 |
225 |
|
166 |
226 |
## Reservation |
167 |
227 |
Voor de reservaties heb ik besloten om enkel die data bij te houden die inherent |
168 |
228 |
gerelateerd is aan die reservatie: |
169 |
229 |
- Enkel gereserveerde bedden i.p.v. gereserveerde kamers. Qua uitbreidbaarheid |
170 |
230 |
heeft dit tot gevolg dat men gemakkelijk kan uitbreiden naar reservaties, |
171 |
231 |
gespreid over verschillende kamers. |
172 |
232 |
|
173 |
233 |
|
174 |
234 |
## ReservationView |
175 |
235 |
Merk op hoe in ReservationView de data van de Reservation direct in de GUI wordt |
176 |
236 |
geplaatst. |
177 |
237 |
Dit staat toe om zeer gemakkelijk deze class te hergebruiken voor zowel het |
178 |
238 |
**aanmaken** als het **updaten** van de reservatie. |
179 |
239 |