I'm currently re-doing Tim Buchalka's OOP Master Challenge exercise where the task is to make a Hamburger
class that has the basic fields like name, breadRoll, price, meat etc; and to make an inherited class called Healthy Burger
where it would have it's own specific fields that would distinguish it (having a different bread roll, more additions).
Tim firstly made all of the additions and prices for every addition in to a separate field (and also made methods for every single addition to set them). Like this:
private String addition1;
private String additionPrice1;
private String addition2; etc...
Where as I preferred to make a String[] and pre-defined every addition into a String array. Like this:
private String[] additions = {"lettuce","tomato","carrot","dressing","onion","ginger"};
private double[] additionPrice = {0.3,0.4,0.5,0.6,0.7,0.8};
I had an idea in mind that the user that is going to create a Hamburger class, has to have constrained input and an easier way of creating the Hamburger object, without invalid inputs.
So I made my constructor like this:
public Hamburger(String name) {
this.name=name;
breadRollSelection();
baseMeatSelection();
this.totalPrice = basePrice;
}
breadRollSelection();
and baseMeatSelection();
both contain loops that print out the String[] for types of bread rolls and meats, where user would input using Scanner:
public void breadRollSelection() {
System.out.println("Pick a bread roll: ");
if(this.name.equalsIgnoreCase("Healthy Hamburger")) {
this.breadRoll=breadRolls[2];//healthy bread roll
System.out.println(breadRoll + " has been chosen with the price of "+breadRollsPrice[2]);
this.basePrice += breadRollsPrice[2];
} else {
for (int i = 0; i < 2; i++) {
System.out.println(i + " " + breadRolls[i] + " with the price of: " + breadRollsPrice[i]);
}
Scanner scan = new Scanner(System.in);
int choice = scan.nextInt();
this.breadRoll = breadRolls[choice];
this.basePrice += breadRollsPrice[choice];
}
}
Where as in the Healthy Burger
class, I only did this:|
public class HealthyBurger extends Hamburger {
private String breadRoll="Bread Roll Diet";
public HealthyBurger() {
super("Healthy Hamburger");
}
So when I create a HealthyHamburger ham = new HealthyHamburger();
object, when accessing breadRollSelection()
method, it already knows that a child object is calling it. I would override it in the child class, but I don't have access to the String[] breadRoll, unless I make them protected (if that is ok to do here).
And with addAddition();
method, which is quite larger, I used the name of the burger as a condition which additions should be added:
public void addAddition(){
int additionLimit=0;
boolean q=true;
if(this.name=="Basic") {
while (q) {
for(int i=0;i<4;i++){
System.out.println(i+" "+additions[i]+ " with the price of: "+additionPrice[i]);
}
Scanner scan = new Scanner(System.in);
int choice = scan.nextInt();
choice -= 1;
System.out.println(additions[choice] + " chosen!");
this.totalPrice += additionPrice[choice];
System.out.println(this.totalPrice + " is the price after this addition!");
additionLimit += 1;
if (additionLimit == 4) {
System.out.println("No more additions");
q = false;
break;
}
System.out.println("Press 0 to exit or 1 if you want more additions!");
int choice1 = scan.nextInt();
if (choice1 == 0) {
q = false;
}
}
}
if(this.name=="Healthy Hamburger"){
while (q) {
for(int i=0;i<additions.length;i++){
System.out.println(i+" "+additions[i]+ " with the price of: "+additionPrice[i]);
}
Scanner scan = new Scanner(System.in);
int choice = scan.nextInt();
choice -= 1;
System.out.println(additions[choice] + " chosen!");
this.totalPrice += additionPrice[choice];
System.out.println(this.totalPrice + " is the price after this addition!");
additionLimit += 1;
if (additionLimit == 6) {
System.out.println("No more additions");
q = false;
break;
}
System.out.println("Press 0 to exit or 1 if you want more additions!");
int choice1 = scan.nextInt();
if (choice1 == 0) {
q = false;
}
}
}
}
My question is, if it's ok to use the parent class like this, where it would know which child is accessing it and is it ok if I use the constructor this way?
Your instincts are good, as far as having the sense that something is off here. This is a little bit subjective, but there are some guidelines and design patter descriptions I can offer you to help figure it out.
Firstly, the red flags that this is an anti-pattern:
if
blocksSome of that can be solved by just breaking the larger function up into smaller ones, but a parent class is supposed to be abstract by nature. As soon as you find it necessary to give it more contextual awareness, that's a sign you're using the wrong pattern.
Now, what to do about it.
I believe you're looking for a 'compositional' pattern as opposed to traditional inheritance. Specifically, the one you're probably looking for is the Decorator Pattern. As a matter of fact, the classic example use-case for the Decorator pattern is the construction and pricing of various types of coffee at a cafe, conceptually not far off at all from your hamburger example.
The gist of it is, each component of the thing you're trying to make is its own separate class. Rather than inherit from eachother. So you start with a base-"Hamburger" class, or a base-"VegiBurger". (And there's no reason they can't still inherit from an abstract "Burger" class.) Then, using Dependency Injection, give that base-object to an ingredient instance, then give that ingredient object to the next one, and so on.
Each component is responsible for its own pricing, and its own attributes, and what you end up with is a series of object wrappers that look a bit like the layers of the onions you may be putting on your burger. Now the trick is, each of them implements the same interface. And that interface guarantees the existence of something like a "getTotal" method. And each individual "getTotal" definition would be designed to tap into that of the object initially given to it, so that calculation runs through all the layers.
What this does for you is alleviate any assumptions that the structure needs to make about what a burger entails. Ingredients and prices can be added or modified without having to modify anything else.