Singleton

initialize once use always.

code:

import (
    "log"
    "os"
    "sync"
)

type Logger struct {
    // type declarations
    logger *log.Logger // reference the existing logger
}

var (
    // variable declarations
    instance *Logger // create a variable of type *Logger
    once     sync.Once // sync.Once is a type from the "sync" package it's job is to check if the code has already run.
)

func GetInstance() *Logger {
    once.Do(func() {  // if never run then run; otherwise don't run.
        instance = &Logger{
            logger: log.New(os.Stdout, "[LOG] ", log.LstdFlags),
        }
    })

    return instance // for the fist time it will be initialized by the code above then rest of the times; it will just use the global variable "instance"'s value that is why that is declared outside the function.
    // for the same reason once is also decleared outside the function so that it's value is not limited to the function life cycle.
}

func (l *Logger) Info(message string) {
    l.logger.Println(message)
}

Usage

log1 := logger.GetInstance()
log2 := logger.GetInstance()
log1.Info("Application started")

println(log1 == log2) // true

Factory

Have a unified way to create a bunch of alike services e.g sendEmailNotification, sendSmsNotification, sendNotification.

  • Create a factory that returns object of the userfull type and the method of that type always do what they are intented to do without any jumbled logic.
  • Each service lives saperate and implements a unified interface.
                      Software Services
                           │
          ┌────────────────┴────────────────┐
          │                                 │
     Notification                     Storage
          │                                 │
   ┌──────┴──────┐                 ┌────────┴────────┐
   │             │                 │                 │
 Email        SMS              S3 Storage      Azure Blob

A notification factory only deals with the notification branch it knows nothing of the Storage branch

code:

package main

import "fmt"

// NotificationService is the interface both notification types satisfy implicitly.
type NotificationService interface {
	Send(message string) error
}

// EmailNotification implements NotificationService by having a Send method.
type EmailNotification struct{}

func (e EmailNotification) Send(message string) error {
	fmt.Println("Sending email:", message)
	// ... send email
	return nil
}

// SmsNotification also implements NotificationService.
type SmsNotification struct{}

func (s SmsNotification) Send(message string) error {
	fmt.Println("Sending SMS:", message)
	// ... send SMS
	return nil
}

// NewNotification is the factory. Go has no static methods, so this is a
// package-level function instead of a static method on a factory type.
func NewNotification(kind string) (NotificationService, error) {
	switch kind {
	case "email":
		return EmailNotification{}, nil
	case "sms":
		return SmsNotification{}, nil
	default:
		return nil, fmt.Errorf("unknown notification type: %q", kind)
	}
}

func main() {
	svc, err := NewNotification("email")
	if err != nil {
		panic(err)
	}
	svc.Send("Hello, world!")
}
===================
A real-world analogy is a car manufacturer:

A Factory is like saying:
"Build me an engine."

An Abstract Factory is like saying:
"Build me all the parts for a Toyota."
==================

Abstract Factory

Have a unified way to create a families of realated and dependent objects without specifying their concrete classes.

AWS Factory
   ├── AWS Notification
   ├── AWS Storage
   └── AWS Logger

Azure Factory
   ├── Azure Notification
   ├── Azure Storage
   └── Azure Logger
package main

import "fmt"

// ---- Products: the family of related interfaces ----

type Notification interface {
	Notify(to, message string) error
}

type Storage interface {
	Save(key string, data []byte) error
}

type Logger interface {
	Log(level, message string)
}

// ---- Abstract Factory: produces the whole family ----

type CloudFactory interface {
	NewNotification() Notification
	NewStorage() Storage
	NewLogger() Logger
}

// ---- Concrete family #1: AWS ----

type awsNotification struct{}
func (awsNotification) Notify(to, message string) error {
	fmt.Printf("[AWS SNS] notify %s: %s\n", to, message)
	return nil
}

type awsStorage struct{}
func (awsStorage) Save(key string, data []byte) error {
	fmt.Printf("[AWS S3] save %q (%d bytes)\n", key, len(data))
	return nil
}

type awsLogger struct{}
func (awsLogger) Log(level, message string) {
	fmt.Printf("[AWS CloudWatch] %s: %s\n", level, message)
}

type AWSFactory struct{}
func (AWSFactory) NewNotification() Notification { return awsNotification{} }
func (AWSFactory) NewStorage() Storage           { return awsStorage{} }
func (AWSFactory) NewLogger() Logger             { return awsLogger{} }

// ---- Concrete family #2: Azure ----

type azureNotification struct{}
func (azureNotification) Notify(to, message string) error {
	fmt.Printf("[Azure Notification Hubs] notify %s: %s\n", to, message)
	return nil
}

type azureStorage struct{}
func (azureStorage) Save(key string, data []byte) error {
	fmt.Printf("[Azure Blob] save %q (%d bytes)\n", key, len(data))
	return nil
}

type azureLogger struct{}
func (azureLogger) Log(level, message string) {
	fmt.Printf("[Azure Monitor] %s: %s\n", level, message)
}

type AzureFactory struct{}
func (AzureFactory) NewNotification() Notification { return azureNotification{} }
func (AzureFactory) NewStorage() Storage           { return azureStorage{} }
func (AzureFactory) NewLogger() Logger             { return azureLogger{} }

// ---- Factory-of-factories: pick the provider ----

func NewCloudFactory(provider string) (CloudFactory, error) {
	switch provider {
	case "aws":
		return AWSFactory{}, nil
	case "azure":
		return AzureFactory{}, nil
	default:
		return nil, fmt.Errorf("unknown provider: %q", provider)
	}
}

// ---- Client code depends only on the abstractions ----

func runApp(f CloudFactory) {
	logger := f.NewLogger()
	storage := f.NewStorage()
	notifier := f.NewNotification()

	logger.Log("INFO", "app starting")
	storage.Save("report.pdf", []byte("...file contents..."))
	notifier.Notify("user@example.com", "your report is ready")
	logger.Log("INFO", "app done")
}

func main() {
	factory, err := NewCloudFactory("aws")
	if err != nil {
		panic(err)
	}
	runApp(factory)
	// Change "aws" to "azure" and the entire stack switches providers,
	// with no change to runApp.
}