Object-Oriented Programming (OOP) is a programming paradigm based on objects as the central concept. In OOP, code is formatted based on functionality, enabling code maintenance, abstraction, reusability, efficiency, and numerous functionality on the object.

The object has attributes (variables) that define its characteristics, properties, and methods (functions) which define the actions (procedures) and behaviors of the object.

Object-oriented programming in Go is different from other languages. Object-Oriented concepts are implemented in Go using structs, interfaces, and custom types.

Customizing Types in Go

Custom types make it easy to group and identify similar code for reuse.

The code for declaring custom types is:

        type typeName dataType 

On creating a custom type and assigning a variable, you can check the type using reflect.TypeOf() which takes in a variable and returns the type of the variable.

        import( "fmt"
        "reflect")

type two int // creates type "two"
var number two // variable of type "two"
fmt.Println(reflect.TypeOf(number))

​​​

example-of-creating-custom-types-in-Go-2

The number variable is a type of two which is an integer. You can go further to create more of the custom type.

Creating Structs in Go

Structs (structures) are the blueprints for object-oriented programming in Go. Structs are user-defined collections of fields.

A struct can contain a variety of data types, including compound types and methods.

You can create a struct using this syntax:

        type StructName struct {
// some code
}

Conventionally, struct names are usually capitalized and camel-cased for readability.

The struct type takes in field names and data types. Structs can take in any Go data type, including custom types.

        type User struct {
    field1 string
    field2 int
    fieldMap map[string]int
}

You can instantiate a struct type by assigning the struct as a variable.

        instance := User{
// some code
}

The struct instance can be populated with fields on instantiation as defined at initialization or set to null.

        instance := User{
        field1: "a string field",
        field2: 10,
        fieldMap: map[string]int{},
    }

Accessing Struct Elements

You can access the fields of a struct instance using a dot notation to the field.

        fmt.Println("Accessing a field of value", instance.field2)

This outputs the field2 of the struct instance instantiated.

Assigning Methods to Structs

Functions(methods) are assigned to struct types by specifying a receiver name and the struct name before the function name as shown in the syntax below.

        func (receiver StructName) functionName() {
    // some code
}

The method functionName can only be used on the struct type specified.

Implementing Inheritance in Go

Inheritance is the ability of objects and types to access and use methods and attributes of other objects. Go doesn't have Inheritance as a feature, but you can use compositions. In Go, composition entails referring to a superstruct (the struct to be inherited) in a substruct by providing the superstruct's name to the substruct.

Using the struct example above:

        type User struct {
    field1 string
    field2 int
    fieldMap map[string]int
}

type User2 struct {
    User
}

By passing the User struct name into the User2 struct, the User2 struct can access all the methods and attributes of the User struct on instantiation except abstraction techniques are employed.

        son := User2{
        User{
            field1: "baby",
            field2: 0,
            fieldMap: nil,
        },
    }

fmt.Println(son.field2)

The son variable above is an instantiation of the User2 struct. As seen in the example, the son variable can access and instantiate values of the User type and use them.

Encapsulating Type Fields in Go

Encapsulation, also known as “information hiding,” is a technique of bundling the methods and attributes of an object into units to restrict the use and access except specified (enabling read/write privileges).

Encapsulation is implemented in Go using exported and unexported identifiers in packages.

Exported Identifiers (Read and Write)

Exported identifiers are exported from their defined packages and access to other programs. Capitalizing a field identifier exports the field fo.

        type User struct {
    Field1 string
    Field2 int
    FieldMap map[string]int
}

type User2 struct {
    User
}

Unexported Identifiers (Read-Only)

Unexported identifiers are not exported from the defined package and are conventionally lowercased.

        type User struct {
    field1 string
    field2 int
    fieldMap map[string]int
}

type User2 struct {
    User
}

The concept of exported and unexported identifiers also applies to the methods of an object.

Polymorphism in Go

Polymorphism is a technique used to give different forms to an object for flexibility.

Go implements polymorphism using interfaces. Interfaces are custom types used to define method signatures.

Declaring Interfaces

Declaring interfaces is similar to declaring structs. However, interfaces are declared using the interface keyword.

        type InterfaceName interface{
    //some methods
}

Interface declarations contain methods that are to be implemented by struct types.

Implementing Interfaces in Structs

The types that implement the interface have to be declared after which the methods of the type implement the interface.

        // The Interface
type Color interface{
    Paint() string
}

// Declaring the structs
type Green struct {
 // some struct specific code
}

type Blue struct {
    // some specific code
}

The code snippet above has a Color interface declared with a Paint method to be implemented by the Green and Blue struct types.

Interfaces are implemented by assigning methods to struct types and then naming the method by the interface method to be implemented.

        func (g Green) Paint() string {
    return "painted green"
}

func (b Blue) Paint() string {
    return "painted blue"
}

The Paint method is implemented by Green and Blue types which can now call and use the Paint method.

        brush := Green{}

fmt.Println(brush.Paint())

“Painted green” is printed on the console validating that the interface was successfully implemented.

Abstracting Fields in Go

Abstraction is the process of hiding unimportant methods and attributes of a type, making it easier to secure parts of the program from abnormal, unintended use.

Go doesn’t have abstraction implemented right off the bat; however, you can work our way through implementing abstraction using interfaces.

        // humans can run
type Human interface {
    run() string
}

// Boy is a human with legs
type Boy struct {
    Legs string
}

// a method on boy implements the run method of the Human interface
func (h Boy) run() string {
    return h.Legs

}

The code above creates a Human interface with a run interface that returns a string. The Boy type implements the run method of the Human interface and returns a string on instantiation.

One of the ways to implement abstraction is by making a struct inherit the interface whose methods are to be abstracted. There are many other approaches, but this is the easiest.

        type Person struct {
    Name string
    Age int
    Status Human
}

func main() {
    person1 := &Boy{Legs: "two legs"}
   person2 := &Person{ // instance of a person
        Name: "amina",
        Age: 19,
        Status: person1,
    }
    fmt.Println(person.Status.run())
}

example-of-abstraction-in-Go-3

The Person struct inherits the Human interface and can access all its methods using the variable Status inheriting the interface.

On instantiation by reference(using a pointer), the instance of the Person struct Person2 references an instance of the Boy struct Person1 and gets access to the methods.

This way, you get to specify specific methods to be implemented by the type.

OOP vs Functional Programming

Object-oriented programming is an important paradigm as it gives you more control of your program and encourages code reuse in ways functional programming doesn't.

This doesn’t make functional programming a bad choice, as functional programming can be helpful and better for some use-cases.