Design Pattern: Abstract Factory - using GoLang

Jayaprasanna Roddam - Nov 6 - - Dev Community

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
}
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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{}
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation of the Abstract Factory Pattern in Go Terms

  1. Abstract Products (SMSNotification and EmailNotification): These interfaces define methods for SMS and Email notifications that all concrete notification types must implement.

  2. 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.

  3. 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.

  4. Concrete Factories (IOSFactory and AndroidFactory): These structs implement the NotificationFactory interface. Each factory produces concrete product instances (notifications) specific to the platform it represents.

  5. 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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player