DESIGN PATTERNS SAGA #5: REAL PROJECT SITUATIONS WITH ABSTRACT FACTORY

The problem: Sometimes we need to create complex objects based on abstractions and types. What can we do to deal with this problem? Create lots of “ifs”? No, we can use the Abstract Factory Pattern! Basically, with this Pattern we can create an abstraction of the abstraction. Although this Pattern is not commonly used on enterprise projects, it’s very important to know. One of the cons of this Pattern is that it is complex.

The diagram:

abstract_factory_diagram.PNG

The source code:
https://github.com/rafadelnero/design-patterns-saga.git

1 – The VirtualCoinFactory: Everything begins here in the VirtualCoinFactory class.

The getVirtualCoinFactory(double moneyToInvest) method controls what factory will be returned based on the amount of moneyToInvest. If it is 2000 or more, it will return either the BitcoinFactory or EtheriumFactory. You can see that it is a Factory of another Factory. This is the top class of the Factories.

The abstract getVirtualCoin(VirtualCoinType virtualCoinType) method must be implemented by the other Factory subclass, then we can get a specific class from any type of VirtualCoin.

public abstract class VirtualCoinFactory {

public static double BIT_COIN_VALUE = 2000;

public static VirtualCoinFactory
getVirtualCoinFactory(double moneyToInvest) {

if(moneyToInvest >= BIT_COIN_VALUE) {
return new BitcoinFactory();
}
else {
return new EtheriumFactory();
}
}

public abstract VirtualCoin getVirtualCoin
(VirtualCoinType virtualCoinType);

public abstract Validator getValidator
(VirtualCoinType virtualCoinType);
}

2 – The BitcoinFactory: This is the class that extends the top class VirtualCoinFactory.

As a consequence, the getVirtualCoin(VirtualCoinType virtualCoinType) method has to be implemented returning the Bitcoin based on the type, Integer or Fractioned.

To do this process we have to use the polymorphism concept and return a generic type. We have to create this class:

public class VirtualCoin {

protected double volume;

protected int buyOrderAmount;
protected int sellOrderAmount;

// Getters and setters omitted
}
[/sourcecode]

In the Bitcoin classes, we have to extend VirtualCoin.


public class IntegerBitcoin extends VirtualCoin {

}

public class FractionedBitcoin extends VirtualCoin {

}

The getValidator(VirtualCoinType virtualCoinType) method is used to validate something in the Bitcoin. You can add any method you find suitable for your business requirements here. We also use polymorphism here.

public class BitcoinFactory extends VirtualCoinFactory {

@Override
public VirtualCoin getVirtualCoin(VirtualCoinType virtualCoinType) {
switch(virtualCoinType) {

case FRACTIONED:
return new FractionedBitcoin();

case INTEGER:
return new IntegerBitcoin();
default:
throw new IllegalArgumentException("Card type not found.");
}
}

public Validator getValidator(VirtualCoinType virtualCoinType) {
switch(virtualCoinType) {

case FRACTIONED:
return new FractionedBitcoinValidator();

case INTEGER:
return new IntegerBitcoinValidator();
default:
throw new IllegalArgumentException("Card type not found.");
}
}
}

3 – The EtheriumFactory: There is no new concept here, just the flow of Etherium.

public class EtheriumFactory extends VirtualCoinFactory {

@Override
public VirtualCoin getVirtualCoin(VirtualCoinType virtualCoinType) {
switch (virtualCoinType) {
case FRACTIONED:
return new FractionedEtherium();
case INTEGER:
return new IntegerEtherium();
}

throw new IllegalArgumentException (virtualCoinType + " not found");
}

@Override
public Validator getValidator(VirtualCoinType virtualCoinType) {
return new EtheriumValidator();
}
}

4 – Unitary Tests: Now we are going to see if the classes created from the Abstract Factory will be correct.

On the checkBitcoinCreationTest() method we get the instance of the VirtualCoin based on the value 2000, returning the BitcoinFactory. Having the BitcoinFactory we can get the IntegerBitcoin or the FractionedBitcoin based on the type we pass.

Then we check if the returned classes are correct. We use the Reflection methods getClass().getSimpleName() to check if the instances are correct. We do the same process with Etherium.

public class AbstractFactoryTest {

public static final double BITCOIN_VALUE = 2000;
public static final double ETHERIUM_VALUE = 1000;

@Test
public void checkBitcoinCreationTest() {
VirtualCoinFactory abstractFactory = VirtualCoinFactory.
getVirtualCoinFactory(BITCOIN_VALUE);

VirtualCoin integerBitcoin = abstractFactory.
getVirtualCoin(VirtualCoinType.INTEGER);
VirtualCoin fractionedBitcoin = abstractFactory.
getVirtualCoin(VirtualCoinType.FRACTIONED);

Assert.assertEquals("IntegerBitcoin", integerBitcoin.
getClass().getSimpleName());
Assert.assertEquals("FractionedBitcoin", fractionedBitcoin.
getClass().getSimpleName());
}

@Test
public void checkEtheriumCreationTest() {
VirtualCoinFactory abstractFactory = VirtualCoinFactory.
getVirtualCoinFactory(ETHERIUM_VALUE);

VirtualCoin integerEtherium = abstractFactory.
getVirtualCoin(VirtualCoinType.INTEGER);
VirtualCoin fractionedEtherium = abstractFactory.
getVirtualCoin(VirtualCoinType.FRACTIONED);

Assert.assertEquals("IntegerEtherium", integerEtherium.
getClass().getSimpleName());
Assert.assertEquals("FractionedEtherium", fractionedEtherium.
getClass().getSimpleName());
}
}

Summary of actions:

1 – Created the top Abstract Factory class.
2 – Created the generic method that returns the sub Factory.
3 – Created a generic method on the sub Factory class that returns the final class.

To practice the Abstract Factory Pattern you can create another sub Factory, it can be another VirtualCoin, and make it return the correct object! Try to use TDD (Test Driven Development). Start the development from the test.

Written by
Rafael del Nero
Join the discussion

2 comments