Object-Oriented Go: Unraveling the Power of OOP in Golang
Tags: go, oop, goprogramming, programming
Published: 2024-04-19 09:24:47

## Object-Oriented Programming
Object Oriented Programming is a paradigm in programming based on the concept of "objects". Objects contain data in the form of properties and methods. It is the most popular style used in programming due to its modularity, code reusability and organisability.
In general naming convention, basic concepts of OOPS include:
1. **Class**: A class is a blueprint of an object to be created.
2. **Object**: Instance of class containing both data and methods.
3. **Encapsulation**: Limiting access to properties, methods or variables in a class for code external to that class.
4. **Inheritance**: It allows a class (commonly referred to as subclass) to inherit properties and methods from another class (commonly referred to as parent class).
5. **Polymorphism**: Allowing objects of different classes to be treated as objects of a common superclass.
6. **Abstraction**: Abstracting a basic concept of multiple classes in a single abstract class. This simplifies logic and makes code more readable.

We won't dive into the details and understanding of OOPS in this article. This article focuses on the implementation of these concepts in GO programming language, since unlike normal programming languages like Java, C++, etc, GO doesn't have the concepts of OOPS directly with common naming conventions. For example, it does not have a `class` keyword.

## OOP in GO
Although GO does not have classes, it allows us to use OOP concepts using structs and interfaces. Let's see how we can use OOP concepts in GO with the examples:
### Classes, properties and methods
We can use structs in GO to achieve the same functionality as classes in other programming languages. Structs can have methods and properties.
Let's say we are building a billing application in which we want to define a company:
```go
type Company struct {
    Id string;
    Name string;
    Country string;
}
func newCompany(name string, country string) Company {
    return Company {
        Id: uuid.New().String(),
        Name: name,
        Country: country
    }
}
```
Go does not have the concept of constructors and classes, so we defined a custom function to return the Company. This would work as a constructor for the Company. We initialize Id in this function.
Now to create an object of type Company, we will call `newCompany` method like this:
```go
var company Company= newCompany("MyCompany","India")
```

> Note: Function overloading isn't supported in Go. So to create multiple implementations we would have to create multiple functions of different names.

Further, we want to save a company to the database. We can create a method of `Company` struct, for this purpose:
```go
func(company Company) saveToDatabase() {
    fmt.Println("Saving Company")
}
```

### Encapsulation
Since Go does not concept of classes, objects and access modifiers in it, we cannot implement encapsulation in it like other languages. It has the concept of packages and exported and unexported identifiers.
Let's say we folder structure in our app:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n8tccpdr3t6y66g6z0cr.png)
`Company` is a module. Our models.go file look like this:

```go
package company
import "github.com/google/uuid";
type Company struct {
    Id string;
    Name string;
    Country string;
}
func newCompany(name string, country string) Company {
    return Company {
        Id: uuid.New().String(),
        Name: name,
        Country: country
    }
}
```
Let's add a property `manager` which is private. We cannot prevent the code of the same module from accessing this property, however, we can prevent other modules from accessing the property by making the first letter of the name smallcase. 

> Names with the first letter as capital case are exported and the names with the first letter as small case are limited to the same module. This applies to properties, variables, types and functions.
```go
type Company struct{
  Id string;
  Name string;
  Country string;
  manager string;
}
```
### Inheritance
Although there is no direct concept of Inheritance in go, we can embed base structs into its child structs.
Let's say, we want to define `Employee` struct also. Since it has the same properties as the company we want it to inherit those properties from `Company`. We can define it like this:
```go
type Employee struct{
  Company;
  Salary int;
}
```
To define function `newEmployee`:
```go
func newEmployee(companyName string,
    companyCountry string, salary int
) Employee {
    company: = newCompany(
        companyName,
        companyCountry)
    return Employee {
        Company: company,
        Salary: salary,
    }
}
```
We can access the `Company` parameters or methods directly:
```go
func newEmployee(companyName string,
    companyCountry string, salary int
) Employee {
    company: = newCompany(
        companyName,
        companyCountry)
    employee: = Employee {
        Company: company,
        Salary: salary,
    }
    employee.saveToDatabase();
    return employee
}
```

> Notice we used `saveToDatabase` method of company with `Employee` struct object.

### Polymorphism
Go provides interfaces for this purpose. A type implementing a function or multiple functions defined in the interface becomes the type of that interface.
We will extend our current example and define another type of VendorCompany. This is the company from which our current company buys raw materials.
Implementation would be similar to Employee. Since all three of them are similar objects and being saved into db in the same table/collection (hypothetically), we would define an interface `CompanyInterface` so that we treat all three objects as the same type - the interface. Since we have also defined three different types of entities into one object, let's create a method to return the type of entity when called.

model.go
```go
package company
import ("github.com/google/uuid")
type CompanyInterface interface {
    saveToDatabase();
    getType() CompanyEntity;
}
type CompanyEnum string;
const (CompanyType CompanyEnum =
    "Company"; VendorType CompanyEnum =
    "Vendor"; EmployeeType CompanyEnum =
    "Employee";)
type Company struct {
    Id string
    Name string
    Country string
    manager string
}
type Employee struct {
    Company
    Salary int
}
type VendorCompany struct {
    Company
    AccountNumber string
}
func newCompany(name string, country string) Company {
    return Company {
        Id: uuid.New()
            .String(),
        Name: name,
        Country: country
    }
}
func newEmployee(companyName string,
    companyCountry string, salary int
) Employee {
    company: = newCompany(
        companyName,
        companyCountry)
    employee: = Employee {
        Company: company,
        Salary: salary,
    }
    employee.saveToDatabase()
    return employee
}
func newVendor(companyName string,
    companyCountry string,
    accountNumber string) VendorCompany {
    company: = newCompany(
        companyName,
        companyCountry)
    vendor: = VendorCompany {
        Company: company,
        AccountNumber: accountNumber,
    }
    vendor.saveToDatabase()
    return vendor
}
```

methods.go
```go
package company
import "fmt"
func(c * Company) saveToDatabase() {
    fmt.Println(
        "Saving Company to database..."
    )
}
func(c * Company) getType() CompanyEnum {
    return CompanyType;
}
func(e * Employee) saveToDatabase() {
    fmt.Println(
        "Saving Employee to database..."
    )
}
func(e * Employee) getType() CompanyEnum {
    return EmployeeType;
}
func(v * VendorCompany) saveToDatabase() {
    fmt.Println(
        "Saving VendorCompany to database..."
    )
}
func(v * VendorCompany) getType() CompanyEnum {
    return VendorType;
}
```

Now lets create a generic function in `main.go` to print the type of object provided.

```go
func getCompanyNameInLowerCase(company CompanyInterface) {
    fmt.Println(company.getType());
}
```
We can use this function for all three type of objects:

```go
  employee:= newEmployee("Raju", "India",25)
  company:= newCompany("Lakshmi Chit Funds", "India")
  vendor:= newVendor("Babu Rao","India","abcd1234")
  printCompanyType(&employee)
  printCompanyType(&vendor)
  printCompanyType(&company)
```
Notice that defining the interface saved us from the three different implementations of printCompanyType for all three types. This practice can improve the code maintainability in the long run.

### Abstraction
Similar to all other concepts, Go does not have abstract classes. However, we can make our workaround for these as well.
Let's define a struct CompanyEntity with all Company properties except, we throw a "Not Implemented" error in all struct methods.

struct definition
```go
type CompanyEntity struct {
    Id string
    Name string
    Country string
    manager string
}
```

methods definition
```go
func(c * CompanyEntity) saveToDatabase() error {
    return errors.New(
        "Not implemented")
}
func(c * CompanyEntity) getType() error {
    return errors.New(
        "Not implemented")
}
```
This struct can serve as our abstract class, only difference is we can instantiate an object from this struct. However, all the methods are not implemented and can be overwritten by child classes.
We can now make our child structs inherit the properties of this struct.
```go
package company
import (
    "github.com/google/uuid"
)
// Define an interface for types that can be saved to the database
type CompanyInterface interface {
    saveToDatabase();
    getType() CompanyEnum;
}
type CompanyEntity struct {
    Id string
    Name string
    Country string
    manager string
}
type CompanyEnum string;
const (
    CompanyType CompanyEnum = "Company"; VendorType CompanyEnum =
    "Vendor"; EmployeeType CompanyEnum =
    "Employee";
)
type Company struct {
    CompanyEntity
}
type Employee struct {
    CompanyEntity
    Salary int
}
type VendorCompany struct {
    CompanyEntity
    AccountNumber string
}
func newCompanyEntity(name string,
    country string) CompanyEntity {
    return CompanyEntity {
        Id: uuid.New().String(),
        Name: name,
        Country: country,
    }
}
func newCompany(name string, country string) Company {
    companyEntity: =
        newCompanyEntity(name,
            country);
    return Company {
        companyEntity
    }
}
func newEmployee(companyName string,
    companyCountry string, salary int
) Employee {
    companyEntity: =
        newCompanyEntity(
            companyName,
            companyCountry)
    employee: = Employee {
        CompanyEntity: companyEntity,
        Salary: salary,
    }
    employee.saveToDatabase()
    return employee
}
func newVendor(companyName string,
    companyCountry string,
    accountNumber string) VendorCompany {
    companyEntity: =
        newCompanyEntity(
            companyName,
            companyCountry)
    vendor: = VendorCompany {
        CompanyEntity: companyEntity,
        AccountNumber: accountNumber,
    }
    vendor.saveToDatabase()
    return vendor
}
```

> This way we used `CompanyEntity` struct as an abstract class.

## Conclusion
While Go may not have the traditional syntax and constructs associated with Object-Oriented Programming (OOP), it offers powerful features that enable us to achieve similar functionality in a more idiomatic way. By leveraging structs, interfaces, and package organization, Go allows for the implementation of key OOP concepts such as classes, encapsulation, inheritance, polymorphism, and abstraction.
While transitioning from languages with more explicit support for OOP, such as Java or C++, to Go may require some adjustment in thinking, adjusting our thinking to these concepts in Go opens up new possibilities for coding patterns and code structure management.

Thank you for making this far 😇. Please let me know how I can improve on the content and refer me some interesting topics to learn.
Connect me on LinkedIn: https://www.linkedin.com/in/parthlaw