Hi, that looks quite a bit better! To answer your questions:
JaAnTr said:
I've made the discount class and the corporate discount inherited class but I'm not sure what they should be connected to? Is it the HotelBillingSystem class?
The discounts are attached to the Customer interface: each customer instance can have up to one discount associated with it: corporate customers will have a CorporateDiscount attached to them, for instance (others will just have none: null, I guess). Technically the most general implementation would accept arbitrarily many discounts, and combine them multiplicatively or additively somehow, but since you only have one type of discount this is unnecessary here.
Similarly for the extra charges, each customer instance can have up to one extra charge, here there is only one type of extra charge for the group bookings.
This ties into your second question...
JaAnTr said:
Secondly I'm not sure if I've done what you meant with the customer class? I've got it so the 3 different types of customer all use the customer class as an interface but at the moment they are all identical. I don't have a method for storing a discount that should be applied to that customer as I'm not sure if that is correct. Should the extra charge, seeing as it's only applied to group bookings also go in the 3 different customer classes?
The key here is to reason abstractly: if you consider a group customer, a corporate customer, and an individual customer, there is no fundamental difference between them besides the fact that some types of customers get discounts or charges others don't. So the three classes are, indeed, functionally identical. The only difference here would be in the constructor of each: the CorporateCustomer's constructor would take a name, address and phone as usual, but would then automatically attach the appropriate discount. Similarly for the GroupCustomer's constructor, which would attach the extra charge for groups. That way you are still using polymorphism, but you do not need to reason on the type of the customer: all the information needed to bill a customer is already in the interface (or, to be more specific, you have already reasoned on the customer's type: when you created it; in essence, instead of saying "I have a customer here, let me see what type of customer he is so I can bill him", you're saying "I am creating a corporate customer, and advertising that this customer has a 20% discount, 50% on weekends"). Note the flexibility in that you can effectively "customize" customers (no pun intended) by giving them custom discounts and charges without changing the underlying logic. This is what is referred to as
data-driven design. If, later on, you find that different types of customers should actually have more structural differences, the design may be reconsidered, but this seems unlikely for a Customer class which almost by construction shouldn't really be "doing" anything but should instead be a simple data container, a record of sorts. In fact this means that the Customer interface should probably be an abstract base class rather than an interface as the customer subclasses are identical in behaviour (and just differ in the data they contain) but this is not a huge deal.
Basically, this changes the perspective from "a corporate customer is a type of customer" to "customers have attributes such as discounts and extra charges which affect the way they are billed, and a corporate customer happens to be a customer with a corporate discount". They don't really teach you this in most CS courses but composition (which is what you are using here: you are composing discounts and charges with the Customer interface to extend behaviour) is usually much more powerful than inheritance, simply because composition behaves better with respect to dependency chains and separation of concerns. For instance, I mentioned in my previous post that you could in principle keep using inheritance by sticking a "calculateBill" on the customer interface. After all, the only difference (from the perspective of the billing system, anyway) between the customers is the way they are billed. But is it really up to the customer classes to calculate their own bill? Not really, because they don't have all the information. For instance, some rooms could be king size and lead to a higher bill, and so on, and at that point you may as well pass the HotelBooking and the flat rate to the customer class, which means the HotelBooking becomes hopelessly coupled to the Customer hierarchy, and they may as well be merged. With composition, on the other hand, the billing system simply takes in the HotelBooking, looks at the room involved, the length of the booking, the customer (and hence the discounts he is entitled to) and computes the bill from that. Much more straightforward, and doesn't lead to the problematic coupling, since the customer is just one additional factor in the billing system, not a critical component of it, and the HotelBillingSystem class actually does what it's supposed to be doing: calculating billls, rather than delegating that somewhere else.
In some ways composition is far more fundamental than inheritance, but it's hard to see that if you just got introduced to the merits of inheritance the instant you walked through the lecture hall door (like I did, by the way). It's just a tool in your toolbox, but is sadly too often regarded as the ultimate tool that solves every problem.
Please do ask for clarification as needed, this is quite tricky stuff and took me a while to grasp the utility of.