CDI, Polymorphism and The Factory Pattern

DZone 's Guide to

CDI, Polymorphism and The Factory Pattern

· DevOps Zone ·
Free Resource
One of the most useful things about object oriented programming is polymorphism. In fact, it makes our software to have different behaviors without the need to explicit it in the code.
For instance, you are writing a code to make a credit card transaction. If it is a VISA transaction, you should execute one specific action, if it is a MASTERCARD transaction, you should execute another specific action, so, the first thing you think to do is:
01. public void pay(Transaction transaction) throws UnsupportedCardNetworkException {
02.         if("visa".equals(transaction.getType())){
03.             //pay with visa
04.             return;
05.         }
07.         if("mastercard".equals(transaction.getType())){
08.             //pay with mastercard
09.             return;
10.         }
12.         throw new UnsupportedCardNetworkException(); 
That's not a good design, is it? Probably we need to write a lot of code for each card network, besides, if I want to add support to another card network, I will need another if. Someday this code is going to be little demon.
So, what should I do? Well, in oriented programming, there is something what people call "polymorphism", and what they say that the same method could have several behaviors. Well, that's just what we need for this situation. So, how can we begin?
Well, we are going to have an interface called Payment, having a method, of course, called "pay":
1. public interface Payment {
2.     void pay(Transaction transaction);
3. }
That's pretty simple, actually. So, as we have two credit card networks to implement, we are going to have two more classes, one implementing visa, another implementing mastercard:
1. public class VisaPayment implements Payment {
2.     @Override
3.     public void pay(Transaction transaction) {
4.         //paying with visa
5.     }
6. }
1. public class MasterCardPayment implements Payment {
2.     @Override
3.     public void pay(Transaction transaction) {
4.         //paying with mastercard
5.     }
6. }
So, when I need to pay using a visa card, I just do:
1. Payment payment = new VisaPayment();
2. payment.pay(transaction);
And, when I need to pay using mastercard:
1. Payment payment = new MasterCardPayment();
2. payment.pay(transaction);
That solves our first "pay" method, which was going to be very big (very!!)
But, if I want to do something dynamically? For instance, I don't want to use ifs in my code to choose which instance of a Payment I am going to use, I just want to tell the code to give me an instance based on some variable, like:
1. Payment payment = payments.get("visa");
2. payment.pay(transaction);
Well, I said something dynamic, so:
1. Payment payment = payments.get(transaction.getType());
2. payment.pay(transaction);
That's pretty good, actually! But how can we do that? Well, the anwser is short and simple:
01. public class PaymentFactory {
02.     private static final Map<String, Payment> payments;
04.     private PaymentFactory(){}
05.     static {
06.         payments = new HashMap<String, Payment>();
07.         payments.put("visa", new VisaPayment());
08.         payments.put("mastercard", new MasterCardPayment());
09.     }
10.     public static Payment get(String type){
11.         return payments.get(type);
13. }
So, when I want to get a  Payment instance:
1. Payment payment = PaymentFactory.get(transaction.getType());
Quite simple, isn't it? Well, you should be wondering "but, why is there a CDI in the title of this article?", well, now you are going to know why and see how this stuff gets interesting in here.
CDI allows us to create several beans of a super type and choose a instance by a qualifier, so, here is what we are going to do first:
Create a qualifier called Network
1. @Retention(RetentionPolicy.RUNTIME)
3. @Qualifier
4. public @interface Network {
5.     String value();
6. }
And, annotate our classes MasterCardPayment and VisaPayment with this qualifier, setting the value in the qualifier in each one:
1. @Network("mastercard")
2. public class MasterCardPayment implements Payment {
3.     @Override
4.     public void pay(Transaction transaction) {
5.         //paying with mastercard
6.     }
7. }
1. @Network("visa")
2. public class VisaPayment implements Payment {
3.     @Override
4.     public void pay(Transaction transaction) {
5.         //paying with visa
6.     }
7. }
We can't select an instance just by the string value, so, we need to create a class whichs extends from AnnotationLiteral and implements our Network annotation.
01. public class NetworkAnnotationLiteral extends AnnotationLiteral<Network> implements Network{
02.     private String value;
03.     private NetworkAnnotationLiteral(String value) {
04.         this.value = value;
05.     }
06.     @Override
07.     public String value() {
08.         return value;
09.     }
10.     public static NetworkAnnotationLiteral network(String value){
11.         return new NetworkAnnotationLiteral(value);
12.     }
13. }
If we want a instance of NetworkAnnotationLiteral, we just call: 
1. NetworkAnnotationLiteral.network("type");
But, how can we select our instance based on a transaction type now? Well:
01. @Inject @Any
02. private Instance<Payment> payments;
04. @Test
05. public void payWithVisa(){
06.     Transaction transaction = new Transaction();
07.     transaction.setType("visa");
08.     Payment payment = payments.select(NetworkAnnotationLiteral.network(transaction.getType())).get();
09.     payment.pay(transaction);
10. }
12. @Test
13. public void payWithMaster(){
14.     Transaction transaction = new Transaction();
15.     transaction.setType("mastercard");
16.     Payment payment = payments.select(NetworkAnnotationLiteral.network(transaction.getType())).get();
17.     payment.pay(transaction);
18. }
When we inject the object javax.enterprise.inject.Instance<Payment> with the qualifier @Any, every instance of Payment will be available for you to choose and use. The nicest thing is: now CDI will control the creation of your objects, you don't need to write your own factory, you can also inject resources into your Payment subtypes and choose any CDI scope you want.
That's not a new thing in CDI, but, most people still don't know how powerful CDI is.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}