Anonymous composition in Golang implements pseudo inheritance methods

  • 2020-06-15 09:15:02
  • OfStack

"The object-oriented mechanism of Go is different from that of general language 1. It has no class hierarchy, or even classes; Complex objects are built by simply combining (rather than inheriting) simple objects." -- Go Language Bible

1. Anonymous combination

1.1 Anonymous composition definition

In golang, the combined syntax is the introduction of another class in one class, such as


  type Logger struct{
  }
  type Work struct{
    log Logger
  }
  type Work2 struct{
    log *Logger
  }

  func (Logger)Info(v ...interface{}){
  }

As shown in the above code, a variable of type Logger is defined in the Work class, which is a relatively common introduction method. Let's call it non-anonymous combination here. What is anonymous combination?


type Logger struct {
}
type Work struct {
  Logger
}
type Work2 struct {
  *Logger
}

func (Logger) Info(v ...interface{}) {
}

In the above code, both the Work and Work2 classes are combined anonymously with the Logger class. The only difference between the two classes is that Work2 combines the pointer type Logger class.

1.2 Combination object initialization

Non-anonymous combination initialization method


func main(){
  var wk = Work{log:Logger{}}
  var wwk = Work{Logger{}}
  //...and so on

  var wk2 = Work2{log:new(Logger)}
  var wwk2 = Work2{new(Logger)}
  //... and so on
}

Anonymous combination initialization


func main(){
  var wk = Work{Logger{}}
  var wwk = Work{Logger:Logger{}}
  //... and so on
  var wk2 = Work2{new(Logger)}
  var wwk2 = Work2{Logger:&Logger{}}
  //... and so on
}

The above is a common way to initialize anonymous combinations. When combined anonymously, methods and properties of contained classes can be used directly, even if they are private variables.

Notes:

1. When anonymously combining multiple classes, different classes have the same method, will there be conflicts? The answer is, different methods in different classes don't conflict, but when you call this method, you need to specify which method is in that class, and if a method that is anonymously combined into a class conflicts with a method in the body of the class, by default, the method in the body of the class is used.

2. When anonymously combining multiple classes, will the same class name conflict? The answer is yes. Even if the package name is different and the class name is the same, there will be conflicts.

Sample code:


package main
import(
  "bufio"
)
type Reader struct {
}
type Work4 struct {
  Reader
  bufio.Reader
}

When the above code is compiled, Reader is prompted to redefine duplicate field Reader

The reason is that in the anonymous combination, no class name is given to the incoming class, so the default is to use the class name as the attribute name. For example, the object wwk2 above can use wwk2.Info (" hello ") or ES65en2.Logger.Info (" hello ") when calling Logger's Info method.

Below is attached a complete demo code, note that the error will be reported, this code contains the above duplicate field Reader error:


package main

import (
  "bufio"
  "fmt"
)

type Logger struct {
}

type Work struct {
  Logger
}

type Work2 struct {
  *Logger
}
type Work3 struct {
  log *Logger
}

type Reader struct {
}
type Work4 struct {
  Reader
  bufio.Reader
}

func (Logger) Info(v ...interface{}) {
  fmt.Println(v...)
}

func main() {
  var wk = Work{Logger{}}
  wk.Info("hello: Work{Logger{}}")
  var wwk = Work{Logger: Logger{}}
  wwk.Info("hello: Work{Logger: Logger{}}")
  //... and so on
  var wk2 = Work2{new(Logger)}
  wk2.Info("hello: Work2{new(Logger)}")
  var wwk2 = Work2{Logger: &Logger{}}
  wwk2.Info("hello: Work2{Logger: &Logger{}}")
  wwk2.Logger.Info("hello: wwk2.Logger.Info")

  var wk3 = Work3{new(Logger)}
  wk3.log.Info("hello: Work3{new(Logger)}")
}

3. Structure embedded and anonymous members

Go language provides unique structure embedded mechanism, make a structure containing the other one anonymous member structure type, so you can through the simple point operator x. f to access the anonymous members in the chain of nested x. d. e. f members.

The Go language has a feature that lets us declare only the data type corresponding to a member without naming the member. Such members are called anonymous members. The data type of an anonymous member must be a named (rather than anonymous) type or a pointer to a named type.


type Circle struct {
 Point
 Radius int
} 

type Wheel struct {
 Circle
 Spokes int
}

Thanks to the anonymous embedding feature, we can directly access member variables of the embedded type without giving the full path:


var w Wheel
w.X = 8 //  Is equivalent to  w.Circle.Point.X = 8
w.Y = 8 //  Is equivalent to  w.Circle.Point.Y = 8
w.Radius = 5 //  Is equivalent to  w.Circle.Radius = 5
w.Spokes = 20

As a rule, methods that are inline are promoted to methods that are externally typed.

3.1 Anonymous Conflict (duplicate field)

Anonymous members also have an implicit name, with their type name (the unswapped name section) as the name of the member variable. Therefore, you cannot have two anonymous members of the same type at the same level, resulting in name conflicts.


type Logger struct {
  Level int
}

type MyJob struct {
  *Logger
  Name string
  *log.Logger // duplicate field Logger
}

Anonymous composition is not inheritance

4.1 The recipient of the method remains the same

When we embed a type, a method of that type becomes an externally-typed method, but when it is called, the recipient of the method is an internal-type (embedded type), not an external-type. - Effective Go


type Job struct {
 Command string
 *log.Logger
}

func (job *Job)Start() {
 job.Log("starting now...")
 ... //  do 1 Some of the things 
 job.Log("started.")
}

The Job example above, even if the combined method is changed to ES119en. Log(...) , but the recipient of the Log function is still the log.Logger pointer, so it is not possible to access other job member methods and variables in Log.

4.1 Embedded types are not base classes

If readers are familiar with object-oriented languages implemented based on classes, they may be inclined to think of an embedded type as a base class and an external type as a subclass or an inherited class, or an external type as an "is a" inline type. But that's wrong.


type Logger struct {
}
type Work struct {
  Logger
}
type Work2 struct {
  *Logger
}

func (Logger) Info(v ...interface{}) {
}
0

Notice the call to the Distance method in the above example. Distance has one argument of type Point, but q is not an Point class, so even though q has the embedded type Point, we have to select it explicitly. If you try to send q directly you will see an error:


type Logger struct {
}
type Work struct {
  Logger
}
type Work2 struct {
  *Logger
}

func (Logger) Info(v ...interface{}) {
}
1

1 ColoredPoint is not 1 Point, but ColoredPoint "has a" Point, and it has the Distance method introduced from the Point class.

In fact, from an implementation point of view, inline fields instruct the compiler to generate additional wrapping methods to delegate the declared methods, which are equivalent to:


type Logger struct {
}
type Work struct {
  Logger
}
type Work2 struct {
  *Logger
}

func (Logger) Info(v ...interface{}) {
}
2

When ES160en.Distance is called by the wrapper method generated by the compiler above, its sink value is ES162en.Point, not p.

4.3 Anonymous conflicts (duplicate field) and implicit names

Anonymous members also have an implicit name, with their type name (the unswapped name section) as the name of the member variable. Therefore, you cannot have two anonymous members of the same type at the same level, resulting in name conflicts.


type Logger struct {
Level int
}

type MyJob struct {
*Logger
Name string
*log.Logger // duplicate field Logger
}

Both of the following are indirect indications that anonymous composition is not inheritance:

Anonymous members have implicit names Anonymity can conflict (duplicate field)

Related articles: