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.