Golang interface is introduced

Interface is one of the basic features of GO. Can be understood as a type of specification or convention. Unlike Java or C#, it does not require any explicit implementation of an interface. It does not inherit, subclass, or “implements” the keyword, but implements methods in interface implicitly by convention. Therefore, interfaces in Golang make coding more flexible and extensible.

How to understand interface in go? Just keep these three things in mind:Copy the code
  1. Interfaces are collections of method declarations
  2. An object of any type that implements all methods declared in the interface interface implements the interface.
  3. Interface can be used as a data type, and any object that implements the interface can assign a value to the corresponding interface type variable.

Note:

  • Interfaces can be implemented by any object, or multiple interfaces can be implemented by a single type/object.
  • Methods cannot be overloaded, such aseat(), eat(s string)Can’t exist at the same time.
package main

import "fmt"

type Phone interface {
    call()
}

type NokiaPhone struct{}func (nokiaPhone NokiaPhone) call(a) {
    fmt.Println("I am Nokia, I can call you!")}type ApplePhone struct{}func (iPhone ApplePhone) call(a) {
    fmt.Println("I am Apple Phone, I can call you!")}func main(a) {
    var phone Phone
    phone = new(NokiaPhone)
    phone.call()

    phone = new(ApplePhone)
    phone.call()
}
Copy the code

The interface syntax is shown above, and the polymorphic nature is also shown in the main function. The same abstract interface of phone points to different entity objects, invokes call() method, and prints different results, which reflects the characteristics of polymorphism.

Object-oriented – Open close principle

Tiled module design

So what’s the point of being an interface data type? It’s really about satisfying some object-oriented programming ideas. As we know, the highest goal of software design is high cohesion and low coupling. So one of the design principles is called the open close principle. What is the on/off principle? Here’s an example:

package main

import "fmt"

// We want to write a class,Banker Banker
type Banker struct{}// Deposit services
func (this *Banker) Save(a) {
    fmt.Println( "Made a deposit...")}// Transfer service
func (this *Banker) Transfer(a) {
    fmt.Println( "Made a transfer...")}// Payment business
func (this *Banker) Pay(a) {
    fmt.Println( "Made the payment...")}func main(a) {
    banker := &Banker{}

    banker.Save()
    banker.Transfer()
    banker.Pay()
}
Copy the code

The code is very simple, is a bank clerk, he may have a lot of business, such as Save() deposit, Transfer() Transfer, Pay() payment, etc. So if this clerk module only these methods is ok, but as our program writing more and more complex, the bank clerk may have to add methods, will lead to more and more bloated clerk module.

This design will lead to, when we go to add new Banker business, will directly modify the original Banker code, then the function of the Banker module will be more and more, the probability of problems will be more and more, if the Banker has 99 business at this time, Now we want to add the 100th business, may be due to a careless, resulting in the previous 99 business collapse, because all the business are in a Banker class, their coupling degree is too high, the Banker’s responsibilities are not single enough, the maintenance cost of the code with the complex proportional to the business doubled.

Open and close principle design

So, if we had an interface, an interface thing, then we could abstract a layer, make an abstract Banker module, and provide an abstract method. Respectively according to this abstract module, to achieve the payment Banker (implementation of the payment method), transfer Banker (implementation of the transfer method), as follows:

Then you can still handle the requirements of the program. Then, when we want to add additional functionality to Banker, we can now define a separate stock Banker(implement stock method) into the system, instead of modifying Banker directly. And the success or failure of the stock Banker will not affect the stability of the system before, he is very single, and independent.

So, when we add a feature to a system, we don’t do it by modifying the code, we do it by adding code, and that’s the core idea of the open closed principle. In order to meet the above requirements, it is necessary to provide a layer of abstract interface.

The Golang code is implemented as follows:

package main

import "fmt"

// An abstract bank clerk
type AbstractBanker interface{
    DoBusi()    // Handle business interfaces abstractly
}

// A deposit clerk
type SaveBanker struct {
    //AbstractBanker
}

func (sb *SaveBanker) DoBusi(a) {
    fmt.Println("Made a deposit.")}// Transfer operator
type TransferBanker struct {
    //AbstractBanker
}

func (tb *TransferBanker) DoBusi(a) {
    fmt.Println("A transfer was made.")}// Pay the salesman
type PayBanker struct {
    //AbstractBanker
}

func (pb *PayBanker) DoBusi(a) {
    fmt.Println("Payment was made.")}func main(a) {
    // Make a deposit
    sb := &SaveBanker{}
    sb.DoBusi()

    // Make a transfer
    tb := &TransferBanker{}
    tb.DoBusi()

    // Make the payment
    pb := &PayBanker{}
    pb.DoBusi()

}
Copy the code

Of course, we can also design a small frame based on AbstractBanker:

// Implement architecture layer (encapsulate business based on abstraction layer - encapsulate interface interface)
func BankerBusiness(banker AbstractBanker) {
    // Call through the interface, (polymorphisms)
    banker.DoBusi()
}
Copy the code

The business call can be implemented in Main as follows:

func main(a) {
    // Make a deposit
    BankerBusiness(&SaveBanker{})

    // Make a deposit
    BankerBusiness(&TransferBanker{})

    // Make a deposit
    BankerBusiness(&PayBanker{})
}
Copy the code
The open closed principle: a software entity such as classes, modules, and functions should be open for extension and closed for modification. Simply put, when changing requirements, try to implement changes through extensions rather than by modifying existing code.Copy the code

The meaning of the interface

Okay, so now that you have a basic understanding of interface, what does interface mean in the end, and you have a little bit of a sense of what interface means, in fact, the biggest idea of interface is to implement the idea of polymorphism, which is that we can design apis based on interface types, So the adaptability of the API interface can not only adapt to all the modules implemented today, but also adapt to the module implemented in the future to call. Invocation may be what interfaces are all about in the future, and that’s why architects are so valuable, because a good architect can design a framework for interfaces that will work for many years to come.

Dependency inversion principle in object orientation

Examples of high coupling design

The implementation code is as follows:

package main

import "fmt"

// Mercedes-benz <===
type Benz struct{}func (this *Benz) Run(a) {
    fmt.Println("Benz is running...")}// BMW <===
type BMW struct{}func (this *BMW) Run(a) {
    fmt.Println("BMW is running ...")}//===> Driver Zhang SAN <=== =
type Zhang3 struct {
    / /...
}

func (zhang3 *Zhang3) DriveBenZ(benz *Benz) {
    fmt.Println("zhang3 Drive Benz")
    benz.Run()
}

func (zhang3 *Zhang3) DriveBMW(bmw *BMW) {
    fmt.Println("zhang3 drive BMW")
    bmw.Run()
}

//===> Driver Li Si <=== ==
type Li4 struct {
    / /...
}

func (li4 *Li4) DriveBenZ(benz *Benz) {
    fmt.Println("li4 Drive Benz")
    benz.Run()
}

func (li4 *Li4) DriveBMW(bmw *BMW) {
    fmt.Println("li4 drive BMW")
    bmw.Run()
}

func main(a) {
    // Business 1 card 3 drive Mercedes Benz
    benz := &Benz{}
    zhang3 := &Zhang3{}
    zhang3.DriveBenZ(benz)

    // Business 2 Li Sikai BMW
    bmw := &BMW{}
    li4 := &Li4{}
    li4.DriveBMW(bmw)
}
Copy the code

Let’s look at the dependency between the above code and each module in the figure. In fact, we don’t use any code of interface interface layer. Obviously, in the end, our two businesses, Including Mercedes-benz, Lisi and BMW, are also implemented in the program. The problem with this kind of design is that it’s fine to be small, but if the program needs to be expanded, like I’m adding a Toyota or a driver, then the dependencies between modules grow exponentially, like a spider’s web, and become harder to maintain and straighten out.

Abstract oriented programming design with low coupling degree

As shown in the figure above, if we design a system, the module is divided into three layers, abstraction layer, implementation layer, business logic layer. So, we first define the module and interface of the abstraction layer, here we need the design of the interface, and then we follow the abstraction layer, implement each implementation layer module in turn, when we write the implementation layer code, in fact, we only need to refer to the corresponding abstraction layer implementation, implement each module, It has no relation to any other implementation module, which also conforms to the open and close principle described above. In this way, each module only depends on the interface of the object, and has no relationship with other modules, the dependency relationship is single. The system is easy to expand and maintain.

We specify the business logic is the same, just reference abstraction layer interface to business, abstraction layer exposed interface is our business can use method, then can through the offline polymorphism, which realize the module, interface pointer to invoke the is the concrete implementation method, so that our business logic layer is also dependent on abstract into programming.

We call this design principle the dependency inversion principle.

Let’s take a look at the modified code:

package main

import "fmt"

// ===== > Abstraction layer < ========
type Car interface {
    Run()
}

type Driver interface {
    Drive(car Car)
}

// ===== > Implementation layer < ========
type BenZ struct {
    / /...
}

func (benz * BenZ) Run(a) {
    fmt.Println("Benz is running...")}type Bmw struct {
    / /...
}

func (bmw * Bmw) Run(a) {
    fmt.Println("Bmw is running...")}type Zhang_3 struct {
    / /...
}

func (zhang3 *Zhang_3) Drive(car Car) {
    fmt.Println("Zhang3 drive car")
    car.Run()
}

type Li_4 struct {
    / /...
}

func (li4 *Li_4) Drive(car Car) {
    fmt.Println("li4 drive car")
    car.Run()
}


// ===== > Business logic layer < ========
func main(a) {
    // Zhang 3 drives a BMW
    var bmw Car
    bmw = &Bmw{}

    var zhang3 Driver
    zhang3 = &Zhang_3{}

    zhang3.Drive(bmw)

    // Li 4 drives a Mercedes Benz
    var benz Car
    benz = &BenZ{}

    var li4 Driver
    li4 = &Li_4{}

    li4.Drive(benz)
}
Copy the code

From one example to another – Exercises

Simulate the assembly of two computers: - Abstract layer: graphics Card method display, Memory method storage, processor CPU method calculate; -- Implementation layers: Intel, products (graphics card, memory, CPU), Kingston, products (memory 3), NVIDIA, products (graphics card); - Logical layer: 1. Assemble an Intel series COMPUTER and run it. 2. Assemble an Intel CPU Kingston memory NVIDIA graphics card computer and run it.Copy the code

The Golang code is implemented as follows:

/* The abstract layer has the graphics Card method display Memory method storage has the processor CPU method Calculate the implementation layer has Intel , the product has (graphics card, memory, CPU) Kingston company, the product has (memory 3) NVIDIA company, the product has (graphics card) -- logic layer -- 1. 2. Assemble a COMPUTER with Intel CPU Kingston memory and NVIDIA graphics card and run */
package main

import "fmt"

//------ abstraction layer -----
type Card interface{
    Display()
}

type Memory interface {
    Storage()
}

type CPU interface {
    Calculate()
}

type Computer struct {
    cpu CPU
    mem Memory
    card Card
}

func NewComputer(cpu CPU, mem Memory, card Card) *Computer{
    return &Computer{
        cpu:cpu,
        mem:mem,
        card:card,
    }
}

func (this *Computer) DoWork(a) {
    this.cpu.Calculate()
    this.mem.Storage()
    this.card.Display()
}

//------ implementation layer -----
//intel
type IntelCPU struct {
    CPU
}

func (this *IntelCPU) Calculate(a) {
    fmt.Println("Intel CPU starts calculating...")}type IntelMemory struct {
    Memory
}

func (this *IntelMemory) Storage(a) {
    fmt.Println("Intel Memory started storing...")}type IntelCard struct {
    Card
}

func (this *IntelCard) Display(a) {
    fmt.Println("Intel Card is showing...")}//kingston
type KingstonMemory struct {
    Memory
}

func (this *KingstonMemory) Storage(a) {
    fmt.Println("Kingston memory storage...")}//nvidia
type NvidiaCard struct {
    Card
}

func (this *NvidiaCard) Display(a) {
    fmt.Println("Nvidia card display...")}//------ business logic layer -----
func main(a) {
    // Intel series of computers
    com1 := NewComputer(&IntelCPU{}, &IntelMemory{}, &IntelCard{})
    com1.DoWork()

    / / miscellaneous brands
    com2 := NewComputer(&IntelCPU{}, &KingstonMemory{}, &NvidiaCard{})
    com2.DoWork()
}
Copy the code

summary

The above is to understand the interface in Golang with object-oriented thinking, and to illustrate how to correctly use interface (interface) through design principles, so as to achieve “high cohesion, low coupling”, so that the code is written more elegant and more extensible.