Cada módulo o clase debe tener una única responsabilidad o motivo para cambiar. En Go, esto se traduce en mantener las funciones y métodos enfocados en una tarea específica.
Ejemplo:
package main
import "fmt"
// Cumple con SRP, ya que solo maneja la información del empleado
type Employee struct {
Name string
Salary float64
}
// Cumple con SRP, ya que solo maneja el cálculo del salario
type SalaryCalculator struct{}
func (sc SalaryCalculator) CalculateSalary(e Employee) float64 {
return e.Salary
}
func main() {
e := Employee{Name: "John Doe", Salary: 50000}
sc := SalaryCalculator{}
fmt.Println(sc.CalculateSalary(e))
}
Las entidades de software deben estar abiertas para la extensión, pero cerradas para la modificación. Esto significa que debes poder añadir nuevas funcionalidades sin cambiar el código existente.
Ejemplo:
package main
import "fmt"
// Cumple con OCP, ya que podemos añadir nuevas formas de pago sin modificar el código existente
type Employee struct {
Name string
Salary float64
}
type PaymentMethod interface {
Pay(e Employee) string
}
type BankTransfer struct{}
func (bt BankTransfer) Pay(e Employee) string {
return fmt.Sprintf("Paid %f to %s via Bank Transfer", e.Salary, e.Name)
}
type PayPal struct{}
func (pp PayPal) Pay(e Employee) string {
return fmt.Sprintf("Paid %f to %s via PayPal", e.Salary, e.Name)
}
func main() {
e := Employee{Name: "John Doe", Salary: 50000}
var pm PaymentMethod
pm = BankTransfer{}
fmt.Println(pm.Pay(e))
pm = PayPal{}
fmt.Println(pm.Pay(e))
}
Los objetos de una clase base deben poder ser sustituidos por objetos de una clase derivada sin afectar el funcionamiento del programa.
Ejemplo:
package main
import "fmt"
// Cumple con LSP, ya que ambos tipos de empleados pueden ser usados de manera intercambiable
type Employee interface {
GetDetails() string
}
type FullTimeEmployee struct {
Name string
Salary float64
}
func (fte FullTimeEmployee) GetDetails() string {
return fmt.Sprintf("Full-time: %s with salary %f", fte.Name, fte.Salary)
}
type PartTimeEmployee struct {
Name string
HourlyRate float64
}
func (pte PartTimeEmployee) GetDetails() string {
return fmt.Sprintf("Part-time: %s with hourly rate %f", pte.Name, pte.HourlyRate)
}
func PrintEmployeeDetails(e Employee) {
fmt.Println(e.GetDetails())
}
func main() {
fte := FullTimeEmployee{Name: "John Doe", Salary: 50000}
pte := PartTimeEmployee{Name: "Jane Doe", HourlyRate: 30}
PrintEmployeeDetails(fte)
PrintEmployeeDetails(pte)
}
Los clientes no deben verse obligados a depender de interfaces que no utilizan. En Go, esto significa definir interfaces pequeñas y específicas.
Ejemplo:
package main
import "fmt"
// Cumple con ISP, ya que las interfaces son específicas y pequeñas
type Printer interface {
Print()
}
type Scanner interface {
Scan()
}
type MultiFunctionDevice interface {
Printer
Scanner
}
type MultiFunctionPrinter struct{}
func (mfp MultiFunctionPrinter) Print() {
fmt.Println("Printing...")
}
func (mfp MultiFunctionPrinter) Scan() {
fmt.Println("Scanning...")
}
func main() {
var mfd MultiFunctionDevice = MultiFunctionPrinter{}
mfd.Print()
mfd.Scan()
}
Los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben depender de abstracciones. En Go, esto se logra mediante el uso de interfaces.
Ejemplo:
package main
import "fmt"
// Cumple con DIP, ya que el módulo de alto nivel depende de una abstracción (interfaces)
type Worker interface {
Work()
}
type Developer struct{}
func (d Developer) Work() {
fmt.Println("Writing code...")
}
type Tester struct{}
func (t Tester) Work() {
fmt.Println("Testing code...")
}
type Project struct {
Workers []Worker
}
func (p Project) Start() {
for _, worker := range p.Workers {
worker.Work()
}
}
func main() {
dev := Developer{}
tester := Tester{}
project := Project{
Workers: []Worker{dev, tester},
}
project.Start()
}
-
Principios SOLID en Go: https://daniel-m-spiridione.gitbook.io/designpatternsingo/parte1/poo/solid
-
SOLID Go Design: https://dave.cheney.net/2016/08/20/solid-go-design