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