Reputation: 1422
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:
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?
Upvotes: 0
Views: 373
Reputation: 88707
I'd go with the simplest solution for your problem and implement it at the lowest level possible. That means if it's just an additional call in one method that might be done I'd not use the decorator as this would require you to already select the correct instance early on. The "template" approach seems to be the simpler of the 2.
Also, if the product types are already known just adding some switch logic into the service itself might be sufficient.
If it needs to be extensible then I'd probably use a strategy approach which is similar to your template method approach, e.g. something like this:
interface SpecificProductStatusStrategy {
String getSpecificStatus(String baseStatus, Product product);
}
public class ProductStatusService {
//if the service is not a singleton you might want to put that into a singleton repository and use that
private Map<String, SpecificProductStatusStrategy> strategies = ...;
//simple registration, could also use DI etc.
public void addStrategy(String productType, SpecificProductStatusStrategy strategy) {
strategies.put(productType, strategy);
}
public String getStatus(Result result, Product product) {
String baseStatus;
if (result.isSuccess()) {
baseStatus = "SUCCESS";
} else if (result.getPaymentMethod().equals("TRANSFER")) {
baseStatus = "WAITING_CONFIRMATION";
} else {
baseStatus = "WAITING_PAYMENT";
}
var strategy = strategies.get(product.getType());
return strategy != null ? strategy.getSpecificStatus(baseStatus, product) : baseStatus;
}
Upvotes: 1
Reputation: 1
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.
Upvotes: -1