I write the code in Java and I have a case where I need to get the status of an order depending on the product type. Each product type has the same base logic as written below.
protected String getStatus(ProductType productType, Result result) {
if (result.isSuccess()) {
return "SUCCESS";
} else if (result.getPaymentMethod().equals("TRANSFER")) {
return "WAITING_CONFIRMATION";
} else {
return "WAITING_PAYMENT";
}
}
However, each product type can have custom logic. For example:
- if product type is "A", no need to modify the logic above
- if the status returned from the method above is "SUCCESS" and the product type is "B", I need to call another service to check the status of the product issuance
- if the status returned from the method above is "SUCCESS" and the product type is "C", I need to call partner service.
My question is what is the best design pattern to implement?
The first idea is using something like template method pattern
public class BaseProductStatusService {
public String getStatus(Result result, Product product) {
String status;
if (result.isSuccess()) {
status = "SUCCESS";
} else if (result.getPaymentMethod().equals("TRANSFER")) {
status = "WAITING_CONFIRMATION";
} else {
status = "WAITING_PAYMENT";
}
return doGetStatus(status);
}
protected String doGetStatus(String status, Product product) {
return status;
}
}
// For product A, no need to have its own class since it can use the base class
public class ProductBStatusService extends BaseProductStatusService {
@Override
protected String doGetStatus(String status, Product product) {
if (status.equals("SUCCESS")) {
return this.checkProductIssuance(product);
}
return status;
}
}
public class ProductCStatusService() extends BaseProductStatusService {
@Override
protected String doGetStatus(String status, Product product) {
if (status.equals("SUCCESS")) {
return this.checkStatusToPartner(product);
}
return status;
}
}
Another alternative is using decorator pattern
public interface ProductStatusService() {
String getStatus(Result result, Product product);
}
public class DefaultProductStatusService implements ProductStatusService {
public String getStatus(Result result, Product product) {
String status;
if (result.isSuccess()) {
status = "SUCCESS";
} else if (result.getPaymentMethod().equals("TRANSFER")) {
status = "WAITING_CONFIRMATION";
} else {
status = "WAITING_PAYMENT";
}
return doGetStatus(status);
}
}
public abstract class ProductStatusServiceDecorator implements ProductStatusService {
private ProductStatusService productStatusService;
public ProductStatusServiceDecorator(ProductStatusService productStatusService) {
this.productStatusService = productStatusService;
}
public String getStatus(Result result, Product product) {
return this.productStatusService.getStatus();
}
}
// For product A, no need to have its own class since it can use the DefaultProductStatusService class
public class ProductBStatusServiceDecorator extends ProductStatusServiceDecorator {
public ProductStatusServiceDecorator(ProductStatusService productStatusService) {
super(productStatusService);
}
public String getStatus(Result result, Product product) {
String status = super.getStatus();
if (status.equals("SUCCESS")) {
return this.checkProductIssuance(product);
}
return status;
}
}
public class ProductCStatusServiceDecorator extends ProductStatusServiceDecorator {
public ProductStatusServiceDecorator(ProductStatusService productStatusService) {
super(productStatusService);
}
public String getStatus(Result result, Product product) {
String status = super.getStatus();
if (status.equals("SUCCESS")) {
return this.checkStatusToPartner(product);
}
return status;
}
}
which one is better for the above case and what is the reason? or do you have other suggestion?
The idea of the decorator pattern is to be able to change method behavior at runtime. Which is not something you seem to need: product types are clearly defined from the begining (i.e. before compilation).
You can go with the template unless I misunderstood something.
These being said : each design pattern have been created to solve a problem. In your case, a big "if.. else...", while unelegant would have do just fine. This is a good thing you pactise these concepts, but beware not to end up with something tricky and overcomplicated.