Go object oriented - Interface

Posted by Deemo on Tue, 18 Jan 2022 02:29:02 +0100

22.Go object oriented - Interface

6 interface

Before explaining the specific interface, first look at the following questions.

Using object-oriented method, design an addition and subtraction calculator

The code is as follows:

// Parent class
type ObjectOperate struct {
   num1 int
   num2 int
}

// Additive class
type AddOperate struct {
   ObjectOperate
}

// Method of addition class
func (p *AddOperate) Operate(a, b int) int {
   p.num1 = a
   p.num2 = b
   return p.num1 + p.num2
}

// Subtraction class
type SubOperate struct {
   ObjectOperate
}

// Subtraction method
func (p *SubOperate) Operate(a, b int) int {
   p.num1 = a
   p.num2 = b
   return p.num1 - p.num2
}

func main() {
   // Create subtraction class
   var sub SubOperate
   i := sub.Operate(5, 2) // Perform subtraction
   fmt.Println(i)
} 

The above implementation is very simple, but there is a problem. In the main() function, when we want to use subtraction, we create the object of subtraction class and call its corresponding subtraction method.

However, one day, the system requirements have changed, requiring addition instead of subtraction, so you need to make a lot of modifications to the code in the main() function.

Comment out the original code, create an addition class object, and call its corresponding addition method.

Is there a way to let the main() function modify only a few codes to solve this problem?

Yes, I need to use the knowledge points of the interface explained next.

6.1 what is an interface

Interface is a kind of specification and standard. It is often seen in life. For example, the USB interface of notebook computer can link the mouse and keyboard produced by any manufacturer with the computer.

Why? The reason is that after the specification and standard of USB interface are formulated, each manufacturer can produce mouse and keyboard according to the standard.

In program development, the interface only specifies what to do and what to do. The interface doesn't care what to do. This is also very similar to the interface cases in life. For example, USB interface only stipulates the standard, but it doesn't care how the specific mouse and keyboard are produced according to the standard

In enterprise development, if a project is relatively large, an architect who can sort out all businesses needs to define some main interfaces, which tell developers what functions you need to implement.

6.2 interface definition

The syntax of interface definition is as follows:

// Define interface type
type Humaner interface {
   // Method, only declaration, no implementation, implemented by other types (user-defined types)
   sayhi()
}  

How to implement the methods defined in the interface?

// Define Student class
type Student struct {
   name string
   id   int
}

// Student implements this interface method
func (tmp *Student) sayhi()  {
   fmt.Printf("Student[%s, %d] sayhi\n", tmp.name, tmp.id)
}

// Define Teacher class
type Teacher struct {
   addr  string
   group string
}

// Teacher implements this method
func (tmp *Teacher) sayhi() {
   fmt.Printf("Teacher[%s, %s] sayhi\n", tmp.addr, tmp.group)
}

The specific calls are as follows:

func main() {
   // Variables defining interface types
   var i Humaner
   // Only the type that implements this interface method, then the variable of this type (receiver type) can assign a value to i
   s := &Student{"mike", 666}
   i = s
   i.sayhi()

   t := &Teacher{"beijing", "go"}
   i = t
   i.sayhi()
}

//implement
Student[mike, 666] sayhi
Teacher[beijing, go] sayhi 

As long as the class (structure) implements the corresponding interface, the object created according to the class can be assigned to the corresponding interface type.

Interface naming conventions end with er.

6.3 polymorphism

What are the benefits of interfaces? Implement polymorphism.

Polymorphism refers to a variety of manifestations, as shown in the figure below:

img

The tractor can sweep the floor and act as a fan. Very powerful.

The interface is used to realize polymorphism as follows:

// Define interface type
type Humaner interface {
   // Method, only declaration, no implementation, implemented by other types (user-defined types)
   sayhi()
}

// Define Student class
type Student struct {
   name string
   id   int
}

// Student implements this interface method
func (tmp *Student) sayhi() {
   fmt.Printf("Student[%s, %d] sayhi\n", tmp.name, tmp.id)
}

// Define Teacher class
type Teacher struct {
   addr  string
   group string
}

// Teacher implements this method
func (tmp *Teacher) sayhi() {
   fmt.Printf("Teacher[%s, %s] sayhi\n", tmp.addr, tmp.group)
}

// Define an ordinary function whose parameters are interface types
// Only one function can have different performance and realize polymorphism
func WhoSayHi(i Humaner)  {
   i.sayhi()
}

func main() {
   // Variables defining interface types
   var i Humaner
   // Only the type that implements this interface method, then the variable of this type (receiver type) can assign a value to i
   s := &Student{"mike", 666}
   t := &Teacher{"beijing", "go"}

   // Calling the same function, passing different class objects and different expressions, realizes polymorphism
   WhoSayHi(s)
   WhoSayHi(t)
}

practice:

Exercise 1:
type Person struct{
    name string
}

func (p *Person) SayHello(){
    fmt.Println("I'm human,My name is:"+p.name)
}

type Chinese struct{
    Person
}

func (c *Chinese) SayHello(){
    fmt.Println("I am Chinese,,My name is:"+c.name)
}

type English struct{
    Person
}

func (e *English) SayHello(){
    fmt.Println("I'm English,My name is:"+e.name)
}

type Personer interface{
    SayHello()
}

func main(){
    c := &Chinese{Person{"zhangsan"}}
    e := &English{Person{"MrZhang"}}
    x := make([]Personer,2)
    x[0] = c
    x[1] = e
    for i:=0;i<len(x);i++{
        x[i].SayHello()
 }
}

The above case is basically the same as the first case, except that in terms of output, all objects stored in the slice are obtained through a loop, and then the SayHello() method is called respectively.

If there is no interface, its methods can only be called one by one.

Exercise 2:

Use polymorphism to insert the mobile hard disk or USB flash disk or MP3 into the computer to read and write data (analysis class, interface, method)

type MobileStorager interface {
 Read()
 Write()
}

type MobileDisk struct { //mobile hard disk drive
}

func (m *MobileDisk) Read() {
 fmt.Println("The mobile hard disk is reading data")
}
func (m *MobileDisk) Write() {
 fmt.Println("The mobile hard disk is writing data")
}

type UDisk struct {
}

func (u *UDisk) Read() {
 fmt.Println("U The disk is reading data")
}
func (u *UDisk) Write() {
 fmt.Println("U The disk is writing data")
}

type Computer struct {
}

func (c *Computer) CpuRead(i MobileStorager) {
 i.Read()
}
func (c *Computer) CpuWrite(i MobileStorager) {
 i.Write()
}

func main() {
 m := &MobileDisk{}
 c := &Computer{}
 c.CpuRead(m)
 c.CpuWrite(m)
}

// Execution:
The mobile hard disk is reading data
 The mobile hard disk is writing data

Exercise 3:

Sparrows can fly, parrots can fly, helicopters can fly

type Flyabler interface {
 Fly()
}

type Bird struct {
}

func (b *Bird) EatAndDrink() { //Define methods for Bird
 fmt.Println("Birds eat and drink")
}

type MaQue struct { //sparrow
 Bird
}

func (m *MaQue) Fly() {
 fmt.Println("Sparrows can fly")
}

type YingWu struct {
 Bird
}

func (y *YingWu) Fly() {
 fmt.Println("Parrot flying")
}

type Plane struct {
}

func (p *Plane) Fly() {
 fmt.Println("Aircraft flying")
}

func WhoFly(i Flyabler) {
 i.Fly()
}

func main() {
 m := &MaQue{}
 m.EatAndDrink()
 WhoFly(m)
 plane := &Plane{}
 WhoFly(plane)
}

// Execution:
Birds eat and drink
 Sparrows can fly
 Aircraft flying

Calculator case

We are familiar with the definition of interface and the use of interface to realize polymorphism, but what are the benefits of polymorphism?

Now let's talk about the calculator case proposed at the beginning. At the beginning, we have implemented a calculator with addition and subtraction function, but some students feel it is too troublesome, because to realize addition, we need to define the class (structure) of addition operation, and to realize subtraction, we need to define the class (structure) of subtraction, Therefore, the student realized a relatively simple addition and subtraction calculator, as shown below:

1. Using the object-oriented idea to realize a calculator with addition and subtraction function, some students may feel very simple. The code is as follows:
type Operation struct {
}

func (p *Operation) GetResult(numA float64, numB float64, operate string) float64 {
   var result float64
   switch operate {
   case "+":
      result = numA + numB
   case "-":
      result = numA - numB
   }
   return result
}

func main() {
   var operation Operation
   sum := operation.GetResult(10, 13, "+")
   fmt.Println(sum)
} 

We define a class (structure), and then create a method for this class to encapsulate the whole calculator function. In the future, we can directly use this class (structure) to create objects.

This is object-oriented encapsulation.

That is to say, when you finish writing this calculator, give it to your colleagues, your colleagues need to create objects directly, and then call the GetResult() method, so you don't need to care how the method is implemented.

Isn't this what we said earlier when explaining the concept of object-oriented? Find an object to work? You don't need to implement this function yourself.

2. If you carefully observe the above code, is there any problem?

Now let's add another function to the calculator, such as multiplication. What should we do? You might say it's very simple. Just add a case branch in the switch of GetResult() method.

The question is: in this process, what if you accidentally change the addition to subtraction? In other words, what if the rules of addition operation are modified?

Examples:

You can think of this program method as a salary management system in the company. If the company decides to modify the calculation rules of salary, because all the calculation rules are in the GetResult() method of Operation class, the company can only give you all the codes of this class before you can modify them.

At this time, when you see that your salary as a developer is so low, you think, "TMD, I'm tired to give you such a salary. Now I have a chance.". Add 3000 directly to your salary

numA+numB+3000

Therefore, we should separate addition and subtraction and not mix them all together, so that when you modify addition, it will not affect other operation rules:

The specific implementation is as follows:

// Define action parent class
type Operation struct {
   numA float64
   numB float64
}

// Define result interface
type GetResulter interface {
   GetResult() float64 // Method has a return value
}

// addition
type OperationAdd struct {
   Operation
}

func (a *OperationAdd) GetResult() float64 {
   return a.numA + a.numB
}

// subtraction
type OperationSub struct {
   Operation
}

func (s *OperationSub) GetResult() float64 {
   return s.numA - s.numB
}

img

Now the operations have been separated, and here we also define a parent class (structure) to put the public members in the parent class. If you want to modify an operation rule now, just send the corresponding classes and methods to you for modification.

Although the implementation here separates each operation, it is still a little different from what we implemented for the first time. The addition and subtraction calculator we implemented for the first time also separates each operation, but there is no interface defined. So what is the meaning of this interface? Continue to look at the following questions.

3: Now how to call it?

This is the question we asked you at the beginning. If you directly create the object of addition operation and call the corresponding method when calling, will it be changed to subtraction later?

A lot of modifications need to be made, so the solution to the problem is as follows:

// Create factory class
type OperationFactory struct {

}

func (f *OperationFactory) CreateOption(option string, numA float64, numB float64) float64  {
   var result float64
   switch option {
   case "+":
      add := &OperationAdd{Operation{numA, numB}}
      result = OperationWho(add)
   case "-":
      sub := &OperationSub{Operation{numA, numB}}
      result = OperationWho(sub)
   }
   return result
}

// Implementing polymorphism using interfaces
func OperationWho(i GetResulter) float64 {
   return i.GetResult()
} 

A class OperationFactory is created, and a method CreateOption() is added to the modified class to create the object. If the input is "+", create the object

The object of OperationAdd is then called the OperationWho() method to transfer the address of the object to the method, so the variable i refers to OperationAdd, and then the GetResult() method is invoked, which actually invokes the GetResult() method implemented by the OperationAdd class.

Similarly, if "-" is passed, the process is the same.

Therefore, through this program, we can realize the benefits of polymorphism.

4: last call
func main() {
   var factory OperationFactory
   s := factory.CreateOption("-", 10, 12)
   fmt.Println(s)
} 

At this time, you will find that the call is very simple. If you want to calculate the addition now, just change "-" to "+".

In other words, the dependency between the main() function and the concrete operation class is removed.

Of course, after the program is designed in this way:

If you want to modify the operation rules of addition now, you only need to modify the corresponding methods in the OperationAdd class, and you don't need to care about other classes. If you want to add the "multiplication" function now, how should you modify it?

First: define the class of multiplication and complete the multiplication operation.

Second: add the corresponding branch in the criteoption () method of the OperationFactory class.

But doing so will not affect any other operations.

You can try to realize the operation of "multiplication" and "division" by yourself.

When using object-oriented thinking to solve problems, we must first analyze and define which classes, interfaces and methods. Define these analyses, and then consider the specific implementation.

Finally, the complete code is as follows:

// Define action parent class
type Operation struct {
   numA float64
   numB float64
}

// Define result interface
type GetResulter interface {
   GetResult() float64 // Method has a return value
}

// addition
type OperationAdd struct {
   Operation
}

func (a *OperationAdd) GetResult() float64 {
   return a.numA + a.numB
}

// subtraction
type OperationSub struct {
   Operation
}

func (s *OperationSub) GetResult() float64 {
   return s.numA - s.numB
}

// Create factory class
type OperationFactory struct {

}

func (f *OperationFactory) CreateOption(option string, numA float64, numB float64) float64  {
   var result float64
   switch option {
   case "+":
      add := &OperationAdd{Operation{numA, numB}}
      result = OperationWho(add)
   case "-":
      sub := &OperationSub{Operation{numA, numB}}
      result = OperationWho(sub)
   }
   return result
}

// Implementing polymorphism using interfaces
func OperationWho(i GetResulter) float64 {
   return i.GetResult()
}


func main() {
   var factory OperationFactory
   s := factory.CreateOption("-", 10, 12)
   fmt.Println(s)
}

Through the above cases, we should be able to realize the benefits of polymorphism.

Next, let's experience the application of interfaces and polymorphism through another exercise.

Exercise: design a program to sell cars in a 4s store. (multiple classes, methods and interfaces shall be designed for analysis)

First, let's think about how many classes (structures) the program needs to design

People think of cars and 4s stores.

So the basic design code:

(1) : create a car class (structure)
// Automobile
type Car struct {
   
}

func (p *Car) Move()  {
   fmt.Println("Car movement")
}

func (p *Car) Stop()  {
   fmt.Println("Car stop")
} 

Two methods are defined in the modified structure.

(2) : create 4S store class (structure)
// 4s stores
type CarStore struct {
}

func (c *CarStore) Order(money float64) *Car {
   if money > 50000 {
      return &Car{}
   } else {
      return nil
   }
} 

Add an Order() method to this class (structure). The function of this method is to sell cars, so you need to pass "money" to this method, and then judge. If the conditions are met, the Car address will be returned, so the return type is * Car

(3) Call below:
func main() {
   var carStore CarStore
   car := carStore.Order(80000)
   car.Move()
   car.Stop()
}
(4) : if you are adding cars of different brands, what should you do?

The code is as follows:

// Automobile
type Car struct {
   catType string 
   money   float64
}

func (p *Car) Move() {
   fmt.Println(p.catType + "Car movement")
}

func (p *Car) Stop() {
   fmt.Println(p.catType + "Car stop")
}

// Define BMW car
type BMWCar struct {
   Car
} 

In the above code, the "BMW" class is defined to inherit the Car class, and two members are defined in the Car class.

(5) Define interface

Before defining the interface, the "Audi" class is also defined to inherit the Car class

// Defining the Audi category
type AudiCar struct {
   Car
}

Then define an interface:

type CarTyper interface {
   GetCar()
} 

The following implements the methods defined in the interface,

func (p *BMWCar) GetCar() {
   if p.money >= 60000 {
      p.Move()
      p.Stop()
   } else {
      fmt.Println("Not enough money to buy a BMW!")
   }
}

The function of this method is to judge whether the money is enough. If the money is enough, you can call the method of the car.

func (p *AudiCar) GetCar() {
   if p.money >= 70000 {
      p.Move()
      p.Stop()
   } else {
      fmt.Println("Not enough money to buy Audi!")
   }
}
(6) : modification of Order()
func (c *CarStore) Order(money float64, carType string) {
   switch carType {
   case "BMW":
      CarWho(&BMWCar{Car{carType, money}})
   case "Audi":
      CarWho(&AudiCar{Car{carType, money}})
   }

} 

According to the type of vehicle passed, we make a judgement and call the CarWho() method. This method is a polymorphic method. It is defined as follows:

func CarWho(i CarTyper)  {
   i.GetCar()
} 

If the passed type is "BMW", when calling the CarWho() method, the address of the BMWCar {} class (structure) is passed to the method (the assignment of the two attributes in the parent class Car is completed at the same time).

Since the type of CarWho() method parameter is an interface, but the BMWCar {} class (structure) implements the interface, it can be passed from the address of the BMWCar {} class.

At this time, parameter i refers to BMWCar {}. Calling GetCar() method refers to the method implemented by BMWCar {}.

In this method, the judgment of the amount of money is completed.

Similarly, if the passed type is "Audi", the process is the same.

(7) Call of main() function
func main() {
   var carStore CarStore
   carStore.Order(30000, "Audi")
}
(8) The complete code is as follows:
// Automobile
type Car struct {
   catType string
   money   float64
}

func (p *Car) Move() {
   fmt.Println(p.catType + "Car movement")
}

func (p *Car) Stop() {
   fmt.Println(p.catType + "Car stop")
}

// Define BMW car
type BMWCar struct {
   Car
}

func (p *BMWCar) GetCar() {
   if p.money >= 60000 {
      p.Move()
      p.Stop()
   } else {
      fmt.Println("Not enough money to buy a BMW!")
   }
}

// Defining the Audi category
type AudiCar struct {
   Car
}

func (p *AudiCar) GetCar() {
   if p.money >= 70000 {
      p.Move()
      p.Stop()
   } else {
      fmt.Println("Not enough money to buy Audi!")
   }
}

type CarTyper interface {
   GetCar()
}

// 4s stores
type CarStore struct {
}

func (c *CarStore) Order(money float64, carType string) {
   switch carType {
   case "BMW":
      CarWho(&BMWCar{Car{carType, money}})
   case "Audi":
      CarWho(&AudiCar{Car{carType, money}})
   }

}

func CarWho(i CarTyper)  {
   i.GetCar()
}

func main() {
   var carStore CarStore
   carStore.Order(30000, "Audi")
}

In the next chapter, we will talk about other knowledge points of the interface.