“A class/function/type should have one, and only one reason to change”
Open-Closed principle
“A class should be open for extension but closed for modifications”
Liskov substitution principle
“Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program”
Interface segregation principle
“Clients should not be forced to depend on methods they don't use”
Dependency inversion
“High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should not depend on abstractions”
Single responsibility principle
Before - breaks SRP because the function area will have to be changed for two reasons, the formula of area changes, and second the output of the program changes
After - only have to change the function area when the formula changes and the function print when the printing format changes.
packagemainimport ("fmt""math")typecirclestruct { radius float64}func (c circle) area() float64 {return math.Pi * c.radius * c.radius}func (c circle) print() { fmt.Printf("The are of circle is %f \n", c.area())}typesquarestruct { length float64}func (c square) print() { fmt.Printf("The are of square is %f \n", c.area())}func (c square) area() float64 {return c.length * c.length}typeshapeinterface {name() stringarea() float64}funcmain() { c :=circle{ radius: 3, } c.print() s :=square{ length: 3, } s.print()}
Open-Closed principle
In go, this can be achieved by using interfaces and polymorphism => all structs are open for extension but not for modification
packagemainimport"fmt"// This is an interface that defines a shape.typeShapeinterface {Area() float64}// This is a struct that implements the Shape interface.typeRectanglestruct { width float64 height float64}// This method calculates the area of a rectangle.func (r Rectangle) Area() float64 {return r.width * r.height}// This is a struct that implements the Shape interface.typeCirclestruct { radius float64}// This method calculates the area of a circle.func (c Circle) Area() float64 {return3.1415926535* c.radius * c.radius}// This function takes a Shape as an argument and calculates its area.// polymorphismfunccalculateArea(s Shape) float64 {return s.Area()}funcmain() { rect :=Rectangle{width: 10, height: 5} fmt.Println("Area of rectangle:", calculateArea(rect)) circle :=Circle{radius: 5} fmt.Println("Area of circle:", calculateArea(circle))}
Liskov substitution principle
A derived class should be able to substitute its base class without affecting the functionality of the program. e.g. calculateArea can take any subtype of Shape - Square/ Rectangle and its behvaiour won't change.
packagemainimport"fmt"// This is an interface that defines a shape.typeShapeinterface {Area() float64}// This is a struct that implements the Shape interface.typeRectanglestruct { width float64 height float64}// This method calculates the area of a rectangle.func (r Rectangle) Area() float64 {return r.width * r.height}// This is a struct that implements the Shape interface.typeSquarestruct { side float64}// This method calculates the area of a square.func (s Square) Area() float64 {return s.side * s.side}// This function takes a Shape as an argument and calculates its area.funccalculateArea(s Shape) float64 {return s.Area()}funcmain() { rect :=Rectangle{width: 10, height: 5} fmt.Println("Area of rectangle:", calculateArea(rect)) square :=Square{side: 5} fmt.Println("Area of square:", calculateArea(square))}
Interface segregation principle
In Golang, this can be achieved by creating smaller, focused interfaces that provide only the functionality needed by a specific client.
packagemainimport"fmt"// This is an interface that defines basic operations for a shape.typeBasicShapeinterface {Area() float64Perimeter() float64}// This is an interface that defines advanced operations for a shape.typeAdvancedShapeinterface {Volume() float64}// This is a struct that implements the BasicShape interface.typeRectanglestruct { width float64 height float64}// This method calculates the area of a rectangle.func (r Rectangle) Area() float64 {return r.width * r.height}// This method calculates the perimeter of a rectangle.func (r Rectangle) Perimeter() float64 {return2* (r.width + r.height)}// This is a struct that implements the BasicShape and AdvancedShape interfaces.typeCubestruct { side float64}// This method calculates the area of a cube.func (c Cube) Area() float64 {return6* c.side * c.side}// This method calculates the perimeter of a cube.func (c Cube) Perimeter() float64 {return12* c.side}// This method calculates the volume of a cube.func (c Cube) Volume() float64 {return c.side * c.side * c.side}// This function takes a BasicShape as an argument and calculates its area.funccalculateArea(s BasicShape) float64 {return s.Area()}// This function takes an AdvancedShape as an argument and calculates its volume.funccalculateVolume(s AdvancedShape) float64 {return s.Volume()}funcmain() { rect :=Rectangle{width: 10, height: 5} fmt.Println("Area of rectangle:", calculateArea(rect)) cube :=Cube{side: 5} fmt.Println("Area of cube:", calculateArea(cube)) fmt.Println("Volume of cube:", calculateVolume(cube))}
Dependency Inversion Principle
In this example, the Service struct depends on the Database interface, and the MySQL and PostgreSQL structs implement the Database interface. This means that the Service struct can store data in either a MySQL or PostgreSQL database, without having to know or care which database it’s using.
This is an example of the Dependency Inversion Principle in action. The high-level Service module depends on an abstraction (the Database interface), while both the low-level MySQL and PostgreSQL modules depend on the same abstraction. This allows the Service module to be decoupled from the specific implementation of the database, making it more flexible and maintainable.
packagemainimport"fmt"// This is an interface that defines basic operations for a database.typeDatabaseinterface {Connect()Store(data string)}// This is a struct that implements the Database interface.typeMySQLstruct {}// This method connects to a MySQL database.func (m MySQL) Connect() { fmt.Println("Connecting to MySQL database...")}// This method stores data in a MySQL database.func (m MySQL) Store(data string) { fmt.Println("Storing data in MySQL database:", data)}// This is a struct that implements the Database interface.typePostgreSQLstruct {}// This method connects to a PostgreSQL database.func (p PostgreSQL) Connect() { fmt.Println("Connecting to PostgreSQL database...")}// This method stores data in a PostgreSQL database.func (p PostgreSQL) Store(data string) { fmt.Println("Storing data in PostgreSQL database:", data)}// This is a struct that depends on a Database interface.typeServicestruct { db Database}// This method sets the Database for the Service.func (s *Service) SetDatabase(db Database) { s.db = db}// This method stores data in a database using the Database interface.func (s *Service) StoreData(data string) { s.db.Connect() s.db.Store(data)}funcmain() {// This creates a Service that uses a MySQL database. mysqlService :=Service{db: MySQL{}} mysqlService.StoreData("Hello, world!")// This creates a Service that uses a PostgreSQL database. postgresService :=Service{db: PostgreSQL{}} postgresService.StoreData("Hello, world!")}