CDI, Polymorphism and The Factory Pattern
Join the DZone community and get the full member experience.
Join For FreeOne 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.
}
06.
07.
if
(
"mastercard"
.equals(transaction.getType())){
08.
//pay with mastercard
09.
return
;
10.
}
11.
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":
Payment:
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:
VisaPayment:
1.
public
class
VisaPayment
implements
Payment {
2.
@Override
3.
public
void
pay(Transaction transaction) {
4.
//paying with visa
5.
}
6.
}
MasterCardPayment:
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:
PaymentFactory:
01.
public
class
PaymentFactory {
02.
private
static
final
Map<String, Payment> payments;
03.
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);
12.
}
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)
2.
@Target
({TYPE, METHOD, FIELD, PARAMETER, CONSTRUCTOR})
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:
MasterCardPayment:
1.
@Network
(
"mastercard"
)
2.
public
class
MasterCardPayment
implements
Payment {
3.
@Override
4.
public
void
pay(Transaction transaction) {
5.
//paying with mastercard
6.
}
7.
}
VisaPayment:
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.
NetworkAnnotationLiteral:
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;
03.
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.
}
11.
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.
CDI
Factory (object-oriented programming)
Polymorphism (computer science)
Opinions expressed by DZone contributors are their own.
Comments