The singleton pattern ensures that a class has only one instance and provides a global point of access to it.

Components

SOLID Principle

✅ Single Responsibility Principle (SRP)

✅ Open/Closed Principle (OCP): The Abstract Product is open to extend.

✅ Liskov Substitution Principle (LSP) / Interface Segregation Principle (ISP): ⚠️ If Concrete Objects implement Abstract Object correctly.

✅ Dependency Inversion Principle (DIP): The object doesn’t rely on another concrete objects.

Implementation

Inheritance

abstract class Logger {
  abstract log(message: string): void;
  
  // Reusable Shared Logic
  protected formatMessage(message: string): string {
    const timestamp = new Date().toISOString();
    return `[${timestamp}] ${message}`;
  }
}

class ConsoleLogger extends Logger {
  private static instance: ConsoleLogger;

  private constructor() {
    super();
  }

  static getInstance(): ConsoleLogger {
    if (!ConsoleLogger.instance) {
      ConsoleLogger.instance = new ConsoleLogger();
    }
    return ConsoleLogger.instance;
  }

  public log(message: string): void {
    const formatted = this.formatMessage(message);
    console.log(formatted);
  }
}

// Client
const logger = ConsoleLogger.getInstance();
logger.log("Shared logic works!");

Interface Polymorphism

interface Logger {
  log(message: string): void;
  
  // Reusable Shared Logic
  formatMessage(message: string): string
}

class ConsoleLogger implements Logger {
  private static instance: ConsoleLogger;

  private constructor() {}

  static getInstance(): ConsoleLogger {
    if (!ConsoleLogger.instance) {
      ConsoleLogger.instance = new ConsoleLogger();
    }
    return ConsoleLogger.instance;
  }

  public log(message: string): void {
    const formatted = this.formatMessage(message);
    console.log(formatted);
  }
  
  private formatMessage(message: string): string {
    const timestamp = new Date().toISOString();
    return `[${timestamp}] ${message}`;
  }
}

// Client
const logger = ConsoleLogger.getInstance();
logger.log("Shared logic works!");

Go Interface Polymorphism

Go lacks encapsulation mechanisms (private, protected, public).

type Logger interface {
	Log(message string)
	
	// Reusable Shared Logic
	formatMessage(message string) string
}

type ConsoleLogger struct {}
func (c *ConsoleLogger) Log(message string) {
	formatted := c.formatMessage(message)
	fmt.Println(formatted)
}
func (c *ConsoleLogger) formatMessage(message string) string {
	timestamp := time.Now().Format(time.RFC3339)
	return fmt.Sprintf("[%s] %s", timestamp, message)
}

var (
	consoleLoggerInstance *ConsoleLogger
	once sync.Once
)

func GetConsoleLogger() *ConsoleLogger {
	once.Do(func() {
		consoleLoggerInstance = &ConsoleLogger{}
	})
	return consoleLoggerInstance
}

// Client
func main() {
	logger := GetConsoleLogger()
	logger.Log("Shared logic works!")
}

Add Reusable Shared Logics → + Go Composition

type Logger interface {
	Log(message string)
}

type LoggerBase struct{}
// Reusable Shared Logic
func (l LoggerBase) formatMessage(message string) string {
	timestamp := time.Now().Format(time.RFC3339)
	return fmt.Sprintf("[%s] %s", timestamp, message)
}

type ConsoleLogger struct {
	LoggerBase
}
func (c ConsoleLogger) Log(message string) {
	formatted := c.formatMessage(message)
	fmt.Println(formatted)
}

// ... same code

References