Skip to content

Instantly share code, notes, and snippets.

@sachinsmc
Last active March 27, 2023 19:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sachinsmc/1c11e449b03f61c5e404178373888757 to your computer and use it in GitHub Desktop.
Save sachinsmc/1c11e449b03f61c5e404178373888757 to your computer and use it in GitHub Desktop.
SOLID Principles in Golang
package main
import "fmt"
// Single Responsibility Principle:
// Each struct has only a single responsibility.
// User struct represents a user in the system.
type User struct {
ID int
FirstName string
LastName string
}
// UserService struct defines a service for managing users.
type UserService struct {
users []User
}
// AddUser adds a new user to the service.
func (s *UserService) AddUser(u User) {
s.users = append(s.users, u)
}
// GetUserByID returns the user with the given ID.
func (s *UserService) GetUserByID(id int) User {
for _, u := range s.users {
if u.ID == id {
return u
}
}
return User{}
}
// Open/Closed Principle:
// The UserService is open for extension, but closed for modification.
// We can add new functionality by implementing new interfaces,
// rather than modifying the existing UserService.
// UserRepository defines the interface for a user repository.
type UserRepository interface {
SaveUser(u User) error
FindUserByID(id int) (User, error)
}
// UserRepositoryImpl is a concrete implementation of the UserRepository interface.
// It uses the UserService to store and retrieve users.
type UserRepositoryImpl struct {
userService *UserService
}
// SaveUser saves a user to the repository.
func (r *UserRepositoryImpl) SaveUser(u User) error {
r.userService.AddUser(u)
return nil
}
// FindUserByID finds a user with the given ID in the repository.
func (r *UserRepositoryImpl) FindUserByID(id int) (User, error) {
return r.userService.GetUserByID(id), nil
}
// Liskov Substitution Principle:
// The UserRepositoryImpl should be substitutable for the UserRepository interface.
// This means that we should be able to use either the interface or the concrete implementation
// without knowing which one we are using.
// UserController is a controller for managing users.
// It uses a UserRepository to store and retrieve users.
type UserController struct {
repository UserRepository
}
// NewUserController creates a new UserController.
func NewUserController(r UserRepository) *UserController {
return &UserController{repository: r}
}
// CreateUser creates a new user.
func (c *UserController) CreateUser(u User) error {
return c.repository.SaveUser(u)
}
// GetUserByID gets the user with the given ID.
func (c *UserController) GetUserByID(id int) (User, error) {
return c.repository.FindUserByID(id)
}
func main() {
// Dependency Inversion Principle:
// The UserController depends on the UserRepository interface,
// rather than on the concrete UserRepositoryImpl.
// This allows us to use any implementation of the UserRepository interface with the UserController.
userService := &UserService{}
repository := &UserRepositoryImpl{userService: userService}
controller := NewUserController(repository)
user := User{ID: 1, FirstName: "John", LastName: "Doe"}
controller.CreateUser(user)
retrievedUser, _ := controller.GetUserByID(1)
fmt.Println(retrievedUser)
}
@sachinsmc
Copy link
Author

SOLID Principles in Golang

The SOLID principles are a set of guidelines for writing object-oriented software. The acronym SOLID stands for:

  1. Single Responsibility Principle (SRP): A class should have only one reason to change.
  2. Open/Closed Principle (OCP): A class should be open for extension but closed for modification.
  3. Liskov Substitution Principle (LSP): Subtypes must be substitutable for their base types.
  4. Interface Segregation Principle (ISP): Clients should not be forced to depend on interfaces they do not use.
  5. Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules. Both should depend on abstractions.

Single Responsibility Principle (SRP)

The SRP states that a class should have only one reason to change. In Go, this principle can be applied by separating concerns into separate packages or functions. Each function or package should be responsible for a single task or functionality. This allows for easier testing and maintenance of the codebase. For example, a web application might have a separate package for handling user authentication, another for handling database access, and another for rendering HTML templates.

Open/Closed Principle (OCP)

The OCP states that a class should be open for extension but closed for modification. In Go, this can be achieved through the use of interfaces and composition. Instead of modifying existing code to add new functionality, new behavior can be added by composing new objects that implement the desired interface. This allows for more flexibility and maintainability in the codebase. For example, a web server might have a pluggable middleware architecture that allows for adding new features without modifying the core server code.

Liskov Substitution Principle (LSP)

The LSP states that subtypes must be substitutable for their base types. In Go, this can be achieved by designing interfaces that are agnostic to the implementation details of the implementing types. This allows for different types to be used interchangeably, which is particularly useful for testing and refactoring. For example, an interface for a database driver might specify a set of methods for querying and updating data, which can be implemented by different types of databases such as MySQL, PostgreSQL, or MongoDB.

Interface Segregation Principle (ISP)

The ISP states that clients should not be forced to depend on interfaces they do not use. In Go, this can be achieved by defining smaller, more focused interfaces that are tailored to specific use cases. This allows clients to only depend on the functionality they need, which reduces coupling and improves maintainability. For example, an interface for a logger might only specify a single method for logging messages, rather than a more general-purpose interface that includes methods for configuring the logger or retrieving log data.

Dependency Inversion Principle (DIP)

The DIP states that high-level modules should not depend on low-level modules. Both should depend on abstractions. In Go, this can be achieved by using dependency injection and inversion of control. Instead of creating and managing dependencies directly in code, dependencies can be injected into objects or functions through interfaces. This allows for more flexible and testable code, as dependencies can be easily swapped out or mocked for testing purposes. For example, a web application might inject a database driver implementation into a user authentication package, rather than having the authentication package directly create and manage the database connection.

In conclusion, the SOLID principles are a set of guidelines that can greatly improve the maintainability and flexibility of Go code. By following these principles, developers can create code that is more modular, testable, and extensible, which is particularly important for large-scale, distributed systems. While there is no silver bullet for writing good software, the SOLID principles provide a solid foundation for building high-quality software in Go and other programming languages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment