Implementation of the Abstract Factory pattern
In this article, we will look at the implementation of Abstract Factory pattern with Kotlin. The problem we will solve is for a client that needs to be able to watch different football leagues.
As written in “Design Patterns: Elements of Reusable Object-Oriented Software”, the intent of Abstract Factory pattern is to provide an interface for creating families of related or dependent objects without specifying their concrete classes. This interface is named AbstractFactory and defines two functions, one for creating the array of teams and the other for creating the league.
interface AbstractFactory {
fun createTeams(): Array<AbstractTeam>;
fun createLeague(): AbstractLeague;
}
The AbstractFactory interface has knowledge only of AbstractTeam and AbstractLeague. These are two abstract classes that define methods overriden by the concrete classes. AbstractTeam is an abstract class that defines the abstract functions every team should implement.
abstract class AbstractTeam {
abstract fun getName();
abstract fun getTopPlayer();
abstract fun getCurrentPlace();
}
AbstractLeague is also an abstract class that defines the abstract functions every league should implement. As you can see, the AbstractFactory uses only abstract classes and does not know anything about the concrete classes. The concrete classes will be used only by specific concrete factories, such as SerieAFactory and LaLigaFactory. Both these concrete factories are used to create country related teams and leagues.
abstract class AbstractLeague {
abstract fun getName();
abstract fun getCountry();
}
First, let’s look at the implementation of a concrete class that represents a team, Milan. It overrides all the functions and specifies information related to the Milan team. In the same way, we write other concrete classes that represent teams, such as Juventus, Barcelona and Real Madrid.
class Milan: AbstractTeam() {
override fun getName() {
println("A.C. Milan")
}
override fun getTopPlayer() {
println("Top Player: Gianluigi Donnarumma");
} override fun getCurrentPlace() {
println("Current Place: 1st");
}
}
Now, we will look at the implementation of a concrete class that represents a league, Serie A. SerieA class overrides the functions and as the concrete team class, it provides specific simple information about the Serie A league, such the name and country.
class SerieA: AbstractLeague() {
override fun getName() {
println("Serie A")
} override fun getCountry() {
println("Italy")
}
}
Same as the SerieA, we have created the LaLiga concrete class. It provides the name and country of the league.
class LaLiga:AbstractLeague() {
override fun getName() {
println("La Liga")
} override fun getCountry() {
println("Spain")
}
}
Now we have all the ingredients to cook the concrete factories. The main benefit of AbstractFactory is that it constrains the use of related concrete classes, such as Milan, Juventus and SerieA. When we use SerieAFactory, we are certain of creating only classes related to Italian football. On the other hand, when we use LaLigaFactory, we are certain of creating the LaLiga league and an array of type AbstractTeam, that consists of only Spanish teams, such as Barcelona and Real Madrid.
class SerieAFactory: AbstractFactory { override fun createTeams(): Array<AbstractTeam>; {
return arrayOf(Milan(), Juventus());
} override fun createLeague(): AbstractLeague {
return SerieA()
}
}
class LaLigaFactory: AbstractFactory { override fun createTeams(): Array<AbstractTeam>; {
return arrayOf(RealMadrid(), Barcelona())
} override fun createLeague(): AbstractLeague {
return LaLiga()
}
}
Finally, as we can see below, we are able to instantiate different clients that watch different leagues very easily. The only thing we need to do is to pass to method watchLeague(factory: AbstractFactory)
a concrete factory class. It can be a SerieAFactory or a LaLigaFactory. The design is quite flexible, because clients know nothing about the concrete classes and handle everything using the abstract classes. We maintain a level of decoupling between clients and concrete classes (Milan, SerieA, Barcelona, LaLiga, etc.). As a result, we can add new teams and create new leagues very easily benefiting from inheritance and subclassing.
class Client() {
fun watchLeague(factory: AbstractFactory) {
val league = factory.createLeague();
league.getName()
league.getCountry() for (team in factory.createTeams()) {
team.getName();
team.getTopPlayer();
team.getCurrentPlace();
}
}
}fun main(args: Array<String>) {
val client = Client()
client.watchLeague(SerieAFactory())
val client2 = Client()
client2.watchLeague(LaLigaFactory())
}
For more similar content, visit https://lejdiprifti.web.app