Abstract Factory Pattern #
When an application has to run across several different environments — say AWS in production, GCP in staging, and mocks in unit tests — the problem is not just one object that differs, but the entire family of objects that must be swapped at once: storage, message queue, secret manager, monitoring, all of it has to come from the same provider and work consistently with each other. Swapping them one by one by hand in many places is not just tedious — it invites inconsistency: you could accidentally use AWS S3 alongside GCP Pub/Sub. The Abstract Factory Pattern solves this by providing a single point to swap the whole family of objects at once, without touching the code that uses them.
What Is the Abstract Factory Pattern? #
Abstract Factory is a creational design pattern that provides an interface for creating a set of related objects, without specifying their concrete classes. Instead of creating each object individually, the client asks one factory to create all the objects it needs — and that factory guarantees every object comes from the same “family” and is compatible with the rest.
The most fundamental difference between Abstract Factory and Factory Method: Factory Method handles one product, while Abstract Factory handles a product family. A single NotificationFactory can create a Sender, a Formatter, and an AuditLogger — all three come from the Email “family” or the SMS “family”, and are guaranteed to be compatible.
Three properties define Abstract Factory:
- Objects are created as a family — one factory, many related products
- Consistency is guaranteed — you cannot accidentally mix products from different families
- The client knows no concrete classes — it only interacts with product interfaces and the factory interface
flowchart TD
subgraph Client
C[NotificationService]
end
subgraph Abstract Layer
AF[NotificationFactory\\ninterface]
AS[Sender\\ninterface]
AFmt[Formatter\\ninterface]
AL[AuditLogger\\ninterface]
end
subgraph Email Family
EF[EmailFactory]
ES[EmailSender]
EFmt[EmailFormatter]
EL[EmailAuditLogger]
end
subgraph SMS Family
SF[SMSFactory]
SS[SMSSender]
SFmt[SMSFormatter]
SL[SMSAuditLogger]
end
C -->|only knows| AF
AF --> EF
AF --> SF
EF -->|creates| ES
EF -->|creates| EFmt
EF -->|creates| EL
SF -->|creates| SS
SF -->|creates| SFmt
SF -->|creates| SL
ES & SS -->|implement| AS
EFmt & SFmt -->|implement| AFmt
EL & SL -->|implement| ALWhy Object “Families” Matter #
The problem Abstract Factory solves is not about creating a single object — Factory Method already handles that. The problem is guaranteeing consistency between objects that must work together.
Consider the following scenario without Abstract Factory:
// ANTI-PATTERN: mixing objects from different families — nothing prevents this
type NotificationService struct {
sender Sender
formatter Formatter
logger AuditLogger
}
func NewNotificationService() *NotificationService {
return &NotificationService{
sender: &EmailSender{}, // from the Email family
formatter: &SMSFormatter{}, // from the SMS family — inconsistent!
logger: &EmailAuditLogger{}, // from the Email family
}
// The compiler will not complain — but SMS-formatted text gets sent via Email
// Bugs like this are extremely hard to debug
}
// CORRECT: the factory guarantees every object comes from the same family
func NewNotificationService(factory NotificationFactory) *NotificationService {
return &NotificationService{
sender: factory.CreateSender(),
formatter: factory.CreateFormatter(),
logger: factory.CreateAuditLogger(),
}
// Mixing is impossible — the factory is the only door
}
Inconsistencies like this can be very subtle in large systems. Abstract Factory removes the possibility structurally, not just by convention.
Structure and Components #
Abstract Factory has more components than Factory Method because it manages a family of products rather than a single product. Understanding each layer helps you read and write implementations correctly.
classDiagram
class NotificationFactory {
<<interface>>
+CreateSender() Sender
+CreateFormatter() Formatter
+CreateAuditLogger() AuditLogger
}
class Sender {
<<interface>>
+Send(to, message string) error
}
class Formatter {
<<interface>>
+Format(template string, data map) string
+FormatSubject(subject string) string
}
class AuditLogger {
<<interface>>
+Log(event NotificationEvent) error
}
class EmailFactory {
+smtpHost string
+apiKey string
+CreateSender() Sender
+CreateFormatter() Formatter
+CreateAuditLogger() AuditLogger
}
class SMSFactory {
+providerURL string
+accountSID string
+CreateSender() Sender
+CreateFormatter() Formatter
+CreateAuditLogger() AuditLogger
}
NotificationFactory <|.. EmailFactory
NotificationFactory <|.. SMSFactory
Sender <|.. EmailSender
Sender <|.. SMSSender
Formatter <|.. EmailFormatter
Formatter <|.. SMSFormatter
AuditLogger <|.. EmailAuditLogger
AuditLogger <|.. SMSAuditLogger
EmailFactory ..> EmailSender : creates
EmailFactory ..> EmailFormatter : creates
EmailFactory ..> EmailAuditLogger : creates
SMSFactory ..> SMSSender : creates
SMSFactory ..> SMSFormatter : creates
SMSFactory ..> SMSAuditLogger : createsThe five components of Abstract Factory:
| Component | Role | Example |
|---|---|---|
| Abstract Factory | Interface defining all creation methods | NotificationFactory |
| Concrete Factory | Implementation that creates one product family | EmailFactory, SMSFactory |
| Abstract Product | Interface for each product type | Sender, Formatter, AuditLogger |
| Concrete Product | Concrete implementation of each product | EmailSender, SMSSender |
| Client | Uses factory and products through interfaces only | NotificationService |
Full Implementation: Notification System #
Let’s build a notification system that supports Email and SMS. Each channel needs three components: a Sender to deliver messages, a Formatter to format the message, and an AuditLogger to record activity.
Abstract Products #
Define an interface for each product type the factory will create. This is the contract every concrete product must satisfy.
package notification
import "time"
// NotificationEvent represents one notification delivery event.
type NotificationEvent struct {
Channel string
Recipient string
Message string
SentAt time.Time
Success bool
Error string
}
// Sender is responsible for delivering a message to the recipient.
type Sender interface {
Send(recipient, message string) error
ChannelName() string
}
// Formatter is responsible for formatting message content per channel.
type Formatter interface {
FormatBody(template string, data map[string]string) string
FormatSubject(subject string) string // relevant for email, ignored for SMS
}
// AuditLogger records all delivery activity for auditing purposes.
type AuditLogger interface {
Log(event NotificationEvent) error
GetLogs(recipient string) ([]NotificationEvent, error)
}
Abstract Factory #
One factory interface defining all products in a single family.
// NotificationFactory is the abstract factory — it defines the contract
// for creating an entire family of notification objects.
type NotificationFactory interface {
CreateSender() Sender
CreateFormatter() Formatter
CreateAuditLogger() AuditLogger
}
Concrete Products: The Email Family #
package notification
import (
"fmt"
"net/smtp"
"strings"
"sync"
"time"
)
// EmailSender delivers notifications over the SMTP protocol.
type EmailSender struct {
smtpHost string
smtpPort int
username string
password string
fromAddr string
}
func (e *EmailSender) Send(recipient, message string) error {
addr := fmt.Sprintf("%s:%d", e.smtpHost, e.smtpPort)
auth := smtp.PlainAuth("", e.username, e.password, e.smtpHost)
body := fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: Notification\r\n\r\n%s",
e.fromAddr, recipient, message)
if err := smtp.SendMail(addr, auth, e.fromAddr, []string{recipient}, []byte(body)); err != nil {
return fmt.Errorf("email send failed: %w", err)
}
return nil
}
func (e *EmailSender) ChannelName() string { return "email" }
// EmailFormatter formats messages with a structure suited to email.
type EmailFormatter struct{}
func (f *EmailFormatter) FormatBody(template string, data map[string]string) string {
result := template
for key, value := range data {
result = strings.ReplaceAll(result, "{{"+key+"}}", value)
}
// Email can carry richer content — add a header and footer
return fmt.Sprintf("Dear Customer,\n\n%s\n\nRegards,\nSupport Team", result)
}
func (f *EmailFormatter) FormatSubject(subject string) string {
return fmt.Sprintf("[Notification] %s", subject)
}
// EmailAuditLogger records email delivery logs in an in-memory store.
// In production, this would write to a database or logging service.
type EmailAuditLogger struct {
mu sync.Mutex
logs []NotificationEvent
}
func (l *EmailAuditLogger) Log(event NotificationEvent) error {
l.mu.Lock()
defer l.mu.Unlock()
l.logs = append(l.logs, event)
fmt.Printf("[EMAIL AUDIT] %s -> %s (success: %v)\n",
event.SentAt.Format(time.RFC3339), event.Recipient, event.Success)
return nil
}
func (l *EmailAuditLogger) GetLogs(recipient string) ([]NotificationEvent, error) {
l.mu.Lock()
defer l.mu.Unlock()
var result []NotificationEvent
for _, log := range l.logs {
if log.Recipient == recipient {
result = append(result, log)
}
}
return result, nil
}
Concrete Products: The SMS Family #
// SMSSender delivers notifications through an SMS gateway.
type SMSSender struct {
providerURL string
accountSID string
authToken string
fromNumber string
}
func (s *SMSSender) Send(recipient, message string) error {
// In a real implementation: call the provider API (Twilio, Vonage, etc.)
fmt.Printf("[SMS] Sending to %s via %s: %s\n", recipient, s.providerURL, message)
return nil
}
func (s *SMSSender) ChannelName() string { return "sms" }
// SMSFormatter formats messages to fit SMS limits (160 characters per segment).
type SMSFormatter struct {
maxLength int
}
func (f *SMSFormatter) FormatBody(template string, data map[string]string) string {
result := template
for key, value := range data {
result = strings.ReplaceAll(result, "{{"+key+"}}", value)
}
// SMS needs no header/footer — short and to the point
if len(result) > f.maxLength {
return result[:f.maxLength-3] + "..."
}
return result
}
func (f *SMSFormatter) FormatSubject(subject string) string {
// SMS has no subject — return an empty string
return ""
}
// SMSAuditLogger records SMS delivery logs.
type SMSAuditLogger struct {
mu sync.Mutex
logs []NotificationEvent
}
func (l *SMSAuditLogger) Log(event NotificationEvent) error {
l.mu.Lock()
defer l.mu.Unlock()
l.logs = append(l.logs, event)
fmt.Printf("[SMS AUDIT] %s -> %s (success: %v)\n",
event.SentAt.Format(time.RFC3339), event.Recipient, event.Success)
return nil
}
func (l *SMSAuditLogger) GetLogs(recipient string) ([]NotificationEvent, error) {
l.mu.Lock()
defer l.mu.Unlock()
var result []NotificationEvent
for _, log := range l.logs {
if log.Recipient == recipient {
result = append(result, log)
}
}
return result, nil
}
Concrete Factories #
Each factory gathers its configuration and creates its entire product family. All the configuration complexity stays hidden here — the client never needs to know these details.
// EmailConfig stores all the configuration the email factory needs.
type EmailConfig struct {
SMTPHost string
SMTPPort int
Username string
Password string
FromAddr string
}
// EmailFactory creates the entire family of email-based notification objects.
type EmailFactory struct {
config EmailConfig
logger *EmailAuditLogger // shared logger within one factory instance
}
func NewEmailFactory(cfg EmailConfig) *EmailFactory {
return &EmailFactory{
config: cfg,
logger: &EmailAuditLogger{},
}
}
func (f *EmailFactory) CreateSender() Sender {
return &EmailSender{
smtpHost: f.config.SMTPHost,
smtpPort: f.config.SMTPPort,
username: f.config.Username,
password: f.config.Password,
fromAddr: f.config.FromAddr,
}
}
func (f *EmailFactory) CreateFormatter() Formatter {
return &EmailFormatter{}
}
func (f *EmailFactory) CreateAuditLogger() AuditLogger {
return f.logger // all email senders share one logger
}
// SMSConfig stores all the configuration the SMS factory needs.
type SMSConfig struct {
ProviderURL string
AccountSID string
AuthToken string
FromNumber string
MaxLength int
}
// SMSFactory creates the entire family of SMS-based notification objects.
type SMSFactory struct {
config SMSConfig
logger *SMSAuditLogger
}
func NewSMSFactory(cfg SMSConfig) *SMSFactory {
maxLen := cfg.MaxLength
if maxLen == 0 {
maxLen = 160 // SMS standard
}
return &SMSFactory{
config: cfg,
logger: &SMSAuditLogger{},
}
}
func (f *SMSFactory) CreateSender() Sender {
return &SMSSender{
providerURL: f.config.ProviderURL,
accountSID: f.config.AccountSID,
authToken: f.config.AuthToken,
fromNumber: f.config.FromNumber,
}
}
func (f *SMSFactory) CreateFormatter() Formatter {
return &SMSFormatter{maxLength: f.config.MaxLength}
}
func (f *SMSFactory) CreateAuditLogger() AuditLogger {
return f.logger
}
Client Code #
The client uses NotificationFactory without ever naming a single concrete type. Switching from Email to SMS only requires swapping the injected factory.
// NotificationService is the client that uses the abstract factory.
type NotificationService struct {
factory NotificationFactory
sender Sender
formatter Formatter
logger AuditLogger
}
func NewNotificationService(factory NotificationFactory) *NotificationService {
return &NotificationService{
factory: factory,
sender: factory.CreateSender(),
formatter: factory.CreateFormatter(),
logger: factory.CreateAuditLogger(),
}
}
// Send delivers a notification and records the result in the audit log.
func (s *NotificationService) Send(recipient, template string, data map[string]string) error {
body := s.formatter.FormatBody(template, data)
err := s.sender.Send(recipient, body)
event := NotificationEvent{
Channel: s.sender.ChannelName(),
Recipient: recipient,
Message: body,
SentAt: time.Now(),
Success: err == nil,
}
if err != nil {
event.Error = err.Error()
}
_ = s.logger.Log(event) // the audit trail is recorded even if sending fails
return err
}
// SendWelcome is an example business method that knows nothing about channels.
func (s *NotificationService) SendWelcome(recipient, username string) error {
return s.Send(
recipient,
"Welcome, {{name}}! Your account is now active.",
map[string]string{"name": username},
)
}
func (s *NotificationService) SendOTP(recipient, otp string) error {
return s.Send(
recipient,
"Your OTP code is {{otp}}. It expires in 5 minutes.",
map[string]string{"otp": otp},
)
}
Usage from main.go:
func main() {
// Configuration is read from the environment or a config file
emailCfg := notification.EmailConfig{
SMTPHost: "smtp.gmail.com",
SMTPPort: 587,
Username: os.Getenv("SMTP_USER"),
Password: os.Getenv("SMTP_PASS"),
FromAddr: "[email protected]",
}
smsCfg := notification.SMSConfig{
ProviderURL: "https://api.twilio.com",
AccountSID: os.Getenv("TWILIO_SID"),
AuthToken: os.Getenv("TWILIO_TOKEN"),
FromNumber: "+628123456789",
MaxLength: 160,
}
// Pick the factory in one place — the whole object family switches with it
var factory notification.NotificationFactory
if os.Getenv("NOTIFICATION_CHANNEL") == "sms" {
factory = notification.NewSMSFactory(smsCfg)
} else {
factory = notification.NewEmailFactory(emailCfg)
}
svc := notification.NewNotificationService(factory)
// Business code does not know whether this is email or SMS
svc.SendWelcome("[email protected]", "Budi")
svc.SendOTP("[email protected]", "847291")
}
Second Case Study: Cloud Provider Abstraction #
The notification system is a relatively simple example. Abstract Factory really shines in more complex scenarios like cloud provider abstraction, where a single “family” can consist of a dozen interdependent components.
// CloudFactory is the abstract factory for cloud resources.
// One factory represents one cloud provider — all its resources are compatible.
type CloudFactory interface {
CreateObjectStorage() ObjectStorage
CreateMessageQueue() MessageQueue
CreateSecretManager() SecretManager
CreateMetricsCollector() MetricsCollector
}
// ObjectStorage is the abstract product for object storage.
type ObjectStorage interface {
Put(bucket, key string, data []byte) error
Get(bucket, key string) ([]byte, error)
Delete(bucket, key string) error
ListKeys(bucket, prefix string) ([]string, error)
}
// MessageQueue is the abstract product for message queues.
type MessageQueue interface {
Publish(topic string, payload []byte) error
Subscribe(topic string, handler func([]byte) error) error
}
// SecretManager is the abstract product for secret management.
type SecretManager interface {
GetSecret(name string) (string, error)
SetSecret(name, value string) error
}
// MetricsCollector is the abstract product for metric delivery.
type MetricsCollector interface {
Counter(name string, tags map[string]string) error
Histogram(name string, value float64, tags map[string]string) error
}
With this structure, swapping the entire cloud infrastructure is just a matter of swapping the factory:
// In production: every resource from AWS
var factory CloudFactory = NewAWSFactory(awsConfig)
// In staging: every resource from GCP
var factory CloudFactory = NewGCPFactory(gcpConfig)
// In unit tests: every resource from mocks
var factory CloudFactory = NewMockCloudFactory()
// Business code does not change at all
app := NewApplication(factory)
app.Run()
flowchart LR
subgraph Production
AF1[AWSFactory] --> AS[S3Storage]
AF1 --> AQ[SQSQueue]
AF1 --> ASM[AWSSecretsManager]
AF1 --> AM[CloudWatch]
end
subgraph Staging
GF[GCPFactory] --> GS[GCSStorage]
GF --> GQ[PubSubQueue]
GF --> GSM[SecretManagerGCP]
GF --> GM[StackdriverMetrics]
end
subgraph Testing
MF[MockFactory] --> MS[InMemoryStorage]
MF --> MQ[InMemoryQueue]
MF --> MSM[InMemorySecrets]
MF --> MM[NoopMetrics]
end
App[Application] -->|prod| AF1
App -->|staging| GF
App -->|test| MFAbstract Factory vs Factory Method #
These two patterns are often confused because their names are similar and both deal with object creation. The difference is fundamental:
// Factory Method — one product, one method
type PaymentFactory interface {
CreatePayment() Payment // one product
}
// Abstract Factory — a product family, many methods
type InfrastructureFactory interface {
CreateStorage() Storage // first product
CreateQueue() MessageQueue // second product
CreateCache() Cache // third product
CreateLogger() DistributedLogger // fourth product
// all of them must be compatible with each other
}
An easy way to tell them apart: if you only need one Create...(), use Factory Method. If you need several Create...() methods whose products must be compatible with each other, use Abstract Factory.
| Aspect | Factory Method | Abstract Factory |
|---|---|---|
| Number of products | One | Many (a family) |
| Factory interface | One method | Many methods |
| Problem solved | Flexibility in creating a single object | Consistency between related objects |
| Complexity | Lower | Higher |
| When to use | Variations of one concept’s implementation | Variations of an entire implementation stack |
| Example | NewPayment(method) | NewInfrastructure(provider) |
Testing with a Mock Factory #
Abstract Factory makes testing very clean. You can create a MockFactory that returns mock products, and the whole test suite runs without external dependencies.
// MockNotificationFactory for testing — replaces the Email or SMS factory
type MockNotificationFactory struct {
sender *MockSender
formatter *MockFormatter
logger *MockAuditLogger
}
func NewMockNotificationFactory() *MockNotificationFactory {
return &MockNotificationFactory{
sender: &MockSender{},
formatter: &MockFormatter{},
logger: &MockAuditLogger{},
}
}
func (f *MockNotificationFactory) CreateSender() Sender { return f.sender }
func (f *MockNotificationFactory) CreateFormatter() Formatter { return f.formatter }
func (f *MockNotificationFactory) CreateAuditLogger() AuditLogger { return f.logger }
// MockSender records calls for assertion in tests
type MockSender struct {
SentMessages []struct{ Recipient, Message string }
ShouldFail bool
}
func (m *MockSender) Send(recipient, message string) error {
if m.ShouldFail {
return errors.New("mock send failure")
}
m.SentMessages = append(m.SentMessages, struct{ Recipient, Message string }{recipient, message})
return nil
}
func (m *MockSender) ChannelName() string { return "mock" }
// MockFormatter returns a predictable string for assertions
type MockFormatter struct{}
func (f *MockFormatter) FormatBody(template string, data map[string]string) string {
return template // return the template as-is for testing
}
func (f *MockFormatter) FormatSubject(subject string) string { return subject }
// MockAuditLogger records logs for assertion in tests
type MockAuditLogger struct {
Events []NotificationEvent
}
func (l *MockAuditLogger) Log(event NotificationEvent) error {
l.Events = append(l.Events, event)
return nil
}
func (l *MockAuditLogger) GetLogs(recipient string) ([]NotificationEvent, error) {
return l.Events, nil
}
// Clean tests — no SMTP, no network
func TestNotificationService_SendWelcome(t *testing.T) {
mockFactory := NewMockNotificationFactory()
svc := NewNotificationService(mockFactory)
err := svc.SendWelcome("[email protected]", "Budi")
if err != nil {
t.Fatalf("expected no error, got: %v", err)
}
// Assert the message was sent
if len(mockFactory.sender.SentMessages) != 1 {
t.Errorf("expected 1 message sent, got %d", len(mockFactory.sender.SentMessages))
}
if mockFactory.sender.SentMessages[0].Recipient != "[email protected]" {
t.Errorf("wrong recipient")
}
// Assert the audit log was recorded
if len(mockFactory.logger.Events) != 1 {
t.Errorf("expected 1 audit event, got %d", len(mockFactory.logger.Events))
}
if !mockFactory.logger.Events[0].Success {
t.Errorf("expected success=true in audit log")
}
}
func TestNotificationService_SendWelcome_WhenSenderFails(t *testing.T) {
mockFactory := NewMockNotificationFactory()
mockFactory.sender.ShouldFail = true
svc := NewNotificationService(mockFactory)
err := svc.SendWelcome("[email protected]", "Budi")
if err == nil {
t.Error("expected error when sender fails")
}
// The audit log is still recorded even on failure
if len(mockFactory.logger.Events) != 1 {
t.Errorf("expected audit log even on failure")
}
if mockFactory.logger.Events[0].Success {
t.Errorf("expected success=false in audit log")
}
}
Don’t Create a Factory That Is Too Large
If your abstract factory has more than 5-7
Create...()methods, that is a strong signal the factory is trying to do too much. Consider splitting it into several smaller, focused factories — for instance, a separateStorageFactoryandMessagingFactoryinstead of one monolithicInfrastructureFactory.
When to Use and When Not to #
Abstract Factory is a powerful pattern, but it is not the answer to every problem. Overusing it produces complex code without real benefit.
USE Abstract Factory if:
✓ There are several implementation "families" that will be swapped between
✓ Objects within one family must be compatible and consistent
✓ The client must not know concrete classes at all
✓ There is a real chance of adding new families in the future
✓ System testing needs the whole family mocked at once
AVOID Abstract Factory if:
✗ There is only one implementation and no plan to add more
✗ Products in the "family" do not depend on each other
✗ The abstraction complexity outweighs the benefit
✗ Small team with a tight timeline — start simple, refactor when needed
✗ Every factory method has only one concrete implementation
Start Simple, Refactor When Needed
If you are not sure you need Abstract Factory, start with a Simple Factory or even plain constructors. When you start seeing the pattern of “a set of objects that must be swapped together” — that is the right time to refactor to Abstract Factory. Premature abstraction is more dangerous than refactoring later.
Abstract Factory Review Checklist #
DESIGN:
□ There is an abstract factory interface with more than one Create method
□ All products in one family are compatible with each other
□ The client depends only on the abstract factory and abstract products
□ There is no way to mix products from different families
IMPLEMENTATION:
□ Each concrete factory creates its entire product family
□ Configuration is injected into the factory, not into concrete products
□ The factory contains no business logic — only object creation
□ Errors from the creation process are handled clearly
CONSISTENCY:
□ Every concrete factory implements all the methods in the interface
□ Products from one factory work together without extra configuration
□ Adding a new family does not change client code at all
TESTING:
□ There is a mock factory that returns mock products
□ The test suite runs without external dependencies
□ Edge cases (factory failure, products returning errors) are tested
□ The audit trail (logger) is tested even for failure scenarios
Summary #
- Abstract Factory manages object families — unlike Factory Method, which manages a single object, here one factory is responsible for many products that must be compatible.
- Consistency is the main advantage — there is no way to accidentally mix products from different families; an inconsistency like EmailSender with SMSFormatter becomes structurally impossible.
- Five components: Abstract Factory, Concrete Factory, Abstract Product, Concrete Product, and Client — each with a clear, non-overlapping role.
- The client is fully isolated from concrete classes; swapping an entire implementation family is just a matter of swapping the injected factory — no other code changes.
- Testing becomes very clean — a Mock Factory replaces the entire product family at once; no SMTP, no HTTP calls, no database.
- Distinguish it from Factory Method: use Factory Method for one product with implementation variations; use Abstract Factory for several products that must stay consistent within one family.
- Don’t over-engineer — start with plain constructors or Factory Method, and refactor to Abstract Factory only when a real need to swap entire object families appears.
- A large factory is a danger signal — more than 5-7
Create...()methods means the factory is trying to do too much; split it into several more focused factories.