...

How To Master Higher level abstraction in Golang using generics and interface in 30 Minutes

Rate this post

Higher level abstraction in Golang is now possible after the introduction of generics.

The addition of generics in Golang fills the missing gap. It also helps in writing abstract code allowing loose coupling.

This post will easily illustrate how to use higher-level abstraction in Golang using generics and interface. Read through it and practice it yourself. Remember, consistent and intelligent practice is the key to success in programming.

Prerequisites

Learn Generics in Golang

Higher level abstraction in golang
Higher level abstraction in Golang

Implementation of Achieve Higher level abstraction in Golang using generics and interface

Let us understand this with an example.

Suppose we want to model fruit and vegetable packaging. So we will need a Fruit and Vegetable interface since they are different. Again we have many types of fruits and vegetables. We want to package them in a box, clearly their packaging will also be different. On top of this packaging (Box) will have its property. At this point, it seems complex.

Let us understand this with the below code.

// Implementation of Achieve Higher level abstraction in golang using generics and interface

We will start with defining Fruit interface
type Fruit interface {
	Name() string
	Color() string
	Taste() string
	PricePerKg() int
}

Also, let us create struct for apple and mango fruit

type Apple struct {
	count int
}

func (a *Apple) Name() string {
	return "apple"
}

func (a *Apple) Color() string {
	return "green"
}

func (a *Apple) Taste() string {
	return "sweet-sour"
}

func (a *Apple) PricePerKg() int {
	return 100
}


type Mango struct {
	count int
}

func (m *Mango) Name() string {
	return "mango"
}

func (m *Mango) Color() string {
	return "yellowish-green"
}

func (m *Mango) Taste() string {
	return "sweet"
}

func (m *Mango) PricePerKg() int {
	return 200
}


Now let us define Vegetable interface

type Vegetable interface {
	Name() string
	IsFresh() bool
	PricePerUnit() int
	IsFragile() bool
}

Now we will define Potato and Tomato fruit

type Potato struct {
	name string
	age  int // value in days
}

func (p *Potato) Name() string {
	return "potato"
}

func (p *Potato) IsFresh() bool {
	return p.age < 2
}

func (p *Potato) PricePerUnit() int {
	return 40
}

func (p *Potato) IsFragile() bool {
	return false
}

type Tomato struct {
	name string
	age  int // value in days
}

func (t *Tomato) Name() string {
	return "tomato"
}

func (t *Tomato) IsFresh() bool {
	return t.age < 2
}

func (t *Tomato) PricePerUnit() int {
	return 50
}

func (t *Tomato) IsFragile() bool {
	return true
}

Now we will proceed to complete box related code. First we will define Boxer interface which will match any type of box

type Boxer interface {
	Name() string
	PricePerBox() int
}


then we will define boxstructs for fruits and vegetables

type BoxFruit[T Fruit] struct {
	item       T
	weightInKg int
}

func (b *BoxFruit[T]) Name() string {
	return "box of fruit of " + b.item.Name()
}

func (b *BoxFruit[T]) PricePerBox() int {
	return b.weightInKg * b.item.PricePerKg()
}

type BoxVegetable[T Vegetable] struct {
	item       T
	weightInKg int
}

func (b *BoxVegetable[T]) Name() string {
	return "vegetable box of " + b.item.Name()
}

func (b *BoxVegetable[T]) PricePerBox() int {
	return b.weightInKg * b.item.PricePerUnit()
}

We will finish this by writing a driver code (main code) as below
func main() {
	ap := &Apple{10}
	m := &Mango{5}

	boxApple := &BoxFruit[*Apple]{ap, 10} //Since *Apple implements Fruit interface
	boxMango := &BoxFruit[*Mango]{m, 20}  //Since *Mango implements Fruit interface

	potato := &Potato{"potato", 1}
	boxPotato := &BoxVegetable[*Potato]{potato, 5} //Since *Potato implements Vegetable interface
	tomato := &Tomato{"tomato", 2}
	boxTomato := &BoxVegetable[*Tomato]{tomato, 7} //Since *Tomato implements Fruit interface

	boxes := []Boxer{boxApple, boxMango, boxPotato, boxTomato}
	for _, box := range boxes {
		fmt.Println(box.Name(), box.PricePerBox())
	}

}

///////////////////////////////////////////////////////// Complete code as below //////////////////////////////////////////////////////////////////

Let us apply generics in Golang to decouple the above code and achieve higher level abstraction in Golang

//
package main

import "fmt"

type Fruit interface {
	Name() string
	Color() string
	Taste() string
	PricePerKg() int
}

type Apple struct {
	count int
}

func (a *Apple) Name() string {
	return "apple"
}

func (a *Apple) Color() string {
	return "green"
}

func (a *Apple) Taste() string {
	return "sweet-sour"
}

func (a *Apple) PricePerKg() int {
	return 100
}

type Mango struct {
	count int
}

func (m *Mango) Name() string {
	return "mango"
}

func (m *Mango) Color() string {
	return "yellowish-green"
}

func (m *Mango) Taste() string {
	return "sweet"
}

func (m *Mango) PricePerKg() int {
	return 200
}

type Vegetable interface {
	Name() string
	IsFresh() bool
	PricePerUnit() int
	IsFragile() bool
}

type Potato struct {
	name string
	age  int // value in days
}

func (p *Potato) Name() string {
	return "potato"
}

func (p *Potato) IsFresh() bool {
	return p.age < 2
}

func (p *Potato) PricePerUnit() int {
	return 40
}

func (p *Potato) IsFragile() bool {
	return false
}

type Tomato struct {
	name string
	age  int // value in days
}

func (t *Tomato) Name() string {
	return "tomato"
}

func (t *Tomato) IsFresh() bool {
	return t.age < 2
}

func (t *Tomato) PricePerUnit() int {
	return 50
}

func (t *Tomato) IsFragile() bool {
	return true
}

type Boxer interface {
	Name() string
	PricePerBox() int
}

type BoxFruit[T Fruit] struct {
	item       T
	weightInKg int
}

func (b *BoxFruit[T]) Name() string {
	return "box of fruit of " + b.item.Name()
}

func (b *BoxFruit[T]) PricePerBox() int {
	return b.weightInKg * b.item.PricePerKg()
}

type BoxVegetable[T Vegetable] struct {
	item       T
	weightInKg int
}

func (b *BoxVegetable[T]) Name() string {
	return "vegetable box of " + b.item.Name()
}

func (b *BoxVegetable[T]) PricePerBox() int {
	return b.weightInKg * b.item.PricePerUnit()
}

func main() {
	apple := &Apple{10}
	mango := &Mango{5}

	boxApple := &BoxFruit[*Apple]{apple, 10} //Since *Apple implements Fruit interface
	boxMango := &BoxFruit[*Mango]{mango, 20} //Since *Mango implements Fruit interface

	potato := &Potato{"potato", 1}
	boxPotato := &BoxVegetable[*Potato]{potato, 5} //Since *Potato implements Vegetable interface
	tomato := &Tomato{"tomato", 2}
	boxTomato := &BoxVegetable[*Tomato]{tomato, 7} //Since *Tomato implements Fruit interface

	boxes := []Boxer{boxApple, boxMango, boxPotato, boxTomato}
	for _, box := range boxes {
		fmt.Printf("name: %s, price per box: %v\n", box.Name(), box.PricePerBox())
	}

}

// output
name: box of fruit of apple, price per box: 1000
name: box of fruit of mango, price per box: 4000
name: vegetable box of potato, price per box: 200
name: vegetable box of tomato, price per box: 350

Program link

FAQs on Higher level abstraction in Golang

Q1 – When do I need to use Higher level abstraction in Golang?

A – Whenever you need to use multiple compositions of abstraction or multiple levels of abstraction in Golang (as the above example in this post clearly illustrates its use case), there you can apply Higher level abstraction in Golang to remove the coupling and write modular code.

Conclusion

In this post, you learned how to use Higher level abstraction in Golang using generics and interfaces (also pay attention to how easy it is to use generics in Golang). Just practice with simple programs then apply it to your projects.

Enjoy the post!

Related Posts

Spread the love

Leave a Comment

Seraphinite AcceleratorOptimized by Seraphinite Accelerator
Turns on site high speed to be attractive for people and search engines.