如何使用 Go 語(yǔ)言寫(xiě)出面向?qū)ο箫L(fēng)格的代碼
文末本文轉(zhuǎn)載自微信公眾號(hào)「Golang夢(mèng)工廠」,作者Golang夢(mèng)工廠。轉(zhuǎn)載本文請(qǐng)聯(lián)系Golang夢(mèng)工廠公眾號(hào)。
前言
!! 哈嘍,大家好,我是asong。在上一篇文章:小白也能看懂的context包詳解:從入門(mén)到精通 分析context的源碼時(shí),我們看到了一種編程方法,在結(jié)構(gòu)體里內(nèi)嵌匿名接口,這種寫(xiě)法對(duì)于大多數(shù)初學(xué)Go語(yǔ)言的朋友看起來(lái)是懵逼的,其實(shí)在結(jié)構(gòu)體里內(nèi)嵌匿名接口、匿名結(jié)構(gòu)體都是在面向?qū)ο缶幊讨欣^承和重寫(xiě)的一種實(shí)現(xiàn)方式,之前寫(xiě)過(guò)java、python對(duì)面向?qū)ο缶幊讨械睦^承和重寫(xiě)應(yīng)該很熟悉,但是轉(zhuǎn)Go語(yǔ)言后寫(xiě)出的代碼都是面向過(guò)程式的代碼,所以本文就一起來(lái)分析一下如何在Go語(yǔ)言中寫(xiě)出面向?qū)ο蟮拇a。
面向?qū)ο蟪绦蛟O(shè)計(jì)是一種計(jì)算機(jī)編程架構(gòu),英文全稱(chēng):Object Oriented Programming,簡(jiǎn)稱(chēng)OOP。OOP的一條基本原則是計(jì)算機(jī)程序由單個(gè)能夠起到子程序作用的單元或?qū)ο蠼M合而成,OOP達(dá)到了軟件工程的三個(gè)主要目標(biāo):重用性、靈活性和擴(kuò)展性。OOP=對(duì)象+類(lèi)+繼承+多態(tài)+消息,其中核心概念就是類(lèi)和對(duì)象。
這一段話在網(wǎng)上介紹什么是面向?qū)ο缶幊虝r(shí)經(jīng)常出現(xiàn),大多數(shù)學(xué)習(xí)Go語(yǔ)言的朋友應(yīng)該也都是從C++、python、java轉(zhuǎn)過(guò)來(lái)的,所以對(duì)面向?qū)ο缶幊痰睦斫鈶?yīng)該很深了,所以本文就沒(méi)必要介紹概念了,重點(diǎn)來(lái)看一下如何使用Go語(yǔ)言來(lái)實(shí)現(xiàn)面向?qū)ο缶幊痰木幊田L(fēng)格。
類(lèi)
Go語(yǔ)言本身就不是一個(gè)面向?qū)ο蟮木幊陶Z(yǔ)言,所以Go語(yǔ)言中沒(méi)有類(lèi)的概念,但是他是支持類(lèi)型的,因此我們可以使用struct類(lèi)型來(lái)提供類(lèi)似于java中的類(lèi)的服務(wù),可以定義屬性、方法、還能定義構(gòu)造器。來(lái)看個(gè)例子:
- 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())
 - }
 
這就一個(gè)簡(jiǎn)單的 "類(lèi)"的使用,這個(gè)類(lèi)名就是Hero,其中Name、Age就是我們定義的屬性,GetName、GetAge這兩個(gè)就是我們定義的類(lèi)的方法,NewHero就是定義的構(gòu)造器。因?yàn)镚o語(yǔ)言的特性問(wèn)題,構(gòu)造器只能夠依靠我們手動(dòng)來(lái)實(shí)現(xiàn)。
這里方法的實(shí)現(xiàn)是依賴于結(jié)構(gòu)體的值接收者、指針接收者的特性來(lái)實(shí)現(xiàn)的。
封裝
封裝是把一個(gè)對(duì)象的屬性私有化,同時(shí)提供一些可以被外界訪問(wèn)的屬性和方法,如果不想被外界訪問(wèn),我們大可不必提供方法給外界訪問(wèn)。在Go語(yǔ)言中實(shí)現(xiàn)封裝我們可以采用兩種方式:
Go語(yǔ)言支持包級(jí)別的封裝,小寫(xiě)字母開(kāi)頭的名稱(chēng)只能在該包內(nèi)程序中可見(jiàn),所以我們?nèi)绻幌氡┞兑恍┓椒?,可以通過(guò)這種方式私有包中的內(nèi)容,這個(gè)理解比較簡(jiǎn)單,就不舉例子了。
Go語(yǔ)言可以通過(guò) type 關(guān)鍵字創(chuàng)建新的類(lèi)型,所以我們?yōu)榱瞬槐┞兑恍傩院头椒?,可以采用?chuàng)建一個(gè)新類(lèi)型的方式,自己手寫(xiě)構(gòu)造器的方式實(shí)現(xiàn)封裝,舉個(gè)例子:
- 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])
 - }
 
聲明一個(gè)新類(lèi)型IdCard,本質(zhì)是一個(gè)string類(lèi)型,NewIdCard用來(lái)構(gòu)造對(duì)象,
GetPlaceOfBirth、GetBirthDay就是封裝的方法。
繼承
Go并沒(méi)有原生級(jí)別的繼承支持,不過(guò)我們可以使用組合的方式來(lái)實(shí)現(xiàn)繼承,通過(guò)結(jié)構(gòu)體內(nèi)嵌類(lèi)型的方式實(shí)現(xiàn)繼承,典型的應(yīng)用是內(nèi)嵌匿名結(jié)構(gòu)體類(lèi)型和內(nèi)嵌匿名接口類(lèi)型,這兩種方式還有點(diǎn)細(xì)微差別:
- 內(nèi)嵌匿名結(jié)構(gòu)體類(lèi)型:將父結(jié)構(gòu)體嵌入到子結(jié)構(gòu)體中,子結(jié)構(gòu)體擁有父結(jié)構(gòu)體的屬性和方法,但是這種方式不能支持參數(shù)多態(tài)。
 - 內(nèi)嵌匿名接口類(lèi)型:將接口類(lèi)型嵌入到結(jié)構(gòu)體中,該結(jié)構(gòu)體默認(rèn)實(shí)現(xiàn)了該接口的所有方法,該結(jié)構(gòu)體也可以對(duì)這些方法進(jìn)行重寫(xiě),這種方式可以支持參數(shù)多態(tài),這里要注意一個(gè)點(diǎn)是如果嵌入類(lèi)型沒(méi)有實(shí)現(xiàn)所有接口方法,會(huì)引起編譯時(shí)未被發(fā)現(xiàn)的運(yùn)行錯(cuò)誤。
 
內(nèi)嵌匿名結(jié)構(gòu)體類(lèi)型實(shí)現(xiàn)繼承的例子
- 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)
 - }
 
上面注釋掉的方法就證明了不能進(jìn)行參數(shù)多態(tài)。
內(nèi)嵌匿名接口類(lèi)型實(shí)現(xiàn)繼承的例子
直接拿一個(gè)業(yè)務(wù)場(chǎng)景舉例子,假設(shè)現(xiàn)在我們現(xiàn)在要給用戶發(fā)一個(gè)通知,web、app端發(fā)送的通知內(nèi)容都是一樣的,但是點(diǎn)擊后的動(dòng)作是不一樣的,所以我們可以進(jìn)行抽象一個(gè)接口OrderChangeNotificationHandler來(lái)聲明出三個(gè)公共方法:GenerateMessage、GeneratePhotos、generateUrl,所有類(lèi)都會(huì)實(shí)現(xiàn)這三個(gè)方法,因?yàn)閣eb、app端發(fā)送的內(nèi)容是一樣的,所以我們可以抽相出一個(gè)父類(lèi)OrderChangeNotificationHandlerImpl來(lái)實(shí)現(xiàn)一個(gè)默認(rèn)的方法,然后在寫(xiě)兩個(gè)子類(lèi)WebOrderChangeNotificationHandler、AppOrderChangeNotificationHandler去繼承父類(lèi)重寫(xiě)generateUrl方法即可,后面如果不同端的內(nèi)容有做修改,直接重寫(xiě)父類(lèi)方法就可以了,來(lái)看例子:
- 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)
 - }
 
因?yàn)樗薪M合都實(shí)現(xiàn)了OrderChangeNotificationHandler類(lèi)型,所以可以處理任何特定類(lèi)型以及是該特定類(lèi)型的派生類(lèi)的通配符。
多態(tài)
多態(tài)是面向?qū)ο缶幊痰谋举|(zhì),多態(tài)是支代碼可以根據(jù)類(lèi)型的具體實(shí)現(xiàn)采取不同行為的能力,在Go語(yǔ)言中任何用戶定義的類(lèi)型都可以實(shí)現(xiàn)任何接口,所以通過(guò)不同實(shí)體類(lèi)型對(duì)接口值方法的調(diào)用就是多態(tài),舉個(gè)例子:
- 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)
 - }
 
總結(jié)
歸根結(jié)底面向?qū)ο缶幊叹褪且环N編程思想,只不過(guò)有些語(yǔ)言在語(yǔ)法特性方面更好的為這種思想提供了支持,寫(xiě)出面向?qū)ο蟮拇a更容易,但是寫(xiě)代碼的還是我們自己,并不是我們用了java就一定會(huì)寫(xiě)出更抽象的代碼,在工作中我看到用java寫(xiě)出面向過(guò)程式的代碼不勝其數(shù),所以無(wú)論用什么語(yǔ)言,我們都應(yīng)該思考如何寫(xiě)好一份代碼,大量的抽象接口幫助我們精簡(jiǎn)代碼,代碼是優(yōu)雅了,但也會(huì)面臨著可讀性的問(wèn)題,什么事都是有兩面性的,寫(xiě)出好代碼的路還很長(zhǎng),還需要不斷探索............。
文中示例代碼已經(jīng)上傳github:https://github.com/asong2020/Golang_Dream/tree/master/code_demo/oop















 
 
 






 
 
 
 