Object-Oriented Programming
Object-oriented programming in Go language is very different from other languages.
Is Go an object-oriented language?
yes and no . While Go has types and methods, and allows an object-oriented programming style, there is no type hierarchy (inheritance). The "interface" concept in Go offers a different approach that we believe is easier to use and in some ways more general. There are also ways to embed types within other types to provide something similar (but not identical) to subclassing. Furthermore, methods in Go are more general than those in C++ or Java: they can be defined for any type of data, even built-in types such as ordinary "unboxed" integers. They are not limited to structures (classes).
Also, the lack of a type hierarchy makes "objects" in Go feel much more lightweight than "objects" in languages like C++ or Java.
Encapsulating Data and Behavior
Structure definition
Instance creation and initialization
type Employee struct {
Id string
Name string
Age int
}
func TestCreateEmployeeObj(t *testing.T) {
e := Employee{
"0", "Bob", 20}
e1 := Employee{
Name: "Mike", Age: 30}
e2 := new(Employee) // 返回指针
e2.Id = "2"
e2.Name = "Rose"
e2.Age = 22
t.Log(e)
t.Log(e1)
t.Log(e1.Id)
t.Log(e2)
t.Logf("e is %T", e)
t.Logf("e2 is %T", e2)
}
Behavior (method) definition
-
The first
func (e Employee) String() string { fmt.Printf("Address is %x", unsafe.Pointer(&e.Name)) fmt.Println() return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age) } func TestStructOperations(t *testing.T) { e := Employee{ "0", "Bob", 20} fmt.Printf("Address is %x", unsafe.Pointer(&e.Name)) fmt.Println() t.Log(e.String()) }
So this way of writing will have the overhead of copying.
-
the second
func (e *Employee) String() string { fmt.Printf("Address is %x", unsafe.Pointer(&e.Name)) fmt.Println() return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age) } func TestStructOperations(t *testing.T) { e := &Employee{ "0", "Bob", 20} // 传递引用 fmt.Printf("Address is %x", unsafe.Pointer(&e.Name)) fmt.Println() t.Log(e.String()) }
This is more recommended.
Interface (defines the interaction protocol)
Go's interface is very different from that of many mainstream programming languages.
Example of Java code:
Duck Type interface
Interface of Go language:
type Programmer interface {
WriteHelloWorld() string
}
type GoProgrammer struct {
}
func (g *GoProgrammer) WriteHelloWorld() string {
// duck type 鸭子类型
return "Hello World"
}
func TestClient(t *testing.T) {
var p Programmer
p = new(GoProgrammer)
t.Log(p.WriteHelloWorld())
}
Go interface
interface variable
custom type
type IntConv func(op int) int
// 计算函数操作的时长
func timeSpend(inner IntConv) IntConv {
// 以前的方法特别的长 我们可以用自定义类型做替换
// 类似装饰者模式,对原来的函数进行了一层包装
return func(n int) int {
start := time.Now()
ret := inner(n)
fmt.Println("time spend:", time.Since(start).Seconds())
return ret
}
}
Extension and reuse (similar to inheritance)
The Go language cannot naturally support inheritance, but it wants to implement object-oriented features.
That is, the parent class object is initialized with the subclass object, then the function called by the parent class object is the function implemented by the subclass, thus satisfying the LSP (subclass exchange principle).
Case 1 : The Go language supports extending the functions of the parent class, as shown in the following code:
package oriented_test
import (
"fmt"
"testing"
)
// Pet 类
type Pet struct {
}
func (p *Pet) Speak(){
// Pet类的函数成员
fmt.Print("Pet speak.\n")
}
func (p *Pet) SpeakTo(host string) {
// Pet类的函数成员
p.Speak()
fmt.Println("Pet SpeakTo ", host)
}
// Dog 扩展Pet的功能
type Dog struct {
p *Pet
}
// 扩展父类的方法
func (d *Dog) Speak(){
d.p.Speak()
}
// 扩展父类的方法
func (d *Dog) SpeakTo(host string) {
d.Speak()
fmt.Println("Dog Speakto ", host)
}
func TestDog(t *testing.T) {
dog := new(Dog)
dog.SpeakTo("Test dog")
}
The output of the above test code is as follows:
Dog's Speak is called in dog's SpeakTo, which calls Pet's Speak, so the output is normal.
Pet and Dog calls do not affect each other, it is entirely up to the user.
But this is different from what we want, the Pet class has its own method, the Dog class has its own method, and the scope of the two is completely different.
Here, the Go language introduces an anonymous nested type , that is, the Dog class does not need to implement its own method with the same name as the Pet class, and the Pet member can be changed in the declaration of the Dog class.
anonymous nested type
Case 2 : Go language supports anonymous function types
// Dog 扩展Pet的功能
type Dog struct {
Pet
}
In this way, Dog does not need to declare its own function member with the same name, and the default call is the call of the Pet member function.
package oriented_test
import (
"fmt"
"testing"
)
type Pet struct {
}
func (p *Pet) Speak(){
fmt.Print("Pet speak.\n")
}
func (p *Pet) SpeakTo(host string) {
p.Speak()
fmt.Println("Pet SpeakTo ", host)
}
// Dog 扩展Pet的功能
type Dog struct {
Pet // 支持匿名嵌套类型
}
func TestDog(t *testing.T) {
var dog Dog
dog.Speak()
dog.SpeakTo("Test dog")
}
The final output is as follows:
All member functions of Pet are called, which feels like inheritance, because inheritance defaults to the fact that subclasses can use the public members of the parent class.
Under the anonymous nested type, we want to fully try whether the Go language really supports inheritance. We can implement the same-named function of Pet in Dog like the previous code, and can call the member method of the subclass through the parent class object, like C++ /Java does upcasting like this (impossible by itself, Go language does not support explicit typecasting).
Case 3 : Go language does not support inheritance, the following code:
package oriented_test
import (
"fmt"
"testing"
)
type Pet struct {
}
func (p *Pet) Speak(){
fmt.Print("Pet speak.\n")
}
func (p *Pet) SpeakTo(host string) {
p.Speak()
fmt.Println("Pet SpeakTo ", host)
}
// Dog 扩展Pet的功能
type Dog struct {
//p *Pet
Pet // 支持匿名嵌套类型
}
// 重载父类的方法
func (d *Dog) Speak(){
fmt.Print("Dog speak.\n")
}
// 重载父类的方法
func (d *Dog) SpeakTo(host string) {
d.Speak()
fmt.Println("Dog Speakto ", host)
}
func TestDog(t *testing.T) {
var dog Pet = new(Dog) // 这里就会编译错误
dog.Speak()
dog.SpeakTo("Test dog")
}
cannot use new(Dog) (value of type *Dog) as Pet value in variable declaration
不支持将Pet类型转换为Dog类型
To sum up, the Go language does not support inheritance, but can support the extension and reuse of interfaces ** (anonymous nested types) **, this method of embedding cannot be used as inheritance at all, because it does not support access to subclasses The method data (overloading) does not support the LSP principle.
Among them, extension means that different classes implement the same member function, which can realize the extended interface form similar to Case 1.
Reuse is to achieve a function similar to overloading through anonymous nested types. You can see the code of Case 2.
polymorphism
type Programmer interface {
WriteHelloWorld() string
}
type GoProgrammer struct {
}
func (g *GoProgrammer) WriteHelloWorld() string {
return "fmt.Println(\"Hello World\")"
}
type JavaProgrammer struct {
}
func (j *JavaProgrammer) WriteHelloWorld() string {
return "System.out.println(\"Hello World\")"
}
// 多态
func writeFirstProgram(p Programmer) {
fmt.Printf("%T %v\n", p, p.WriteHelloWorld())
}
func TestClient(t *testing.T) {
goProgrammer := new(GoProgrammer)
javaProgrammer := new(JavaProgrammer)
writeFirstProgram(goProgrammer)
writeFirstProgram(javaProgrammer)
}
Empty Interfaces and Assertions
func DoSomething(p interface{
}) {
// '.' 断言,p.(int),p断言为int类型
if i, ok := p.(int); ok {
fmt.Println("Integer", i)
return
}
if s, ok := p.(string); ok {
fmt.Println("string", s)
return
}
fmt.Println("UnKnow type")
}
func TestEmptyInterface(t *testing.T) {
DoSomething(10)
DoSomething("10")
DoSomething(10.00)
}
Go Interface Best Practices
error mechanism
error
package error
import (
"errors"
"testing"
)
func GetFibonacci(n int) ([]int, error) {
if n < 2 || n > 100 {
return nil, errors.New("n should be in [2, 100]")
}
fibList := []int{
1, 1}
for i := 2; i < n; i++ {
fibList = append(fibList, fibList[i-1]+fibList[i-2])
}
return fibList, nil
}
func TestGetFibonacci(t *testing.T) {
if v, err := GetFibonacci(-10); err != nil {
t.Error(err)
} else {
t.Log(v)
}
}
Best Practices
Fail early and avoid nesting!
example:
func GetFibonacci2(str string) {
var (
i int
err error
list []int
)
if i, err = strconv.Atoi(str); err != nil {
fmt.Println("Error", err)
return
}
if list, err = GetFibonacci(i); err != nil {
fmt.Println("Error", err)
return
}
fmt.Println(list)
}
panic
panic vs os.Exit
call back
recover
Similar to java catch
.
func TestRecover(t *testing.T) {
defer func() {
if err := recover(); err != nil {
fmt.Println("recovered from", err)
}
}()
fmt.Println("Start")
panic(errors.New("something wrong"))
}
In fact, the above repair method is very dangerous.
We must be careful what we revocer
are doing, because our revocer does not detect what error occurred, but just records it or ignores it directly. At this time, some core resources of the system may be exhausted, but we force it to recover After that, the system will still not work normally, and our health check program will health check
not be able to detect the current system problems, because many health checks only check whether the current system process is in or not, because our process is in, so it will Formed 僵尸进程
, it is alive, but it cannot provide services.
If this kind of problem occurs, we can use “Let is Crash”
the recoverable design pattern, we crash it directly, so that the daemon process will restart the service process (it’s a bit grand, but it’s actually restarting), restarting is to restore uncertainty The best way to go wrong.
package package
Build reusable modules (packages)
my_series.go
package series
func GetFibonacci(n int) ([]int, error) {
ret := []int{
1, 1}
for i := 2; i < n; i++ {
ret = append(ret, ret[i-1]+ret[i-2])
}
return ret, nil
}
package_test.go
Referencing a method in another package:
package client
import (
"mygoland/geekvideo/ch13/series" // 包路径要从自己的gopath开始写起
"testing"
)
func TestPackage(t *testing.T) {
t.Log(series.GetFibonacci(10))
}
init method
func init() {
fmt.Println("init1")
}
func init() {
fmt.Println("init2")
}
func GetFibonacci(n int) []int {
ret := []int{
1, 1}
for i := 2; i < n; i++ {
ret = append(ret, ret[i-1]+ret[i-2])
}
return ret
}
How to use remote packages
ConcurrentMap for GO
https://github.com/easierway/concurrent_map
go get
Import using the command
go get -u github.com/easierway/concurrent_map
package remote
import (
cm "github.com/easierway/concurrent_map" // 导入远程包
"testing"
)
func TestConcurrentMap(t *testing.T) {
m := cm.CreateConcurrentMap(99)
m.Set(cm.StrKey("key"), 10)
t.Log(m.Get(cm.StrKey("key")))
}
dependency management
Go unresolved dependency issues
vendor path
Commonly used dependency management tools
- godep:https://github.com/tools/godep
- glide:https://github.com/Masterminds/glide
- dep:https://github.com/golang/dep
install glide
-
Mac environment, use brew to install glide
brew install glide
Successful installation
-
Initialize glide
glide init
-
After glide init is executed, a file is generated
yaml
, and the dependent packages and version numbers are defined in it -
Execute in the previous directory
glide install
vender
Then a directory andglide.lock
file will be generated under our specified file . -
So far, Go can search for packages under the vendor directory, and we specify the path and version number of the package through vendor, which means that different versions of the same package can be used in the same environment.
The notes are organized from the Geek Time video tutorial: Go language from entry to actual combat