2
头图

Original link: How to use Go language to write object-oriented style code

Preface

Hello, everyone, my name is asong . In the previous article: , a detailed explanation of the context package that Xiaobai can understand: from entry to proficiency analyzing context , we saw a programming method that embeds an anonymous interface in the structure. Most Go language seem awkward. In fact, the embedded anonymous interface and anonymous structure in the structure are an implementation method of inheritance and rewriting in object-oriented programming. I have written java and python You should be familiar with inheritance and rewriting in object-oriented programming, but Go language is procedural-oriented code, so this article will analyze how to write object-oriented code Go

Object-oriented programming is a computer programming architecture, the English full name: Object Oriented Programming, OOP for short. A basic principle of OOP is that a computer program is composed of a single unit or object that can function as a subroutine. OOP achieves the three main goals of software engineering: reusability, flexibility, and scalability. OOP=object+class+inheritance+polymorphism+message, the core concept is class and object.

This passage often appears when introducing object-oriented programming on the Internet. Most Go language should also be transferred from C++ , python , java , so the understanding of object-oriented programming should be very deep, so this article There is no need to introduce the concept, let's focus on how to use the Go language to realize the programming style of object-oriented programming.

kind

Go language itself is not an object-oriented programming language, so Go is no concept of classes in the 0619074bbb9488 language, but it supports types, so we can use the struct type to provide services similar java , which can define attributes and methods , You can also define the constructor. Let's look at an example:

type Hero struct {
    Name string
    Age uint64
}

func NewHero() *Hero {
    return &Hero{
        Name: "盖伦",
        Age: 18,
    }
}

func (h *Hero) GetName() string {
    return h.Name
}

func (h *Hero) GetAge() uint64 {
    return h.Age
}


func main()  {
    h := NewHero()
    print(h.GetName())
    print(h.GetAge())
}

This is a simple "class" use. The class name is Hero , where Name and Age are the attributes we define, GetName and GetAge are the methods of the class we define, and NewHero is the defined constructor. Because Go language, the constructor can only be implemented manually by us.

The realization of the method here depends on the characteristics of the value receiver and pointer receiver of the structure.

Encapsulation

Encapsulation is to privatize the properties of an object, while providing some properties and methods that can be accessed by the outside world. If we don't want to be accessed by the outside world, we don't need to provide methods for outside access. To achieve encapsulation in the Go language, we can use two ways:

  • Go language supports package-level encapsulation. Names starting with lowercase letters can only be visible in the program in the package, so if we don’t want to expose some methods, we can private the contents of the package in this way. This understanding is relatively simple, so we won’t mention it. Example.
  • Go language can type keyword, so in order not to expose some properties and methods, we can create a new type and implement encapsulation by handwriting the constructor by ourselves, for example:
type IdCard string

func NewIdCard(card string) IdCard {
    return IdCard(card)
}

func (i IdCard) GetPlaceOfBirth() string {
    return string(i[:6])
}

func (i IdCard) GetBirthDay() string {
    return string(i[6:14])
}

Declare a new type IdCard , is essentially a string type, NewIdCard used to construct the object,

GetPlaceOfBirth and GetBirthDay are encapsulation methods.

inherit

Go does not have native-level inheritance support, but we can use a combination to achieve inheritance, through the structure of the embedded type to achieve inheritance, typical applications are embedded anonymous structure type and embedded anonymous interface type, these two There is a slight difference in this method:

  • Embedded anonymous structure type: The parent structure is embedded in the substructure. The substructure has the properties and methods of the parent structure, but this method cannot support parameter polymorphism.
  • Embedded anonymous interface type: The interface type is embedded in the structure, which implements all the methods of the interface by default. The structure can also rewrite these methods. This method can support parameter polymorphism. One point to note is that if the embedded type does not implement all interface methods, it will cause an undetected runtime error at compile time.

Example of implementing inheritance of embedded anonymous structure type

type Base struct {
    Value string
}

func (b *Base) GetMsg() string {
    return b.Value
}


type Person struct {
    Base
    Name string
    Age uint64
}

func (p *Person) GetName() string {
    return p.Name
}

func (p *Person) GetAge() uint64 {
    return p.Age
}

func check(b *Base)  {
    b.GetMsg()
}

func main()  {
    m := Base{Value: "I Love You"}
    p := &Person{
        Base: m,
        Name: "asong",
        Age: 18,
    }
    fmt.Print(p.GetName(), "  ", p.GetAge(), " and say ",p.GetMsg())
    //check(p)
}

The method commented out above proves that parameter polymorphism cannot be performed.

Examples of embedded anonymous interface type implementation inheritance

Take a business scenario as an example. Assuming that we now want to send a notification to the user, the web app is the same, but the actions after clicking are different, so we can abstract an interface OrderChangeNotificationHandler To declare three public methods: GenerateMessage , GeneratePhotos , generateUrl , all classes will implement these three methods, because web and app ends is the same, so we can extract a parent class OrderChangeNotificationHandlerImpl to implement a default Method, and then write two subclasses WebOrderChangeNotificationHandler and AppOrderChangeNotificationHandler to inherit the parent class and override the generateUrl method. If the content of different ends is modified later, just rewrite the parent class method directly. Let’s look at the example:

type Photos struct {
    width uint64
    height uint64
    value string
}

type OrderChangeNotificationHandler interface {
    GenerateMessage() string
    GeneratePhotos() Photos
    generateUrl() string
}


type OrderChangeNotificationHandlerImpl struct {
    url string
}

func NewOrderChangeNotificationHandlerImpl() OrderChangeNotificationHandler {
    return OrderChangeNotificationHandlerImpl{
        url: "https://base.test.com",
    }
}

func (o OrderChangeNotificationHandlerImpl) GenerateMessage() string {
    return "OrderChangeNotificationHandlerImpl GenerateMessage"
}

func (o OrderChangeNotificationHandlerImpl) GeneratePhotos() Photos {
    return Photos{
        width: 1,
        height: 1,
        value: "https://www.baidu.com",
    }
}

func (w OrderChangeNotificationHandlerImpl) generateUrl() string {
    return w.url
}

type WebOrderChangeNotificationHandler struct {
    OrderChangeNotificationHandler
    url string
}

func (w WebOrderChangeNotificationHandler) generateUrl() string {
    return w.url
}

type AppOrderChangeNotificationHandler struct {
    OrderChangeNotificationHandler
    url string
}

func (a AppOrderChangeNotificationHandler) generateUrl() string {
    return a.url
}

func check(handler OrderChangeNotificationHandler)  {
    fmt.Println(handler.GenerateMessage())
}

func main()  {
    base := NewOrderChangeNotificationHandlerImpl()
    web := WebOrderChangeNotificationHandler{
        OrderChangeNotificationHandler: base,
        url: "http://web.test.com",
    }
    fmt.Println(web.GenerateMessage())
    fmt.Println(web.generateUrl())

    check(web)
}

Because all combinations implement the OrderChangeNotificationHandler type, it can handle any specific type and wildcards that are derived from that specific type.

Polymorphism

Polymorphism is the essence of object-oriented programming. Polymorphism is the ability of the code to take different behaviors according to the specific realization Go language can implement any interface, so the interface value method through different entity types The call is polymorphism, for example:

type SendEmail interface {
    send()
}

func Send(s SendEmail)  {
    s.send()
}

type user struct {
    name string
    email string
}

func (u *user) send()  {
    fmt.Println(u.name + " email is " + u.email + "already send")
}

type admin struct {
    name string
    email string
}

func (a *admin) send()  {
    fmt.Println(a.name + " email is " + a.email + "already send")
}

func main()  {
    u := &user{
        name: "asong",
        email: "你猜",
    }
    a := &admin{
        name: "asong1",
        email: "就不告诉你",
    }
    Send(u)
    Send(a)
}

Summarize

In the final analysis, object-oriented programming is a kind of programming idea, but some languages provide better support for this idea in terms of grammatical characteristics. It is easier to write object-oriented code, but we still write the code, not what we use After java will definitely write more abstract code. In my work, I see that there are java procedural codes written with 0619074bbb97c7. So no matter what language we use, we should think about how to write a good code, a lot of Abstract interfaces help us streamline the code. The code is elegant, but we also face readability issues. Everything has two sides. The road to writing good code is still a long way, and we need to continue to explore... ........

The sample code in the article has been uploaded github : https://github.com/asong2020/Golang_Dream/tree/master/code_demo/oop

Welcome to pay attention to the public account: Golang DreamWorks


asong
605 声望906 粉丝