Go object oriented - type assertion

25.Go object oriented - type assertion

9 type assertion

We know that any type of value can be stored in the variable of interface (this type implements interface).

So how do we know which type of object is actually saved in this variable?

At present, there are two methods commonly used:

Comma-ok Assert

Go language has a syntax that can directly judge whether it is a variable of this type:

value, ok = element.(T)

Here, value is the value of the variable, ok is a bool type, element is an interface variable, and T is the type of assertion.

If the value of type T is indeed stored in element, ok returns true, otherwise false

Specific cases are as follows:

package main

import "fmt"

type Student struct {
   name string
   id   int
}

func main() {
   i := make([]interface{}, 3) // Define interface array
   i[0] = 1                    //int
   i[1] = "hello go"           //string
   i[2] = Student{"mike", 666} //Student

   //Type query, type assertion
   //The first returns the subscript and the second returns the value corresponding to the subscript. The data are i[0],i[1],i[2] respectively
   for index, data := range i{
      // The first returns the value, and the second returns the true or false judgment result
      if value, ok := data.(int); ok == true {
         fmt.Printf("x[%d] Type is int, The content is%s\n", index, value)
      } else if value, ok := data.(string); ok == true {
         fmt.Printf("x[%d] Type is string, The content is%s\n", index, value)
      } else if value, ok := data.(Student); ok == true {
         fmt.Printf("x[%d] Type is Student, The content is%s\n", index, value.name)
      }
   }

}

The implementation is as follows:

x[0] Type is int, The content is%!s(int=1)
x[1] Type is string, The content is hello go
x[2] Type is Student, The content is mike

Type assertion is judged by switch

Change the if else judgment in the above case to switch mode:

package main

import "fmt"

type Student struct {
   name string
   id   int
}

func main() {
   i := make([]interface{}, 3) // Define interface array
   i[0] = 1                    //int
   i[1] = "hello go"           //string
   i[2] = Student{"mike", 666} //Student

   //Type query, type assertion
   //The first returns the subscript and the second returns the value corresponding to the subscript. The data are i[0],i[1],i[2] respectively
   for index, data := range i{
      switch value := data.(type) {
      case int:
         fmt.Printf("x[%d] Type is int, The content is%d\n", index, value)
      case string:
         fmt.Printf("x[%d] Type is string, The content is%s\n", index, value)
      case Student:
         fmt.Printf("x[%d] Type is Student, The content is%s\n", index, value.name)
      }
   }
}

Calculator case

Now that we have explained the basic syntax of null interface and type assertion, how should we apply this knowledge in practical development? Next, we will write the calculator case we wrote earlier, combined with empty interface and type assertion.

The specific implementation is as follows:

1 define the parent class (structure) and complete the public member definition

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

// Additive class, inheriting parent class
type Add struct {
   Operation
} 

// Subtraction class, inheriting parent class
type Subtraction struct {
   Operation
} 

Now the parent class has been defined, and the addition class and subtraction class have been defined to inherit the parent class

2 define interfaces

// Define the interface of the calculator
type CalcSuper interface {
   SetData(data ...interface{}) // Validation data
   CalcOperate() float64
}  

The difference between the definition of this interface and the interface we defined earlier is that here we add a method SetData(), which is mainly used to verify the passed data. For example, if we require to operate on data of float64 type, only decimal can be passed. If the passed data is of int type, Then the corresponding error prompt will be given. The parameters of this method are: indefinite parameters and empty interfaces, indicating that various types of data can be transferred.

3 implementation interface

The following are the methods declared in the corresponding interface implemented by the addition class.

Implement the SetData() method

// Data verification of addition class
func (a *Add)  SetData(data ...interface{})  {
   if len(data) != 2 {
      fmt.Println("error,Need two parameters")
      return
   }
   if _,ok := data[0].(float64); !ok{
      fmt.Println("error,Need float64 parameters")
      return
   }
   if _,ok := data[1].(float64); !ok{
      fmt.Println("error,Need float64 parameters")
      return
   }
   a.numA, _ = data[0].(float64)
   a.numB, _ = data[0].(float64)
} 

In the modification method, first verify the length of the passed data, and then verify the type.

Implement the CalcOperate() method

// Implement addition of addition class
func (a *Add) CalcOperate()  float64{
   return a.numA + a.numB
}

Similarly, the implementation of subtraction class is as follows:

// Data verification of subtraction class
func (a *Subtraction)  SetData(data ...interface{})  {
   if len(data) != 2 {
      fmt.Println("error,Need two parameters")
      return
   }
   if _,ok := data[0].(float64); !ok{
      fmt.Println("error,Need float64 parameters")
      return
   }
   if _,ok := data[1].(float64); !ok{
      fmt.Println("error,Need float64 parameters")
      return
   }
   a.numA, _ = data[0].(float64)
   a.numB, _ = data[0].(float64)
}

// Implement addition of addition class
func (a *Subtraction) CalcOperate()  float64{
   return a.numA - a.numB
}

4 encapsulation of object creation

In order to create addition and subtraction objects in the main() function, the creation of objects is encapsulated.

To solve this problem, we previously defined an OperationFactory class (structure), and created a CreateOption() method for this class. This method completes the object creation, and the type returned by this method is a float64, representing the operation result. But what should I do if I want to return an object? The returned type can be changed to interface type. Because both addition and subtraction classes implement this interface, the definition is as follows:

// Calculation factory class
type CalcFactory struct {
   
}

func (f *CalcFactory) CreateOperate(opType string) CalcSuper {
   
}

5. Improve the CreateOperate() method

This method mainly judges according to the passed parameter opType to create different objects.

In order not to make the code of the modified method too large and complex, we put the creation of objects in different methods separately. As follows:

// Create an Add object and return the pointer type
func NewAdd() *Add {
   instance := new(Add)
   return instance
}

// Create a Subtraction object and return the pointer type
func NewSubtraction() *Subtraction {
   instance := new(Subtraction)
   return instance
}

Next, call the above two methods in the CreateOperate() method, as shown below:

func (f *CalcFactory) CreateOperate(opType string) CalcSuper {
   var op CalcSuper
   switch opType {
   case "+":
      op = NewAdd()
   case "-":
      op = NewSubtraction()
   default:
      panic("error! don't has this operate")
   }
   return op
}

6 complete the call in the main() function

(1) First, complete the creation of CalcFactory class object, which is also encapsulated in a method.

// Creation of CalcFactory objects
func NewCalcFactory() *CalcFactory {
   instance := new(CalcFactory)
   return instance
}

(2) Complete the call of NewCalcFactory() method in the main() function to obtain the object of CalcFactory

// Get factory
factory := NewCalcFactory()

(3) Complete subsequent method calls

op := factory.CreateOperate("+")
op.SetData(1.5, 2.0)
fmt.Println(op.CalcOperate())

op = factory.CreateOperate("-")
op.SetData(1.5, 2.0)
fmt.Println(op.CalcOperate())

In this program, we should experience and summarize the differences from the previous program.

The complete code is as follows:

package main

import "fmt"

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

// Additive class, inheriting parent class
type Add struct {
   Operation
}

// Create an Add object and return the pointer type
func NewAdd() *Add {
   instance := new(Add)
   return instance
}

// Data verification of addition class
func (a *Add) SetData(data ...interface{}) {
   if len(data) != 2 {
      fmt.Println("error,Need two parameters")
      return
   }
   if _, ok := data[0].(float64); !ok {
      fmt.Println("error,Need float64 parameters")
      return
   }
   if _, ok := data[1].(float64); !ok {
      fmt.Println("error,Need float64 parameters")
      return
   }
   a.numA, _ = data[0].(float64)
   a.numB, _ = data[0].(float64)
}

// Implement addition of addition class
func (a *Add) CalcOperate() float64 {
   return a.numA + a.numB
}

// Subtraction class, inheriting parent class
type Subtraction struct {
   Operation
}

// Create a Subtraction object and return the pointer type
func NewSubtraction() *Subtraction {
   instance := new(Subtraction)
   return instance
}

// Data verification of subtraction class
func (a *Subtraction) SetData(data ...interface{}) {
   if len(data) != 2 {
      fmt.Println("error,Need two parameters")
      return
   }
   if _, ok := data[0].(float64); !ok {
      fmt.Println("error,Need float64 parameters")
      return
   }
   if _, ok := data[1].(float64); !ok {
      fmt.Println("error,Need float64 parameters")
      return
   }
   a.numA, _ = data[0].(float64)
   a.numB, _ = data[0].(float64)
}

// Implement subtraction of subtraction class
func (a *Subtraction) CalcOperate() float64 {
   return a.numA - a.numB
}

// Define the interface of the calculator
type CalcSuper interface {
   SetData(data ...interface{}) // Validation data
   CalcOperate() float64
}

// Calculation factory class
type CalcFactory struct {
}

// Creation of CalcFactory objects
func NewCalcFactory() *CalcFactory {
   instance := new(CalcFactory)
   return instance
}

// Use the factory class to create objects for subtraction and subtraction
func (f *CalcFactory) CreateOperate(opType string) CalcSuper {
   var op CalcSuper
   switch opType {
   case "+":
      op = NewAdd()
   case "-":
      op = NewSubtraction()
   default:
      panic("error! don't has this operate")
   }
   return op
}

func main() {
   // Get factory
   factory := NewCalcFactory()

   op := factory.CreateOperate("+")
   op.SetData(1.5, 2.0)
   fmt.Println(op.CalcOperate())

   op = factory.CreateOperate("-")
   op.SetData(1.5, 2.0)
   fmt.Println(op.CalcOperate())
}

In object-oriented programming, focus on understanding the idea of object-oriented programming, and be able to understand the application of inheritance, encapsulation and polymorphism.

Added by tweek on Mon, 17 Jan 2022 08:24:27 +0200