13
\$\begingroup\$

Please help me make this code more object oriented. I also want to use a few more classes and separate the input processing code to another class.

Question:

A hotel chain operating in Goa wishes to offer room reservation services. They have three hotels in Goa: GreenValley, RedRiver and BlueHills. Each hotel has separate weekday and weekend (Saturday and Sunday) rates. There are special rates for rewards customer as a part of loyalty program.Each hotel has a rating assigned to it.

  • GreenValley with a rating of 3 has weekday rates as Rs1100 for regular customer and Rs800 for rewards customer. The weekend rates are 900 for regular customer and 800 for a rewards customer.
  • RedRiver with a rating of 4 has weekday rates as Rs1600 for regular customer and Rs1100 for rewards customer. The weekend rates are 600 for regular customer and 500 for a rewards customer.
  • BlueHills with a rating of 5 has weekday rates as Rs2200 for regular customer and Rs1000 for rewards customer. The weekend rates are 1500 for regular customer and 400 for a rewards customer.

The input to the program will be a range of dates for a regular or rewards customer. The output should be the cheapest available hotel. In case of a tie, the hotel with highest rating should be returned.

Example to the input format:

Regular:

16Mar2009(mon), 17Mar2009(tues), 18Mar2009(wed)

Output:

GreenValley

HotelFactory.java:

package org.dipankar.java;

public class HotelFactory {
    String hotelName;
    private int regularWeekDay;
    private int regularWeekEnd;
    private int rewardeeWeekDay;
    private int rewardeeWeekEnd;

    HotelFactory(String name) {
        this.hotelName = name;
    }

    public int getRegularWeekDay() {
    return regularWeekDay;
    }

    public void setRegularWeekDay(int regularWeekDay) {
    this.regularWeekDay = regularWeekDay;
    }

    public int getRegularWeekEnd() {
    return regularWeekEnd;
    }

    public void setRegularWeekEnd(int regularWeekEnd) {
    this.regularWeekEnd = regularWeekEnd;
    }

    public int getRewardeeWeekDay() {
    return rewardeeWeekDay;
    }

    public void setRewardeeWeekDay(int rewardeeWeekDay) {
    this.rewardeeWeekDay = rewardeeWeekDay;
    }

    public int getRewardeeWeekEnd() {
    return rewardeeWeekEnd;
    }

    public void setRewardeeWeekEnd(int rewardeeWeekEnd) {
    this.rewardeeWeekEnd = rewardeeWeekEnd;
    }

    public String getHotelName() {
    return hotelName;
    }
}

TestHotel.java:

package org.dipankar.java;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

    public class TestHotel {

        private static HotelFactory x, y, z;

        public static void main(String[] args) throws IOException {
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        String s = bf.readLine();

        //setting values for green valley
        x = new HotelFactory("Green Valley");
        x.setRegularWeekDay(1100);
        x.setRegularWeekEnd(900);
        x.setRewardeeWeekDay(800);
        x.setRewardeeWeekEnd(800);

        //setting values for blue hills
        y = new HotelFactory("Blue Hills");
        y.setRegularWeekDay(2200);
        y.setRegularWeekEnd(1500);
        y.setRewardeeWeekDay(1000);
        y.setRewardeeWeekEnd(400);

        //setting values for red river   
        z = new HotelFactory("Red River");
        z.setRegularWeekDay(1600);
        z.setRegularWeekEnd(600);
        z.setRewardeeWeekDay(1100);
        z.setRewardeeWeekEnd(500);

        int index = s.indexOf(":");
        String type = s.substring(0, index);

        int cost_x = 0, cost_y = 0, cost_z = 0;
        int day_index_start = 0, day_index_end = 0;

        while (day_index_start != -1) {
        day_index_start = s.indexOf("(", day_index_start + 1);
        day_index_end = s.indexOf(")", day_index_end + 1);

        if (day_index_start != -1) {
        String day = s.substring(day_index_start + 1, day_index_end);
        if (day.equalsIgnoreCase("sun") || day.equalsIgnoreCase("sat")) {
        if (type.equalsIgnoreCase("regular")) {
        cost_x += x.getRegularWeekEnd();
        cost_y += y.getRegularWeekEnd();
        cost_z += z.getRegularWeekEnd();
        } else {
        cost_x += x.getRewardeeWeekEnd();
        cost_y += y.getRewardeeWeekEnd();
        cost_z += z.getRewardeeWeekEnd();
        }
        } else {
        if (type.equalsIgnoreCase("regular")) {
        cost_x += x.getRegularWeekDay();
        cost_y += y.getRegularWeekDay();
        cost_z += z.getRegularWeekDay();
        } else {
        cost_x += x.getRewardeeWeekDay();
        cost_y += y.getRewardeeWeekDay();
        cost_z += z.getRewardeeWeekDay();
        }
        }
        }
        }
        System.out.println(cost_x);
        System.out.println(cost_y);
        System.out.println(cost_z);

        String result = min(cost_x, cost_y, cost_z);
        System.out.println(result);
        }

        //implementing the min method
        private static String min(int a, int b, int c) {
        if (a < b && a < c) {
        return x.getHotelName();
        } else if (b < a && b < c) {
        return y.getHotelName();
        } else if (c < a && c < b) {
        return z.getHotelName();
        } else if (a == b || b == c) {
        return y.getHotelName();
        } else if (c == a) {
        return z.getHotelName();
        } else {
        return x.getHotelName();
        }
        }
    }
\$\endgroup\$
  • \$\begingroup\$ Hmm, this reads like a homework question... \$\endgroup\$ – Nelson Jul 5 '16 at 3:13
  • \$\begingroup\$ BTW thanks for sharing this post, FYI I've referenced this problem and a VBA solution on my Rubberduck News blog, ...would you happen to have a source link for the problem statement? \$\endgroup\$ – Mathieu Guindon Feb 23 '17 at 1:58
22
\$\begingroup\$

I know this isn't what you want to hear, but it's the low-hanging fruit and somebody's gotta say it anyway.

Indentation

Whenever you see something like this in code:

    }
    }
    }
    }

You know something isn't right. In fact, you know something is terribly confusing and broken.

Before you can make any kind of refactoring toward a more OOP approach, you need to fix these issues. When you're done the above "wall of braces" should look like this:

                }
            }
        }
    }

Proper indentation is simple actually. Some tools even automate it for you. Take this while loop there (pipes (|) only to show indentation structure):

while (day_index_start != -1) {
|   day_index_start = s.indexOf("(", day_index_start + 1);
|   day_index_end = s.indexOf(")", day_index_end + 1);
|
|   if (day_index_start != -1) {
|   |   String day = s.substring(day_index_start + 1, day_index_end);
|   |   if (day.equalsIgnoreCase("sun") || day.equalsIgnoreCase("sat")) {
|   |   |   if (type.equalsIgnoreCase("regular")) {
|   |   |   |   cost_x += x.getRegularWeekEnd();
|   |   |   |   cost_y += y.getRegularWeekEnd();
|   |   |   |   cost_z += z.getRegularWeekEnd();
|   |   |   } else {
|   |   |   |   cost_x += x.getRewardeeWeekEnd();
|   |   |   |   cost_y += y.getRewardeeWeekEnd();
|   |   |   |   cost_z += z.getRewardeeWeekEnd();
|   |   |   }
|   |   } else {
|   |   |   if (type.equalsIgnoreCase("regular")) {
|   |   |   |   cost_x += x.getRegularWeekDay();
|   |   |   |   cost_y += y.getRegularWeekDay();
|   |   |   |   cost_z += z.getRegularWeekDay();
|   |   |   } else {
|   |   |   |   cost_x += x.getRewardeeWeekDay();
|   |   |   |   cost_y += y.getRewardeeWeekDay();
|   |   |   |   cost_z += z.getRewardeeWeekDay();
|   |   |   }
|   |   }
|   }
}

Notice how much easier it becomes to identify the code blocks. By lining up the closing brace to the beginning of the matching code block, you instantly enhance your code's readability.


Use meaningful names

And declare them as close as possible to their usage.

   private static HotelFactory x, y, z;

"x", "y" and "z" are horrible names by any standard. But the worst offender is HotelFactory, because it's lying. Any class named FoobarFactory can be reasonably expected to be a class responsible for creating instances of a Foobar class. So when you name something HotelFactory, readers/reviewers/maintainers are going to expect that the responsibility of a HotelFactory is to create instances of some Hotel class.

But it's not. Your HotelFactory is the Hotel. Rename it as such. And then split the three declarations and move them closer to their usage:

private static Hotel greenValley;
greenValley = new Hotel("Green Valley");
greenValley.setRegularWeekDay(1100);
greenValley.setRegularWeekEnd(900);
greenValley.setRewardeeWeekDay(800);
greenValley.setRewardeeWeekEnd(800);

private static Hotel blueHills;
blueHills = new Hotel("Blue Hills");
//...

Now, you gave that Hotel/HotelFactory class a constructor parameter for the name, but not for the regular/rewardee weekday/weekend prices. Why? If having 5 constructor parameters is a smell, then make another class, and have Hotel take 2 constructor parameters: a string name and a PricingStructure pricing, where PricingStructure could be a simple class exposing the needed members - that way you're not bloating a Hotel's constructor, and you're decoupling a Hotel from the pricing elements.


OOP

What you really want to know, is this:

Given a date and whether we're a "rewardee" customer, what price would we be charging?

So the PricingStructure described above should really be a PricingStrategy, and the interface of that could look like this:

int calculatePrice(Date checkinDate, bool isRewardeeCustomer);

You would have 3 different implementations, one for each hotel, and each hotel's actual pricing structure would become an implementation detail - the client code couldn't care less about how you're going to come up with a price: it only needs to be given an amount, given a date and a bool flag.

And then, if BlueHills wants to offer a 20% special discount on July 4th, that detail is encapsulated in the calculatePrice method, and no other code needs to change anywhere.

In other words, the outside world (i.e. the calling code) only needs to see this:

Hotel greenValley = new Hotel("Green Valley", new GreenValleyPricingStrategy());
Hotel redRiver = new Hotel("Red River", new RedRiverPricingStrategy());
Hotel blueHills = new Hotel("Blue Hills", new BlueHillsPricingStrategy());

And then get them into an array or any iteratable, sort them by whatever their calculatePrice method is returning, and get your answer.

OOP is about scaling: what if the problem statement included a data file that described the pricing strategy for 3,500 hotels? Would min(cost_x, cost_y, cost_z) cut it? You need to look into data structures and possibly sorting algorithms to solve this I think.


Specs

It seems you've missed this part:

In case of a tie, the hotel with highest rating should be returned.

The Hotel class also needs a getRating getter method and an encapsulated rating private field, and your final results need to be sorted first by ascending price, then by descending hotel rating.

\$\endgroup\$
  • 1
    \$\begingroup\$ Insisting in the "use meaningfull name" I would have renamed regularWeekDay -> regularWeekDayRate. Naming variables is hard but make code much more legible. \$\endgroup\$ – user119591 Jul 5 '16 at 9:44
  • \$\begingroup\$ @Mat's Mug: I think we need to consider a scenario where if tomorrow let's say there are 3000 hotel, then would we have 3000 pricingStrategy implementations? \$\endgroup\$ – Dhairyashil Jul 17 '17 at 14:26
  • \$\begingroup\$ @Dhairyashil Of course not! In this implementation I have a FixedAmountPricingRule that is reused for all hotels, without being tied to a specific one in any way; any given hotel could be set up with a different implementation if needed. \$\endgroup\$ – Mathieu Guindon Jul 17 '17 at 14:44
  • \$\begingroup\$ @Mat's Mug: What do you think about bookingDates placement? Should it be part of Customer or part of Booking entity ( an entity which we can create only for storing booking details)? Also instead of exposing getRating() method, we should use implements Comparable. It will give us compareTo() method, which will help us in the case of a tie. \$\endgroup\$ – Dhairyashil Jul 19 '17 at 9:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.