How to write object-oriented style code using Go

preface

Hello, everyone, I’m Asong. In the previous article: Xiao Bai can understand the context package: Analysis from entry to the proficient in the context of the source code, we see a programming method, embedded in the structure of anonymous interface, this kind of method for most beginners Go language friend looks meng force, actually embedded in the structure of anonymous interface, anonymous structure is inheritance in object-oriented programming and rewrite a way of implementation, I have written Java and Python before and should be familiar with inheritance and rewriting in object-oriented programming, but the code written after the Go language is procedural code, so this article will analyze how to write object-oriented code in Go language.

Object Oriented Programming (OOP) is a computer Programming architecture. One of the basic principles of OOP is that a computer program is composed of a single unit or object that can act as a subroutine. OOP achieves the three main goals of software engineering: reusability, flexibility and extensibility. OOP= Object + class + inheritance + polymorphic + message, where the core concepts are classes and objects.

Most of my friends who have learned Go have learned it from C++, python and Java, so I have a very deep understanding of object-oriented programming. There is no need to introduce concepts in this article. Let’s focus on how to implement the object-oriented programming style using the Go language.

class

The Go language itself is not an object-oriented programming language, so there is no concept of classes in the Go language, but it does support types, so we can use struct types to provide services similar to classes in Java, defining properties, methods, and constructors. Here’s an example:

type Hero struct {
	Name string
	Age uint64
}

func NewHero(a) *Hero {
	return &Hero{
		Name: "Galen",
		Age: 18,}}func (h *Hero) GetName(a) string {
	return h.Name
}

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


func main(a)  {
	h := NewHero()
	print(h.GetName())
	print(h.GetAge())
}
Copy the code

This is a simple “class”. The class Name is Hero, where Name and Age are the properties we define, GetName and GetAge are the methods of the class we define, and NewHero is the constructor of the definition. Because of the nature of the Go language, the constructor can only be implemented manually.

The implementation of this method depends on the properties of the value receiver and pointer receiver of the structure.

encapsulation

Encapsulation is the privatization of an object’s properties and the provision of properties and methods that can be accessed by the outside world. If we do not want to be accessed by the outside world, we do not need to provide methods to the outside world. We can implement encapsulation in the Go language in two ways:

  • GoThe language supports package-level encapsulation, and names starting with lowercase letters are visible only in the programs in the package, so we can use this method to private the contents of the package without exposing methods.
  • GoLanguage can pass throughtypeKeyword to create a new type, so we do not expose some attributes and methods, we can create a new type, our own handwriting constructor way to achieve encapsulation, for example:
type IdCard string

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

func (i IdCard) GetPlaceOfBirth(a) string {
	return string(i[:6])}func (i IdCard) GetBirthDay(a) string {
	return string(i[6:14])}Copy the code

Declare a new type IdCard, which is essentially a string, NewIdCard is used to construct objects,

GetPlaceOfBirth and GetBirthDay are encapsulation methods.

inheritance

Go does not have natively level inheritance support, but we can implement inheritance in a combinative manner, using inline types within structures. Typical applications are inline anonymous structure types and inline anonymous interface types. The two approaches are slightly different:

  • Embedded anonymous structure type: A parent structure is embedded into a child structure that has the attributes and methods of the parent, but does not support parameter polymorphism.
  • Inline anonymous interface type: to embed the interface type structure, the structure of the default implementation of the interface of all the methods, the structure of these methods can also be rewritten, this approach can support parameters polymorphism, a point to note here is that if the embedded type does not implement all the interface methods, can cause the operation of the undetected error at compile time.

An example of an inline anonymous structure type that implements inheritance

type Base struct {
	Value string
}

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


type Person struct {
	Base
	Name string
	Age uint64
}

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

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

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

func main(a)  {
	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)
}
Copy the code

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

Examples of inline anonymous interface type implementation inheritance

Take a business scenario as an example. Suppose we want to send a notification to the user. The notification content sent by the Web and app are the same, but the action after clicking is different. So we can abstract a interface OrderChangeNotificationHandler to declare the three common methods: GenerateMessage, GeneratePhotos, generateUrl, all classes implement these three methods, because the content sent by the Web and the app is the same, So we can be smoked out of a parent class OrderChangeNotificationHandlerImpl to implement a default method, Then write two subclasses WebOrderChangeNotificationHandler, AppOrderChangeNotificationHandler to inherit the parent class to rewrite the generateUrl method can, if behind the different content, modification, Override the parent method directly, for 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(a) OrderChangeNotificationHandler {
	return OrderChangeNotificationHandlerImpl{
		url: "https://base.test.com",}}func (o OrderChangeNotificationHandlerImpl) GenerateMessage(a) string {
	return "OrderChangeNotificationHandlerImpl GenerateMessage"
}

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

type WebOrderChangeNotificationHandler struct {
	OrderChangeNotificationHandler
	url string
}

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

type AppOrderChangeNotificationHandler struct {
	OrderChangeNotificationHandler
	url string
}

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

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

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

	check(web)
}
Copy the code

Because all combined to realize the OrderChangeNotificationHandler type, so that can handle any specific types as well as the specific types of derived class wildcards.

polymorphism

Polymorphism is the essence of object-oriented programming. Polymorphism is the ability of support code to take different behaviors according to the specific implementation of the type. In Go, any user-defined type can implement any interface, so the invocation of interface value methods through different entity types is polymorphism.

type SendEmail interface {
	send()
}

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

type user struct {
	name string
	email string
}

func (u *user) send(a)  {
	fmt.Println(u.name + " email is " + u.email + "already send")}type admin struct {
	name string
	email string
}

func (a *admin) send(a)  {
	fmt.Println(a.name + " email is " + a.email + "already send")}func main(a)  {
	u := &user{
		name: "asong",
		email: "Guess",
	}
	a := &admin{
		name: "asong1",
		email: "I won't tell you.",
	}
	Send(u)
	Send(a)
}
Copy the code

conclusion

After all, object-oriented programming is a programming idea, but some languages support it better in terms of syntactic features. It’s easier to write object-oriented code, but it’s still us. It’s not that we write more abstract code because we use Java. I see at work in Java write for procedural code of its number, so no matter what language, we should be thinking about how to write a code, a lot of abstract interface helps us simplify code, the code is elegant, but also faced with the problem of readability, nothing is every coin has two sides, there is still a long way to write good code, Still need to explore………… .

The sample code has been uploaded to github: github.com/asong2020/G…

Welcome to the public account: Golang Dream Factory