当前位置: 首页 > 科技观察

如何使用 Go 语言写出面向对象风格的代码

时间:2023-03-15 15:53:39 科技观察

如何使用Go语言编写面向对象风格的代码转载本文请联系Golang梦工厂公众号。前言!!大家好,我是阿松。上一篇:小白能看懂的context包详解:从入门到精通分析context的源码,我们看到了一种在结构体中嵌入匿名接口的编程方式。这样的写法对于大多数初学Go语言的朋友来说似乎是一头雾水。结构中嵌入匿名接口和匿名结构,实际上是面向对象程序设计中继承和重写的一种实现方式。之前写过java和python。对象编程中的继承和重写应该不陌生,但是转用Go语言后写的代码是面向过程的代码,所以本文就来分析一下如何用Go语言编写面向对象的代码。面向对象程序设计是一种计算机程序设计框架,英文全称:ObjectOrientedProgramming,简称OOP。OOP的一个基本原则是计算机程序由可以作为子例程运行的单个单元或对象组成。OOP实现了软件工程的三个主要目标:可重用性、灵活性和可扩展性。OOP=object+class+inheritance+polymorphism+message,其中核心概念是class和object。在介绍什么是面向对象编程时,这段话经常出现在网络上。大多数学Go语言的朋友应该也是从C++、python、java转过来的,所以对面向对象编程的理解应该是很深的,所以这篇文章就不用介绍概念了,重点是如何使用Go语言实现了面向对象编程的编程风格。Go-like语言本身不是面向对象的编程语言,所以Go语言中没有类的概念,但是它支持类型,所以我们可以使用struct类型来提供类似于java中类的服务,并且可以定义属性、方法和定义构造函数。我们来看一个例子:typeHerostruct{NamestringAgeuint64}funcNewHero()*Hero{return&Hero{Name:"Galen",Age:18,}}func(h*Hero)GetName()string{returnh.Name}func(h*Hero)GetAge()uint64{returnh.Age}funcmain(){h:=NewHero()print(h.GetName())print(h.GetAge())}这是一个简单的“类”使用,这个类的名字是Hero,其中Name和Age是我们定义的属性,GetName和GetAge是我们定义的类的方法,NewHero是定义的构造函数。由于Go语言的特点,构造函数只能由我们手动实现。这里方法的实现依赖于结构体的值接收者和指针接收者的特性。封装封装就是将一个对象的属性私有化,同时提供一些可以被外界访问的属性和方法。如果我们不想被外界访问,我们就不必提供外界访问的方法。我们可以通过两种方式实现Go语言的封装:Go语言支持包级封装,小写字母开头的名字只能在包中的程序中看到,所以如果我们不想暴露一些方法,可以使用这种使它们成为私有包中内容的方法比较简单易懂,就不举例了。Go语言可以通过type关键字来创建新的类型,所以为了不暴露一些属性和方法,我们可以创建一个新的类型,通过自己编写构造函数来实现封装。例如:typeIdCardstringfuncNewIdCard(cardstring)IdCard{returnIdCard(card)}func(iIdCard)GetPlaceOfBirth()string{returnstring(i[:6])}func(iIdCard)GetBirthDay()string{returnstring(i[6:14])}声明了一个新的IdCard类型,本质上是一个字符串类型,NewIdCard用于构造对象,GetPlaceOfBirth和GetBirthDay是封装方法。继承Go没有原生级别的继承支持,但是我们可以使用组合来实现继承,通过在结构体中嵌入类型来实现继承。典型的应用是嵌入式匿名结构类型和嵌入式匿名接口类型。这两种方式有一些细微的区别:嵌入式匿名结构类型:将父结构嵌入到子结构中,子结构具有父结构的属性和方法,但这种方式不支持参数多态。嵌入式匿名接口类型:将接口类型嵌入结构中。该结构默认实现接口的所有方法。该结构也可以重写这些方法。该方法可以支持参数多态性。这里需要注意的一点是,如果嵌入类型没有实现所有的接口方法,就会造成编译时没有检测到的运行时错误。使用嵌入式匿名结构类型实现继承的示例(p*Person)GetAge()uint64{returnp.Age}funccheck(b*Base){b.GetMsg()}funcmain(){m:=Base{Value:"ILoveYou"}p:=&Person{Base:m,Name:"asong",Age:18,}fmt.Print(p.GetName(),"",p.GetAge(),"andsay",p.GetMsg())//check(p)}注释掉上面的方法证明不能进行参数多态。实现嵌入式匿名接口类型继承的例子直接以一个业务场景为例。假设现在我们要向用户发送通知。web和app发送的通知内容是一样的,只是点击后的动作不同,所以我们可以抽象出一个接口OrderChangeNotificationHandler来声明三个公共方法:GenerateMessage、GeneratePhotos、generateUrl,所有的类都会实现这三个方法,因为web和app发送的内容是一样的,所以我们可以提取一个父类OrderChangeNotificationHandlerImpl实现了一个默认方法,然后写了两个子类WebOrderChangeNotificationHandler和AppOrderChangeNotificationHandler继承父类重写了generateUrl方法。如果后面修改了不同端的内容,直接重写父类方法就可以了。来看例子:typePhotosstruct{widthuint64heightuint64valuestring}typeOrderChangeNotificationHandlerinterface{GenerateMessage()stringGeneratePhotos()PhotosgenerateUrl()string}typeOrderChangeNotificationHandlerImplstruct{urlstring}funcNewOrderChangeNotificationHandlerImpl()OrderChangeNotificationChangeNotler{returnOrderingHandler{returnOrderingMessage()stringGeneratePhotos()PhotosgenerateUrl()string}url:"https://base.test.com",}}func(oOrderChangeNotificationHandlerImpl)GenerateMessage()string{return"OrderChangeNotificationHandlerImplGenerateMessage"}func(oOrderChangeNotificationHandlerImpl)GeneratePhotos()Photos{returnPhotos{width:1,height:1,value:"https://www.baidu.com",}}func(wOrderChangeNotificationHandlerImpl)generateUrl()string{returnw.url}typeWebOrderChangeNotificationHandlerstruct{OrderChangeNotificationHandlerurlstring}函数(wWebOrderChangeNotificationHandlerNotification)generateUrl()字符串{returnw.url}typeAppOrderChangeNotificationHandlerstruct{OrderChangeNotificationHandlerurlstring}func(aAppOrderChangeNotificationHandler)generateUrl()string{returna.url}funccheck(handlerOrderChangeNotificationHandler){mainfmt.Print=)nrate}(basehandNewOrderChangeNotificationHandlerImpl()web:=WebOrderChangeNotificationHandler{:base,url:"http://web.test.com",}fmt.Println(web.GenerateMessage())fmt.Println(web.generateUrl())check(web)}因为所有组合都实现了OrderChangeNotificationHandler类型,可以处理任何特定类型以及作为该特定类型的派生类的通配符多态性多态性是面向对象编程的精髓。多态性是分支代码根据类型的具体实现采取不同行为的能力。在Go语言中,任何自定义类型都可以实现任何接口,因此不同的实体类型可以实现接口。value方法的调用是多态的,例如:typeSendEmailinterface{send()}funcSend(sSendEmail){s.send()}typeuserstruct{namestringemailstring}func(u*user)send(){fmt.Println(u.name+"emailis"+u.email+"alreadysend")}typeadminstruct{namestringemailstring}func(a*admin)send(){fmt.Println(a.name+"emailis"+a.email+"alreadysend")}funcmain(){u:=&user{name:"asong",email:"你猜的",}a:=&admin{name:"asong1",email:"我不会告诉你的",}Send(u)Send(a)}总结面向对象编程归根结底是一种编程思想,但是有些语言在语法特性上对这种思想提供了更好的支持。写面向对象的代码比较容易,但是代码还是我们自己写的,不是我们自己写的。Java肯定会写比较抽象的代码。在工作中,我见过无数用Java编写的面向过程的代码,所以无论我们使用什么语言,我们都应该思考如何写出好的代码。大量的抽象接口帮助我们简化了代码。代码很优雅,但也面临着可读性的问题。任何事物都有两个方面。写出好的代码还有很长的路要走,需要我们继续探索……。本文示例代码已上传至github:https://github.com/asong2020/Golang_Dream/tree/master/code_demo/oop