660 lines
14 KiB
Markdown
660 lines
14 KiB
Markdown
|
|
# 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/)
|