Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

design patterns how to avoid instanceOf when using List

Imagine you have a menu with dishes each dish should be available in multiple languages (French , English , Arabic , ...). The Dish class contains a list with Language type objects.

class Dish { 
     List<Language> languages
     void addLanguage(Language lg){...}
}
class Language { getDescription(){}}

class French extends Language{}

class Menu {List<Dish> dishes }

How do I avoid using instance of when wanting a description of a specific language for that dish?

Should I define for each language a get method in the dish class:getFrench(), getArabic(),..?

Or should I keep it in list and check for instance of French by looping the list and then call getDescription() on this list object<language> ?

Or is there a way for a more polymorphic approach?

like image 447
majestic Avatar asked Sep 03 '18 08:09

majestic


People also ask

Is it good practice to use instanceof?

Yes, in general using `instanceof`` is a symptom of a bad/poor design. The trick is to use double dispatch or visitor pattern. Look on the web. It would be easier to give you some examples if you included a bit of detail about how you are using instanceof .

How to avoid instance of in java?

The primary alternative to using instanceof is polymorphism. Rather then ask which type of object you have at the current position you tell the object, whatever it is, to do what you want done. If both objects know how to do that then this works fine, even if they do it differently.

What is the return type of the instanceof operator?

The instanceof in java is also known as type comparison operator because it compares the instance with type. It returns either true or false. If we apply the instanceof operator with any variable that has null value, it returns false.

How to check if object is instance of java?

The instanceof operator in Java is used to check whether an object is an instance of a particular class or not. objectName instanceOf className; Here, if objectName is an instance of className , the operator returns true . Otherwise, it returns false .


2 Answers

I don't think it's a good idea to create a separate class for each language. After all, all these classes will have the exact same methods.

I'd use a single Language class, and inside the Dish class I'll keep a Map<Locale,Description> (I'd rename the Language class to something less confusing such as Description, since it doesn't represent a language, it represents a description in some language).

Now you can have a

Description getDescription(Locale locale)

method in your Dish class, which will return the description of the dish in the language of the passed Locale.

You should prefer using standard JDK classes such as java.util.Locale instead of custom classes when possible.

After considering the comments, and agreeing to some of them, I suggest removing the Map to the Description class.

class Dish 
{ 
      Description description = new Description ();

      void addDescription(Locale locale, String text)
      {
          description.addText(locale,text);
      }

      String getDescription(Locale locale) 
      {
          return description.getText(locale);
      }
 }

class Description 
{
    Map<Locale,String> descriptions = new HashMap<>();

    public void addText(Locale locale,String text) 
    {
        descriptions.put(locale,text);
    }

    public void getText(Locale locale) 
    {
        return descriptions.get(locale);
    }
}

You should also note that searching the language specific description in a Map is more efficient than searching for it in a List (O(1) lookup time in a HashMap vs. O(n) lookup time in a List).

like image 69
Eran Avatar answered Nov 05 '22 09:11

Eran


I think a Language doesn't have a menu Description. I would design it in this way.

 class Dish { 
       List<Description> descriptions;
       void addDescription(Description description){...}

       Description getDescription(Language language){
           // iterate over descriptions and select the appropriate
           // or use a Map<Language, Description>
           Optional<Description> applicableDescription =
                          descriptions.stream()
                          .filter(d -> d.getLanguage().isApplicable(language)).findFirst();
           return applicableDescription.orElseGet(this::getDefaultDescription);
       }

       private Description getDefaultDescription(){
           return descriptions.isEmpty() ? null : descriptions.get(0); 
       }
 }


 class Description {
     Language language;
     String shortDescription;
     String title;
     // and so on... 

     public boolean isApplicable(Language language){
         return this.language.equals(language);
         // This logic can be more sophisticated. E.g. If an Italian
         // wants to get a menu description, but no italian translation is
         // available, it might be useful to choose spanish. But then it
         // would be better to return a similarity, like 0.1 to 1.0, instead of true/false.
     }
 }

 class Language {
      String name; // like en, de, it, fr, etc. Maybe an enum is better or just using
                   // java.util.Locale
 }
like image 33
René Link Avatar answered Nov 05 '22 07:11

René Link