5. Design a food delivery system for ordering food and rating restaurants - Multi-Threaded

Observer design pattern, Strategy design pattern can be used to solve this question.


Design a food delivery system for ordering food and rating restaurants - Multi-Threaded
Solution 1: YouTube video explanation     
Solution 2: Blog with complete code
Solution 3: Blog With Explanation and code

Write code for low level design (object oriented design) of a restaurant food ordering and rating system similar to food delivery apps like Zomato, Swiggy, Door Dash, Uber Eats etc.

There will be food items like 'Veg Burger', 'Veg Spring Roll', 'Ice Cream' etc.
And there will be restaurants from where you can order these food items.

Same food item can be ordered from multiple restaurants. e.g. you can order 'food-1' 'veg burger' from burger king as well as from McDonald's.

Users can order food, rate orders, fetch restaurants with most rating and fetch restaurants with most rating for a particular food item e.g. restaurants which have the most rating for 'veg burger'.

Your solution should implement below methods :

Method : init(Helper05 helper)
- Use this method to initialize your instance variables
- use helper's methods for printing logs else logs will not be visible.

Method : addFoodItem(String foodItemId, double price, String foodItemName)
- adds a new food item. Same food item can be sold by many restaurants.
- for simplicity lets assume that food item's price is same in all restaurants.
- PARAMETER : foodItemId will always be a non null, non blank unique string
- PARAMETER : price e.g 240
- PARAMETER : foodItemName e.g. Masala Dosa, Pizza, Burger, Sandwich etc

Method : addRestaurant(String restaurantId, List[String] foodItemIds, String name, String address)
- adds a new restaurant.
- PARAMETER : restaurantId will always be a non null, non blank unique string
- PARAMETER : foodItemIds is a list of id's of food items that have already been added
- PARAMETER : name is name of the restaurant e.g. Burger King
- PARAMETER : address e.g. connaught place, delhi

Method : orderFood(String orderId, String restaurantId, String foodItemId)
- Orders food item from a restaurant.
- for now lets assume for that only a single food item is purchased in one order.
- orderId, restaurantId, foodItemId will all be valid and available.
- PARAMETER : restaurantId is restaurant from where food is being ordered.
- PARAMETER : foodItemId is food item which is being ordered

Method : rateOrder(String orderId, int rating)
- Customers can rate their order.
- when you are giving rating an order e.g giving 4 stars to an order, then it means you are assigning 4 stars to both the food item in that restaurant as well as 4 stars to the overall restaurant ranting.
- PARAMETER : orderId is order which will be rated by customer, orderId will always be valid i.e. order will always be created for an orderId before rateOrder() is called.
- PARAMETER : rating ranges from 1 to 5 stars in increasing order, 1 being the worst and 5 being the best rating.

Method : List[String] getTopRestaurantsByFood(String foodItemId)
- Fetches a list of top 20 restaurants based on strategy
- unrated restaurants will be at the bottom of list.
- restaurants will be sorted on the basis of strategy
- restaurants are sorted in descending order on average ratings of the food item and then based on restaurant id lexicographically.
- e.g. veg burger is rated 4.3 in restaurant-4 and 4.6 in restaurant-6 then we will return ['restaurant-6', 'restaurant-4']
- PARAMETER : foodItemId is food item for which restaurants need to be fetched.

Method : List[String] getTopRatedRestaurants()
- returns top 20 most rated restaurants ids sorted in descending order of their ratings.
- if two restaurants have the same rating then they will be ordered lexicographically by their restaurantId.
- Here we are talking about restaurant's overall rating and NOT food item's rating.
- e.g. restaurant-2 is rated 4.6 while restaurant-3 is rated 4.2 and restaurant-5 is rated 4.4 and restaurant-6 is rated 4.6, we will return ['restaurant-2','restaurant-6', 'restaurant-5', 'restaurant-3']
- even though restaurant-2 and restaurant-6 have same rating , restaurant-6 came later because it is lexicographically greater than restaurant-2

Note :
- Your code will be tested in a MULTI-THREADED environment, so use thread safe data structures and handle synchronization properly.

- method call to rateOrder() will never be done concurrently with
   getTopRatedRestaurants() or
   getTopRestaurantsByFood(String foodItemId) : for the same foodItemId in given orderId
   This is done to keep system state consistent and also test accurately in a multithreaded environment.

- There will be at max 50 food items, at max 10,000 restaurants, and each restaurant can sell at max 25 food items

- Rating for unrated restaurants or food items should be considered 0

- when you rate an order it will count towards overall average rating of restaurant as well as average rating of restaurant for that particular food item.

- when you are rating an order e.g giving 4 stars to an order then it means you are assigning 4 stars to both the food item in that restaurant as well as 4 stars to the overall restaurant ranting.

- Ratings will range from 1,2,3,4 or 5 stars, 1 being the worst and 5 being the best rating.

- Average ratings are rounded down to 1 decimal point, i.e. 4.05, 4.08, 4.11, 4.12, 4.14 all become 4.1 and 4.15, 4.19, 4.22, 4.24 all become 4.2

Example : Read the below method calls to get a better understanding of how this works.

init(helper)
addFoodItemId(foodItemId = 'food-0', price=264.0, foodItemName = 'Veg Burger')
addFoodItemId(foodItemId = 'food-1', price=250.0, foodItemName = 'Paneer Tikka')

addRestaurant(restaurantId = 'restaurant-0', foodItemIds = [food-0, food-1], name='restaurant-name-0', address = 'restaurant-address-0')

addRestaurant(restaurantId = 'restaurant-1', foodItemIds = [food-0], name='restaurant-name-1', address = 'restaurant-address-1')

addRestaurant(restaurantId = 'restaurant-2', foodItemIds = [food-1, food-0], name='restaurant-name-2', address = 'restaurant-address-2')


orderFood(orderId = 'order-0', foodItemId = 'food-1', restaurantId = 'restaurant-0')

rateOrder(orderId = 'order-0', rating = 3)

orderFood(orderId = 'order-1', foodItemId = 'food-0', restaurantId = 'restaurant-2')

rateOrder(orderId = 'order-1', rating = 1)

orderFood(orderId = 'order-2', foodItemId = 'food-0', restaurantId = 'restaurant-1')

rateOrder(orderId = 'order-2', rating = 3)

orderFood(orderId = 'order-3', foodItemId = 'food-0', restaurantId = 'restaurant-2')

rateOrder(orderId = 'order-3', rating = 5)

orderFood(orderId = 'order-4', foodItemId = 'food-0', restaurantId = 'restaurant-0')

rateOrder(orderId = 'order-4', rating = 3)

orderFood(orderId = 'order-5', foodItemId = 'food-0', restaurantId = 'restaurant-1')

rateOrder(orderId = 'order-5', rating = 4)

orderFood(orderId = 'order-6', foodItemId = 'food-1', restaurantId = 'restaurant-0')

rateOrder(orderId = 'order-6', rating = 2)

orderFood(orderId = 'order-7', foodItemId = 'food-1', restaurantId = 'restaurant-0')

rateOrder(orderId = 'order-7', rating = 2)

orderFood(orderId = 'order-8', foodItemId = 'food-0', restaurantId = 'restaurant-1')

rateOrder(orderId = 'order-8', rating = 2)

orderFood(orderId = 'order-9', foodItemId = 'food-0', restaurantId = 'restaurant-1')

rateOrder(orderId = 'order-9', rating = 4)

getTopRestaurantsByFood('food-0')
returns [restaurant-1, restaurant-0, restaurant-2]
(For reference only) list of restaurants with rating for food food-0 : [('restaurant-1' : 3.3), ('restaurant-0' : 3.0), ('restaurant-2' : 3.0)]

getTopRestaurantsByFood('food-1')
returns [restaurant-0, restaurant-2]
(For reference only) list of restaurants with rating for food food-1 : [('restaurant-0' : 2.3), ('restaurant-2' : 0.0)]


getTopRatedRestaurants() returns
[restaurant-1, restaurant-2, restaurant-0]
(For reference only) list of top restaurants with rating : [('restaurant-1' : 3.3), ('restaurant-2' : 3.0), ('restaurant-0' : 2.5)]