3

Let's say we have the following business requirements:

We have a list of dishes. They have a name and a list of ingredients. The restaurant owner always wants to have a dish (and only one) marked as the "main" dish.

Let's suppose we are using an OOP approach, and we have two possible designs (I'm using python but it should not be relevant):

Design 1

class Dish:
    name: str
    ingredients: List[Ingredient]
    is_main: bool

In this approach, we represent the main dish requirement as a field inside our object. This has, among others, the following implications for the consumer of this class, let's say, a Menu class:

class Menu:
    dishes: List[Dish]

    def __init__(self, dishes: List[Dish]):
         self.dishes = dishes
         if self.get_main_dish() is None:
              raise ValueError("You must provide one main dish")
    def get_main_dish() -> Dish:
        for dish in self.dishes:
            if dish.is_main:
                 return dish

Design 2

Using the Interface Segregation Principle, someone may suggest the following approach:

from abc import ABC

class DishInterface(ABC):
    name: str
    ingredients: List[Ingredient]

class NormalDish(DishInterface):
    name: str
    ingredients: List[Ingredient]

class MainDish(NormalDish):
     is_main: bool

Then the Menu object would be more or less the same, but checking the type of the dish instead of one of its attributes:

class Menu:
    dishes: List[DishInterface]

    def __init__(self, dishes: List[DishInterface]):
         self.dishes = dishes
         if self.get_main_dish() is None:
              raise ValueError("You must provide one main dish")
    def get_main_dish() -> MainDish:
        for dish in self.dishes:
            if isinstance(MainDish, dish):
                 return dish

Question

Is this correct? Does this offer any advantages? I think Design 1 is better because it is simpler, but other members of the team suggest Design 2. I want to fully understand why that approach would be better in the long run.

Just to clarify, I see the point of ISP, but I'm not sure that it's applicable here because being the main dish is something arbitrary defined by the customer. Also it seems a little bit weird to me to have to check the type of the class, because that is usually a code smell in OOP.

4
  • 2
    "Using the Interface Segregation Principle [...] Is this correct?" - what you have there is not exactly an application of ISP. ISP is about having small role-based client-oriented interfaces, so that, besides of not depending on what you don't need, you have some flexibility in terms of picking the implementations behind those interfaces. Here, the code just checks for the type. Commented Sep 23, 2022 at 16:05
  • The first one is wrong because you could make multiple dishes a main dish which makes no sense. The identification of the main dish must be external to the dish itself. An effective way to do it (since you have your list of dishes anyway) would be to declare the first one in the list the main dish. The second one makes no sense to me at all, which may be due my lack of Python experience. There is nothing ISP about it though. Commented Sep 23, 2022 at 18:47
  • @MartinMaat what if you need to order the dishes alphabetically? You would not be able to find the main dish Commented Sep 25, 2022 at 11:46
  • @nicolascolman When you keep the main dish the first element there is no issue with that. The first one is by definition the main dish. You could still sort: just remove the first before performing the sort and then insert it as the first again. Commented Sep 25, 2022 at 12:42

1 Answer 1

6

There are multiple small-ish issues I have with your question. I don't mean to nitpick. They just compound in a way that makes simple answers impossible, so bear with me please. :)

We have a list of dishes. They have a name and a list of ingredients. The restaurant owner always wants to have a dish (and only one) marked as the "main" dish.

Those are not requirements. I kind-of suspect what you imply, but requirements are about functionality. Dishes having a name and a list of ingredients is completely irrelevant, unless you use it in a context of a functionality. Again, this is not nitpicking, this is absolutely important for deciding about interfaces.

You could say: We want to display the main dish on a website. It has to display its ingredients. There needs to be exactly one main dish. Etc.

About design 1: Object orientation is about behavior, while encapsulating (hiding) data. Having "objects" that only hold data is diametrically opposed to this concept. Records do not exist in OO.

Again, this ties into your question. What behavior would a Dish have? Well, if you would formulate your requirements a little bit better, it would be clear that a Dish just displays itself. There is nothing else, at least you mentioned nothing else.

About design 2: Would a typed MainDish make sense? Since in OO objects are defined by their behavior, would a MainDish behave differently than a NormalDish? Depends on the exact requirements. If it is displayed completely differently, then yes. Otherwise no.

I propose an alternative one too. How about Dishes are the same, but the Menu takes two arguments. One Dish as main dish, in addition to List[Dish]. That way the Dish is oblivious that it is the main one (again, if it doesn't need to know this) and the Menu doesn't have to violate the information hiding of the Dish by asking for some internal property.

1
  • To be honest I made up the requirements, but your explanation has been flawless. Now I can form my own opinion, thanks a lot for your insights! Commented Sep 23, 2022 at 8:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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