In this blog post, you will learn what the Abstract Factory design pattern is, why the Abstract Factory design pattern is important, and how to implement an Abstract Factory design pattern. Also, you will see an Abstract Factory design pattern implemented in a mobile application.
What Is the Abstract Factory Design Pattern?
โAbstract Factory is a creational design pattern that lets you produce families of related objects without specifying their concrete classesโ (Abstract Factory, 2023). The Abstract Factory is an interface for concrete factories to create a family of objects (Abstract factory pattern, 2017). One common application of the Abstract Factory design pattern is to create objects for specific platforms, such as operating systems, databases, windowing systems, and so on (Abstract factory design pattern, n.d.).
Why Is the Abstract Factory Design Pattern Important?
The Abstract Factory design pattern can prevent mixing up the creation of objects that belong to different families of objects, and it also makes it so that the core code does not need to be modified when the families of objects change (Abstract Factory, 2023). Also, the Abstract Factory design pattern prevents the proliferation of #ifdef and if statements in code when creating platform dependent objects (Abstract factory design pattern, n.d.), which can make the code more convoluted.
How Do You Implement an Abstract Factory Design Pattern?
The Abstract Factory design is comprised of five components, which are the Abstract Factory, Concrete Factories, Abstract Products, Concrete Products, and Client (Abstract Factory Pattern, 2017). The Abstract Factory creates a contract for concrete factories to define a method per Concrete Product to be created and returned by the Concrete Factory (Abstract factory design pattern, n.d.; Abstract Factory Pattern, 2017). Each method of the Abstract Factory specifies the return type of an Abstract Product in its method signature, which serves as a contract for the Concrete Product that must be returned by the Concrete Factory (Abstract Factory Pattern, 2017).
The following is the UML diagram of the Abstract Factory design pattern:
A Real-World Application of the Abstract Factory Design Pattern
When using advertisements to monetize mobile applications developed in Flutter and Dart, the creation of an ad widget object on the Android platform is different from its creation of the ad on the iOS platform. Therefore, an Abstract Factory design pattern is utilized to create ad widgets. Engineering an abstract factory in advanced prevents the proliferation of if statements in the core code when creating ad widgets.
The code in this section makes use of the google_mobile_ads
flutter package.
First, let’s examine the code for the AbstractBannerAdWidget
, which represents the Abstract Product in the Abstract Factory design pattern:
import 'package:flutter/material.dart';
abstract class AbstractBannerAdWidget extends StatefulWidget {
const AbstractBannerAdWidget({super.key});
}
The above abstract class is being used as an interface to be coupled with concrete ad widgets at runtime. Notice the AbstractBannerAdWidget
extends the StatefulWidget
, which is a widget that maintains state in Flutter.
Second, let’s examine the AndroidBannerAdWidget
, which represents the Concrete Product in the Abstract Factory design pattern.
import 'package:computer_science_wire/AbstractBannerAdWidget.dart';
import 'package:computer_science_wire/BannerAdWidgetState.dart';
import 'package:flutter/material.dart';
class AndroidBannerAdWidget extends AbstractBannerAdWidget {
final String AD_UNIT_ID = "<insert ad id here>";
const AndroidBannerAdWidget({super.key});
@override
State<StatefulWidget> createState() {
// ignore: no_logic_in_create_state
return BannerAdWidgetState(AD_UNIT_ID);
}
}
The AndroidBannerAdWidget
class implements the Flutter class StatefulWidget
by overriding createState
method, which returns the BannerAdWidgetState
. The value of AD_UNIT_ID
is copied and pasted from the Google Ad Mob website, which is created when you create a banner ad under your account for a mobile application you are building.
Third, let’s examine the code for the BannerAdWidgetState
class, which is the following:
import 'package:computer_science_wire/AbstractBannerAdWidget.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:flutter/material.dart';
class BannerAdWidgetState extends State<AbstractBannerAdWidget> {
late BannerAd _bannerAd;
String adUnitId;
BannerAdWidgetState(this.adUnitId);
@override
void initState() {
super.initState();
BannerAd(
adUnitId: adUnitId,
request: const AdRequest(),
size: AdSize.banner,
listener: BannerAdListener(
onAdLoaded: (ad) {
setState(() {
_bannerAd = ad as BannerAd;
});
},
onAdFailedToLoad: (ad, err) {
ad.dispose();
},
),
).load();
}
@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.topCenter,
child: Container(
width: _bannerAd.size.width.toDouble(),
height: _bannerAd.size.height.toDouble(),
child: AdWidget(ad: _bannerAd),
),
);
}
@override
void dispose() {
_bannerAd.dispose();
super.dispose();
}
}
The above class loads the banner ad in the initState
method, and it is also responsible for building the widget for the banner in the build
method.
The purpose of the above class is to follow the DRY principle, which stands for Don’t Repeat Yourself. This means the same lines of code should not be repeated in any program. In this case, the AndroidBannerAdWidget
and IOSBannerAdWidget
classes will not contain the implementation of BannerAdWidgetState
twice.
Forth, let’s examine the code for the IOSBannerAdWidget
class, which is a Concrete Product, and it has the following implementation:
import 'package:computer_science_wire/AbstractBannerAdWidget.dart';
import 'package:flutter/material.dart';
import 'package:computer_science_wire/BannerAdWidgetState.dart';
class IOSBannerAdWidget extends AbstractBannerAdWidget {
final String AD_UNIT_ID = "<enter the ad id for the ios platform here>";
const IOSBannerAdWidget({super.key});
@override
State<StatefulWidget> createState() {
return BannerAdWidgetState(AD_UNIT_ID);
}
}
You will notice the code is similar to the AndroidBannerAdWidget
, but it is passing in the AD_UNIT_ID
value for the iOS platform to the constructor of the BannerAdWidgetState
.
Fifth, let’s examine the code for the AbstractAdFactory
, which has the following implementation:
import 'package:computer_science_wire/AbstractBannerAdWidget.dart';
abstract class AbstractAdFactory {
AbstractBannerAdWidget getBannerAdWidget();
}
The above class represents the Abstract Factory, and it is responsible for defining the contract that the concrete factories must implement. Notice that each concrete factory will be responsible implementing a getBannerAdWidget
method.
Sixth, let’s examine the code for the AndroidAdFactory
, which is a Concrete factory that has the following implementation:
import "package:computer_science_wire/AbstractAdFactory.dart";
import "package:computer_science_wire/AbstractBannerAdWidget.dart";
import "package:computer_science_wire/AndroidBannerAdWidget.dart";
class AndroidAdFactory implements AbstractAdFactory {
@override
AbstractBannerAdWidget getBannerAdWidget() {
return const AndroidBannerAdWidget();
}
}
As you can see, the AndroidAdFactory
is responsible for instantiating the Concrete AndroidBannerAdWidget
object. There are other types of ads that could be created in the AndroidAdFactory
, but the application only uses a banner ad, so the AndroidAdFactory
creates a family of one object in this case.
Seventh, let’s examine the code for the IOSAdFactory
, which has the following implementation:
import 'package:computer_science_wire/AbstractAdFactory.dart';
import 'package:computer_science_wire/AbstractBannerAdWidget.dart';
import 'package:computer_science_wire/IOSBannerAdWidget.dart';
class IOSAdFactory implements AbstractAdFactory {
@override
AbstractBannerAdWidget getBannerAdWidget() {
return const IOSBannerAdWidget();
}
}
The IOSAdFactory
is the Concrete factory for instantiating the concrete IOSBannerAdWidget
object.
Finally, let’s examine the code for SuperFactory
, and it has the following implementation:
import 'package:computer_science_wire/AbstractAdFactory.dart';
import 'package:computer_science_wire/AndroidAdFactory.dart';
import 'package:computer_science_wire/IOSAdFactory.dart';
import 'dart:io';
class SuperAdFactory {
static AbstractAdFactory? getAdFactory() {
if (Platform.isAndroid) {
return AndroidAdFactory();
}
else if (Platform.isIOS) {
return IOSAdFactory();
}
return null;
}
}
The SuperAdFactory
is in charge of instantiating the concrete factory depending on what platform the mobile application is running on. Notice that the if statements for determining the platform are isolated within the SuperAdFactory
so that they are not in the core code of the mobile application.
Conclusion
In this blog post, you learned the Abstract Factory design pattern enables one to create families of related objects without specifying concrete classes. You learned the Abstract Factory design pattern is important because it prevents mixing the creation of families of objects and removes conditionals that check which platform is being used in the core code. You learned how to implement an Abstract Factory design pattern in general. Lastly, you saw how the Abstract Factory design pattern can be utilized in a real-world mobile application.
If you found this blog post to be valuable, then you should consider doing the following:
- Share it!
- Subscribe to my blog!
- Consider buying me a coffee!
References
Abstract factory design pattern. (n.d.). Sourcemaking.com. Retrieved June 5, 2024, from https://sourcemaking.com/design_patterns/abstract_factory
Abstract Factory. (2023, January 1). Refactoring.Guru. https://refactoring.guru/design-patterns/abstract-factory
Abstract factory pattern. (2017, July 14). GeeksforGeeks. https://www.geeksforgeeks.org/abstract-factory-pattern/