# Factory Pattern in Go ## Pattern Overview The Factory Pattern provides an interface for creating objects without specifying the exact class of object that will be created. In Go, this is typically implemented through constructor functions that encapsulate complex object creation logic. ## Pattern Structure ```go // Factory function func NewObject(config Config) (*Object, error) { // Complex initialization logic obj := &Object{ field1: config.Value1, field2: config.Value2, } // Validation if err := obj.validate(); err != nil { return nil, err } // Setup if err := obj.initialize(); err != nil { return nil, err } return obj, nil } ``` ## Real Implementation: Error Factories ### Domain Error Constructors ```go // internal/handlers/errors.go // NewDomainError is the base error factory func NewDomainError(code ErrorCode, message string, statusCode int) *DomainError { return &DomainError{ Code: code, Message: message, StatusCode: statusCode, } } // Specific error factories func InvalidLanguageError(lang string) *DomainError { return NewDomainError( ErrCodeInvalidLanguage, fmt.Sprintf("Unsupported language: %s (use 'en' or 'es')", lang), http.StatusBadRequest, ).WithField("lang") } func InvalidLengthError(length string) *DomainError { return NewDomainError( ErrCodeInvalidLength, fmt.Sprintf("Invalid CV length: %s (use 'short' or 'long')", length), http.StatusBadRequest, ).WithField("length") } func PDFGenerationError(err error) *DomainError { return NewDomainError( ErrCodePDFGeneration, "Failed to generate PDF. Please try again.", http.StatusInternalServerError, ).WithErr(err) } func DataNotFoundError(dataType, lang string) *DomainError { return NewDomainError( ErrCodeDataNotFound, fmt.Sprintf("%s data not found for language: %s", dataType, lang), http.StatusInternalServerError, ) } ``` ### Response Factories ```go // internal/handlers/types.go // NewAPIResponse creates a success response func NewAPIResponse(data interface{}) *APIResponse { return &APIResponse{ Success: true, Data: data, Meta: &MetaInfo{ Timestamp: time.Now(), }, } } // NewErrorResponse creates an error response func NewErrorResponse(code, message string) *APIResponse { return &APIResponse{ Success: false, Error: &ErrorInfo{ Code: code, Message: message, }, Meta: &MetaInfo{ Timestamp: time.Now(), }, } } // NewPDFExportRequest creates a validated PDF export request func NewPDFExportRequest() *PDFExportRequest { return &PDFExportRequest{ Lang: "en", Length: "short", Icons: "show", Version: "with_skills", } } ``` ## Handler Factories ### CVHandler Factory ```go // internal/handlers/cv.go // NewCVHandler creates a new CV handler with all dependencies func NewCVHandler(tmpl *templates.Manager, host string) *CVHandler { return &CVHandler{ tmpl: tmpl, host: host, } } // With validation func NewCVHandlerWithValidation( tmpl *templates.Manager, host string, ) (*CVHandler, error) { if tmpl == nil { return nil, errors.New("template manager is required") } if host == "" { return nil, errors.New("host is required") } return &CVHandler{ tmpl: tmpl, host: host, }, nil } ``` ### Template Manager Factory ```go // internal/templates/manager.go // NewManager creates and initializes a template manager func NewManager(config *config.TemplateConfig) (*Manager, error) { // Validate config if config == nil { return nil, errors.New("config is required") } if config.Dir == "" { return nil, errors.New("template directory is required") } // Create manager m := &Manager{ templates: make(map[string]*template.Template), config: config, } // Load templates if err := m.loadTemplates(); err != nil { return nil, fmt.Errorf("load templates: %w", err) } log.Printf("Template manager initialized with %d templates", len(m.templates)) return m, nil } ``` ## Factory with Options Pattern ### Functional Options ```go // Option function type type HandlerOption func(*Handler) // Option constructors func WithTimeout(d time.Duration) HandlerOption { return func(h *Handler) { h.timeout = d } } func WithMaxRetries(n int) HandlerOption { return func(h *Handler) { h.maxRetries = n } } func WithLogger(logger Logger) HandlerOption { return func(h *Handler) { h.logger = logger } } // Factory with options func NewHandler(tmpl *templates.Manager, opts ...HandlerOption) *Handler { h := &Handler{ tmpl: tmpl, timeout: 30 * time.Second, // Defaults maxRetries: 3, logger: &DefaultLogger{}, } // Apply options for _, opt := range opts { opt(h) } return h } // Usage handler := NewHandler( tmplManager, WithTimeout(10*time.Second), WithMaxRetries(5), WithLogger(customLogger), ) ``` ### Options Struct ```go // Options struct approach type HandlerOptions struct { Timeout time.Duration MaxRetries int Logger Logger } // DefaultOptions provides sensible defaults func DefaultOptions() *HandlerOptions { return &HandlerOptions{ Timeout: 30 * time.Second, MaxRetries: 3, Logger: &DefaultLogger{}, } } // Factory with options func NewHandler(tmpl *templates.Manager, opts *HandlerOptions) *Handler { if opts == nil { opts = DefaultOptions() } return &Handler{ tmpl: tmpl, timeout: opts.Timeout, maxRetries: opts.MaxRetries, logger: opts.Logger, } } // Usage handler := NewHandler(tmplManager, &HandlerOptions{ Timeout: 10 * time.Second, MaxRetries: 5, }) ``` ## Abstract Factory Pattern ### Database Factory ```go // Database interface type Database interface { Query(query string) (Result, error) Close() error } // Concrete implementations type PostgresDB struct { conn *sql.DB } type MySQLDB struct { conn *sql.DB } type SQLiteDB struct { conn *sql.DB } // Factory function func NewDatabase(dbType, connString string) (Database, error) { switch dbType { case "postgres": conn, err := sql.Open("postgres", connString) if err != nil { return nil, err } return &PostgresDB{conn: conn}, nil case "mysql": conn, err := sql.Open("mysql", connString) if err != nil { return nil, err } return &MySQLDB{conn: conn}, nil case "sqlite": conn, err := sql.Open("sqlite3", connString) if err != nil { return nil, err } return &SQLiteDB{conn: conn}, nil default: return nil, fmt.Errorf("unsupported database type: %s", dbType) } } // Usage db, err := NewDatabase("postgres", "connection-string") ``` ## Factory with Builder Pattern ### Request Builder ```go // Builder pattern for complex object construction type RequestBuilder struct { req *http.Request err error } // NewRequestBuilder creates a new request builder func NewRequestBuilder(method, url string) *RequestBuilder { req, err := http.NewRequest(method, url, nil) return &RequestBuilder{ req: req, err: err, } } // Builder methods func (b *RequestBuilder) WithHeader(key, value string) *RequestBuilder { if b.err != nil { return b } b.req.Header.Set(key, value) return b } func (b *RequestBuilder) WithBody(body io.Reader) *RequestBuilder { if b.err != nil { return b } b.req.Body = io.NopCloser(body) return b } func (b *RequestBuilder) WithContext(ctx context.Context) *RequestBuilder { if b.err != nil { return b } b.req = b.req.WithContext(ctx) return b } // Build finalizes and returns the request func (b *RequestBuilder) Build() (*http.Request, error) { return b.req, b.err } // Usage req, err := NewRequestBuilder("POST", "https://api.example.com"). WithHeader("Content-Type", "application/json"). WithBody(bytes.NewBuffer(data)). WithContext(ctx). Build() ``` ## Factory Method Pattern ### Data Loader Factory ```go // Loader interface type DataLoader interface { Load(lang string) (interface{}, error) } // Concrete loaders type CVLoader struct{} func (l *CVLoader) Load(lang string) (interface{}, error) { return cvmodel.LoadCV(lang) } type UILoader struct{} func (l *UILoader) Load(lang string) (interface{}, error) { return uimodel.LoadUI(lang) } // Factory method func NewLoader(loaderType string) (DataLoader, error) { switch loaderType { case "cv": return &CVLoader{}, nil case "ui": return &UILoader{}, nil default: return nil, fmt.Errorf("unknown loader type: %s", loaderType) } } // Usage loader, err := NewLoader("cv") if err != nil { return err } data, err := loader.Load("en") ``` ## Factory Registry Pattern ### Handler Registry ```go // Handler factory registry type HandlerFactory func() http.Handler var handlerRegistry = make(map[string]HandlerFactory) // Register handler factory func RegisterHandler(name string, factory HandlerFactory) { handlerRegistry[name] = factory } // Get handler from registry func GetHandler(name string) (http.Handler, error) { factory, ok := handlerRegistry[name] if !ok { return nil, fmt.Errorf("handler not found: %s", name) } return factory(), nil } // Register handlers at init func init() { RegisterHandler("home", func() http.Handler { return http.HandlerFunc(handleHome) }) RegisterHandler("about", func() http.Handler { return http.HandlerFunc(handleAbout) }) } // Usage handler, err := GetHandler("home") ``` ## Real-World Factory Examples ### 1. HTTP Client Factory ```go // NewHTTPClient creates configured HTTP client func NewHTTPClient(timeout time.Duration, maxRetries int) *http.Client { return &http.Client{ Timeout: timeout, Transport: &http.Transport{ MaxIdleConns: 100, MaxIdleConnsPerHost: 10, IdleConnTimeout: 90 * time.Second, }, } } // With retry logic func NewRetryableHTTPClient(timeout time.Duration, maxRetries int) *http.Client { client := NewHTTPClient(timeout, maxRetries) // Wrap with retry logic return client } ``` ### 2. Logger Factory ```go // Logger factory with different outputs func NewLogger(output string) (*log.Logger, error) { switch output { case "stdout": return log.New(os.Stdout, "[APP] ", log.LstdFlags), nil case "stderr": return log.New(os.Stderr, "[APP] ", log.LstdFlags), nil case "file": f, err := os.OpenFile("app.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) if err != nil { return nil, err } return log.New(f, "[APP] ", log.LstdFlags), nil default: return nil, fmt.Errorf("unknown output: %s", output) } } ``` ### 3. Middleware Factory ```go // Middleware factory func NewAuthMiddleware(tokenValidator TokenValidator) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("Authorization") if err := tokenValidator.Validate(token); err != nil { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } next.ServeHTTP(w, r) }) } } // Usage authMiddleware := NewAuthMiddleware(&JWTValidator{}) handler := authMiddleware(myHandler) ``` ## Benefits 1. **Encapsulation**: Complex creation logic is hidden 2. **Consistency**: All objects created the same way 3. **Flexibility**: Easy to change implementation 4. **Testability**: Easy to create test objects 5. **Validation**: Centralized validation in factory ## Best Practices ### ✅ DO ```go // Validate inputs in factory func NewHandler(config *Config) (*Handler, error) { if config == nil { return nil, errors.New("config is required") } return &Handler{config: config}, nil } // Return errors for creation failures func NewDatabase(connString string) (*Database, error) { db, err := sql.Open("postgres", connString) if err != nil { return nil, fmt.Errorf("open database: %w", err) } return &Database{db: db}, nil } // Provide sensible defaults func NewHandler(opts *Options) *Handler { if opts == nil { opts = DefaultOptions() } return &Handler{opts: opts} } // Use descriptive factory names func NewRetryableHTTPClient(...) *http.Client func NewCachedDatabase(...) *Database func NewBufferedWriter(...) *Writer ``` ### ❌ DON'T ```go // DON'T return panics from factories func NewHandler() *Handler { config := loadConfig() if config == nil { panic("no config") // Wrong! Return error } return &Handler{config: config} } // DON'T ignore errors func NewHandler() *Handler { db, _ := connectDB() // Wrong! Handle error return &Handler{db: db} } // DON'T make factories too complex func NewHandler(...20 parameters...) *Handler { // Too many parameters! Use options pattern } ``` ## Testing Factories ```go func TestNewHandler(t *testing.T) { t.Run("Valid config", func(t *testing.T) { config := &Config{Timeout: 10} handler, err := NewHandler(config) if err != nil { t.Errorf("unexpected error: %v", err) } if handler == nil { t.Error("expected handler, got nil") } }) t.Run("Nil config", func(t *testing.T) { handler, err := NewHandler(nil) if err == nil { t.Error("expected error for nil config") } if handler != nil { t.Error("expected nil handler") } }) } ``` ## Related Patterns - **Builder Pattern**: For complex, multi-step object creation - **Singleton Pattern**: Factories can create singletons - **Dependency Injection**: Factories inject dependencies ## Further Reading - [Factory Pattern](https://refactoring.guru/design-patterns/factory-method) - [Functional Options](https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis) - [Go Constructor Patterns](https://www.sohamkamani.com/golang/options-pattern/)