dimanche 10 juillet 2016

Best way to remove multiple if-else

So the similar type of questions are answered elsewhere but here I am expecting the best way to omit if else chain for the given situation.

CURRENT CODE

private ViewModel getViewModel(Receipt receipt) {
  String receiptType = receipt.type;

  if(receiptType.equals("HOTEL")) {
    return new HotelReceiptViewModel(receipt));
  } else if(receiptType.equals("CAR")) {
    return new CarReceiptViewModel(receipt));
  }
  .
  .
  .
  } else if(receiptType.equals("LUNCH")) {
    return new FoodReceiptViewModel(receipt));
  }
}

where all the view models extend a class called ReceiptViewModel. e.g.

public class HotelReceiptViewModel extends ReceiptViewModel implements ViewModel {
    public HotelReceiptViewModel(Receipt receipt) {
        super(receipt);
        this.receiptNumber = receipt.getDocumentNumber();
        this.receiptHeading = "HOTEL";
    }
}

There are currently 5 types of receipts and there will be 3-4 more types of receipts in future.

POSSIBLE SOLUTIONS

  1. Use of HashMap
  2. Use of Enum
  3. Use Strategy Pattern or Command Pattern
  4. Use of Reflection

Let's see pros and cons of each approach


1. Use of HashMap

private ReceiptViewModel getViewModel(Receipt receipt) {
  Map<String, ReceiptViewModel> map = getViewModelsMap();

  String receiptType = receipt.type;
  ReceiptViewModel viewModel = map.get(receiptType);
  if(viewModel != null) {
    viewModel.setReceipt(receipt);
  }

  return viewModel;
}

private Map<String, ReceiptViewModel> getViewModelsMap() {
  Map<String, ReceiptViewModel> map = new HashMap<String, ReceiptViewModel>();
  map.add("HOTEL"), new HotelReceiptViewModel());
  map.add("CAR"), new CarReceiptViewModel());
  map.add("LUNCH"), new FoodReceiptViewModel());
}

and ReceiptViewModel classes will look like

public class HotelReceiptViewModel extends ReceiptViewModel implements ViewModel {
  public HotelReceiptViewModel(Receipt receipt) {
    super(receipt);
    this.receiptNumber = receipt.getDocumentNumber();
    this.receiptHeading = "HOTEL";
  }
}

PROS Faster, Easier, Extensible.

CONS ReceiptViewModel object does not require an object of Receipt type in the constructor. Receipt is rather set using a setter, where all the logic of initializing the ReceiptViewModel class will move now.


2. Use of Enum

private ReceiptViewModel getViewModel(Receipt receipt) {
  String receiptType = receipt.type;
  ReceiptViewModel viewModel = 
        ReceiptViewModels.valueOf(receiptType).getReceiptViewModel(receipt);

  return viewModel;
}

And Enum will look like

public enum ReceiptViewModels {
   HOTEL(
       ReceiptViewModel getReceiptViewModel(Receipt receipt) {
           return new HotelReceiptViewModel(receipt);
       }
    ),
   CAR(
       ReceiptViewModel getReceiptViewModel(Receipt receipt) {
           return new CarReceiptViewModel(receipt);
       }
    ),
    .
    .
    .
   LUNCH(
       ReceiptViewModel getReceiptViewModel(Receipt receipt) {
           return new FoodReceiptViewModel(receipt);
       }
    ),

    public abstract ReceiptViewModel getReceiptViewModel(Receipt receipt);
 }

PROS Fast, Probably Easy.

CONS Size of Enum will keep on increasing as the receipt types increase, resulting in non-maintainable code.

ReceiptViewModels.valueOf(receiptType) expects a known receipt type. If a new receipt type comes as a response from server, it will result in an IllegalArgumentException


3. Use of Reflection

Class<? extends ReceiptViewModel> viewModel = Class.
            forName(receiptType + name + "ReceiptViewModel").asSubclass(ReceiptViewModel.class);
        ReceiptViewModel receiptViewModel = viewModel .newInstance();

CONS 1. Slower

  1. Cannot be used when the class names are different. e.g. For LUNCH type the view model class name is FoodReceiptViewModel

  2. Logic of getting values from receipt is moved to a setter instead of constructor as in case of HashMap


4. Use of Strategy Pattern or Template Pattern

PROS Easy to understand and faster than Reflection

CONS Probably an overkill. A new class will be added for each type of receipt.


Considering all the above points, which would be the best approach for my use case to remove the multiple if-else blocks?

Aucun commentaire:

Enregistrer un commentaire