The Abstract Factory pattern is an extension of the Factory Method pattern. Instead of producing a single product, it provides an interface for creating families of related objects without specifying their concrete types. In Go, we can use the Abstract Factory pattern when creating multiple related objects that share a common theme or purpose but have variations.
Scenario: Notification System with Different Device Types
Let’s say we’re creating a system that can send different types of notifications (SMS
and Email
) on different device platforms (iOS
and Android
). Using the Abstract Factory pattern allows us to group each type of notification by platform and to create a new platform family of notifications without modifying client code.
Step-by-Step Implementation of Abstract Factory in Go
Step 1: Define Product Interfaces
First, define two interfaces for our products: SMSNotification
and EmailNotification
. Each interface has a Send
method that the structs will implement.
package main
import "fmt"
// SMSNotification is an interface for SMS notifications.
type SMSNotification interface {
SendSMS() string
}
// EmailNotification is an interface for Email notifications.
type EmailNotification interface {
SendEmail() string
}
Step 2: Define Concrete Structs for Each Platform
Create struct implementations for each platform type (iOS
and Android
) that satisfy both SMSNotification
and EmailNotification
interfaces.
// iOS-based notifications
// IOSSMS is a struct for iOS SMS notifications.
type IOSSMS struct{}
func (s IOSSMS) SendSMS() string {
return "Sending iOS SMS Notification"
}
// IOSEmail is a struct for iOS Email notifications.
type IOSEmail struct{}
func (e IOSEmail) SendEmail() string {
return "Sending iOS Email Notification"
}
// Android-based notifications
// AndroidSMS is a struct for Android SMS notifications.
type AndroidSMS struct{}
func (s AndroidSMS) SendSMS() string {
return "Sending Android SMS Notification"
}
// AndroidEmail is a struct for Android Email notifications.
type AndroidEmail struct{}
func (e AndroidEmail) SendEmail() string {
return "Sending Android Email Notification"
}
Step 3: Define the Abstract Factory Interface
The abstract factory interface provides methods for creating SMS and Email notifications. Each concrete factory for a platform (e.g., iOSFactory
or AndroidFactory
) will implement these methods.
// NotificationFactory is an abstract factory interface for creating notifications.
type NotificationFactory interface {
CreateSMSNotification() SMSNotification
CreateEmailNotification() EmailNotification
}
Step 4: Implement Concrete Factories
Create concrete factories for each platform. Each factory returns the appropriate struct for SMS and Email notifications, specific to the platform.
// IOSFactory is a concrete factory for iOS notifications.
type IOSFactory struct{}
func (f IOSFactory) CreateSMSNotification() SMSNotification {
return IOSSMS{}
}
func (f IOSFactory) CreateEmailNotification() EmailNotification {
return IOSEmail{}
}
// AndroidFactory is a concrete factory for Android notifications.
type AndroidFactory struct{}
func (f AndroidFactory) CreateSMSNotification() SMSNotification {
return AndroidSMS{}
}
func (f AndroidFactory) CreateEmailNotification() EmailNotification {
return AndroidEmail{}
}
Step 5: Create the Factory Selector
We define a function GetNotificationFactory
that takes in a platform type and returns the corresponding factory. This allows client code to retrieve the appropriate factory based on the platform.
// GetNotificationFactory returns a NotificationFactory based on the input type.
func GetNotificationFactory(platform string) NotificationFactory {
switch platform {
case "iOS":
return IOSFactory{}
case "Android":
return AndroidFactory{}
default:
return nil
}
}
Step 6: Using the Abstract Factory in Client Code
In the main
function, we select the factory based on the platform type. We then use this factory to create SMS and Email notifications without knowing the concrete types, allowing for scalable and modular code.
func main() {
// Get an iOS factory
factory := GetNotificationFactory("iOS")
if factory != nil {
sms := factory.CreateSMSNotification()
email := factory.CreateEmailNotification()
fmt.Println(sms.SendSMS()) // Output: Sending iOS SMS Notification
fmt.Println(email.SendEmail()) // Output: Sending iOS Email Notification
}
// Get an Android factory
factory = GetNotificationFactory("Android")
if factory != nil {
sms := factory.CreateSMSNotification()
email := factory.CreateEmailNotification()
fmt.Println(sms.SendSMS()) // Output: Sending Android SMS Notification
fmt.Println(email.SendEmail()) // Output: Sending Android Email Notification
}
}
Explanation of the Abstract Factory Pattern in Go Terms
Abstract Products (
SMSNotification
andEmailNotification
): These interfaces define methods for SMS and Email notifications that all concrete notification types must implement.Concrete Products: Each struct (e.g.,
IOSSMS
,AndroidSMS
,IOSEmail
,AndroidEmail
) implements one of the product interfaces, representing a specific type of notification for a specific platform.Abstract Factory (
NotificationFactory
): This interface declares factory methods for creating SMS and Email notifications. Each concrete factory will implement these methods for a specific platform.Concrete Factories (
IOSFactory
andAndroidFactory
): These structs implement theNotificationFactory
interface. Each factory produces concrete product instances (notifications) specific to the platform it represents.Factory Selector (
GetNotificationFactory
): This function allows client code to request a specific factory based on the platform type. This makes the factory easily extendable for additional platforms without modifying the client code.
Why Use Abstract Factory in Go?
- Encapsulation: Each platform-specific notification family is encapsulated in its factory, so client code doesn’t need to know the concrete details.
-
Flexibility: The client code interacts only with the
NotificationFactory
and product interfaces, allowing the underlying implementations to change without affecting the client. - Extensibility: New platforms (e.g., Windows) or new notification types (e.g., PushNotification) can be added by creating new factories and concrete products without modifying existing code.
Extending the Abstract Factory Pattern in Go
Suppose we want to add a new platform, like Windows
. We can create a WindowsFactory
and WindowsSMS
, and WindowsEmail
structs that implement the same interfaces. Then, we add a case in GetNotificationFactory
to return the WindowsFactory
.
This pattern is useful when we need to create families of related objects, and it enables clean, scalable code by decoupling object creation from usage, allowing the code to be easily extended with minimal modification to existing logic.