Let’s imagine we need to know exchange rates in different cities, for this we are going to the Central Bank and are asking for an entity that can give us exchange rates for specific cities.
Factory
CentralBank will create or will give us instances of ExchangePlace based on the city name that are passing as a parameter – CentralBank will be a factory of ExchangeRates:
interface CentralBank { ExchangePlace getExchangePlace(String city); } interface ExchangePlace { BigDecimal getExchangeRateFor(String currency); String cityName(); }
Let’s implement two exchange places, one for Chisinau and another for Balti:
class ChisinauExchange implements ExchangePlace { private final MapexchangeRate; public ChisinauExchange() { exchangeRate = new HashMap<>(); exchangeRate.put("EUR", BigDecimal.valueOf(19.58)); } @Override public BigDecimal getExchangeRateFor(String currency) { return exchangeRate.get(currency); } @Override public String cityName() { return "Chisinau"; } } class BaltiExchange implements ExchangePlace { private final Map exchangeRate; public BaltiExchange() { exchangeRate = new HashMap<>(); exchangeRate.put("EUR", BigDecimal.valueOf(19.52)); } @Override public BigDecimal getExchangeRateFor(String currency) { return exchangeRate.get(currency).multiply(BigDecimal.valueOf(1.01)); } @Override public String cityName() { return "Balti"; } }
Now, we will create a concrete CentralBank (the factory) that will create/give us instances for ExchangeRates:
class NationalBankOfMoldova implements CentralBank { private final Mapexchanges; NationalBankOfMoldova() { this.exchanges = new HashMap<>(); exchanges.put("Chisinau", new ChisinauExchange()); exchanges.put("Balti", new BaltiExchange()); } @Override public ExchangePlace getExchangePlace(String city) { return exchanges.get(city); } }
Now the we have our factory implemented, we can use it by asking for EchangePlaces by city name:
class Scratch { public static void main(String[] args) { CentralBank centralBank = new NationalBankOfMoldova(); ExchangePlace baltiExchangePlace = centralBank.getExchangePlace("Balti"); printEuroRate(baltiExchangePlace); } static void printEuroRate(ExchangePlace exchangePlace) { System.out.println(exchangePlace.cityName() + " EUR <-> MDL exchange rate: " + exchangePlace.getExchangeRateFor("EUR")); } }
Output:
Balti EUR <-> MDL exchange rate: 19.7152
AbstractFactory
Now, let’s imagine that our software is growing and we are extending our business to other countries, we had the CentralBank
that gave us instances of ExchangePlace
, now we know that there is a GlobalBank
that will give us instances of CentralBanks
interface GlobalBank { CentralBank getBankForCountry(String country); }
Each continent have a bank that implements GlobalBank:
class BigEuropeanBank implements GlobalBank { private final MapregionalBanks; public BigEuropeanBank() { this.regionalBanks = new HashMap<>(); this.regionalBanks.put("Moldova", new NationalBankOfMoldova()); // we can add more countries here } @Override public CentralBank getBankForCountry(String country) { return regionalBanks.get(country); } }
This is how we use it:
class Scratch { public static void main(String[] args) { // abstract factory (GlobalBank) creates factories (CentralBank) that are creating ExchangePlaces GlobalBank bigEuropeanBank = new BigEuropeanBank(); CentralBank centralBankOfMoldova = bigEuropeanBank.getBankForCountry("Moldova"); ExchangePlace chisinauExchangePlace = centralBankOfMoldova.getExchangePlace("Chisinau"); printEuroRate(chisinauExchangePlace); } static void printEuroRate(ExchangePlace exchangePlace) { System.out.println(exchangePlace.cityName() + " EUR <-> MDL exchange rate: " + exchangePlace.getExchangeRateFor("EUR")); } }
Output:
Chisinau EUR <-> MDL exchange rate: 19.58