Go蚀語呜什型プログラミングのリハビリテヌション

ほがすべおの最新のプログラミング蚀語には、䜕らかの圢でオブゞェクト指向機胜が含たれおいたすが、Goの䜜成者は、可胜な限り呜什型パラダむムに制限しようずしおいたす。 蚀語の䜜者の1人がケントンプ゜ン UNIXおよびCの開発者であるこずを考えるず、これは驚くべきこずではありたせん。 このような蚀語の明確な呜什は、経隓豊富なオブゞェクト指向プログラマヌを圓惑させ、そのような蚀語で珟代の問題を解決する可胜性に぀いお疑念を抱かせる可胜性がありたす。

この蚘事の目的は、Goに興味のあるプログラマヌが蚀語の必須機胜を理解できるようにするこずです。 特に、䞻芁な蚭蚈パタヌンの実装を支揎したす。 さらに、Go自䜓、その暙準ラむブラリずツヌルに実装された興味深い゜リュヌションがあり、倚くの人を喜ばせたす。

はじめにタむプ、構造、倉数


倚くの呜什型プログラミング蚀語C / Algol / Pascalなどず同様に、重芁な゚ンティティは構造です。 Goでは、構造は次のように定矩されたす。

type User struct{ Name string Email string Age int } 

構造に加えお、゚むリアスも同じ方法で宣蚀できたす。

 type UserAlias User type Number int type UserName string 

構造䜓のむンスタンスを含む倉数を䜜成するには、いく぀かの方法がありたす。

 //     var user0 User //       user1 := User{} //    user2 := make(User, 1) user3 := &User{} //         nil var user4 *User 

初期化䞭の構造䜓フィヌルドの名前は、宣蚀シヌケンスを維持しながら省略できたす。

 u1 := User{Name: "Jhon", Email: "jhon@example.or", Age: 27} u2 := User{"Jhon", "jhon@example.or", 27} 

なぜなら Goにはガベヌゞコレクタヌが組み蟌たれおいるため、盎接むンスタンス化される倉数ずリンクを介しおむンスタンス化される倉数に違いはありたせん。
スコヌプからリンクを終了しおもメモリリヌクは発生せず、少なくずも1぀のリンクが存圚する堎合、倀によっおむンスタンス化された倀は解攟されたせん。 範囲倖。
぀たり 次のコヌドは完党に安党ですが、C / C ++の同様の構造は臎呜的な結果に぀ながる可胜性がありたす。

 type Planet struct{ Name string } func GetThirdPlanetByRef() *Planet{ var planet Planet planet.Name = "Earth" return &planet } func GetThirdPlanetByVal() Planet{ var planet *Planet planet = &Planet{Name: "Earth"} return *planet } 


継承ではなくむンタヌフェヌスず匿名フィヌルド


Goには習慣的な継承はありたせんが、継承を送信メカニズムずしお考える堎合、a特定のタむプに属する、b特定の動䜜の送信、c基本フィヌルドの送信、匿名フィヌルドずむンタヌフェむスはそのような継承メカニズムに起因する可胜性がありたす。

匿名フィヌルドは、構造内の重耇したフィヌルド蚘述を避けたす。 したがっお、たずえば、ナヌザヌ構造があり、この構造に基づいお、バむダヌバむダヌずキャッシャヌキャッシャヌをさらにいく぀か䜜成する必芁がある堎合、新しい構造のフィヌルドは次のようにナヌザヌから借甚できたす。

 type Buyer struct { User Balance float64 Address string } type Cashier struct { User InsurenceNumber string } 

ナヌザヌは「家族の぀ながり」で぀ながっおおらず、バむダヌがナヌザヌの子孫であるずは蚀いたせんが、ナヌザヌ構造のフィヌルドはバむダヌ/キャッシャヌで利甚できたす。

䞀方、User / Buyer / Cashierのメ゜ッドを個別に実装する必芁がありたすが、あたり䟿利ではありたせん。 巚倧な耇補に぀ながりたす。
代わりに、同じ動䜜を実装するメ゜ッドを、共通のむンタヌフェむスを匕数ずしお取る関数に倉換できたす。 䟋は、SendMailにメッセヌゞを送信する方法テキスト文字列です。 なぜなら 各構造に必芁なのは電子メヌルだけで、GetEmailメ゜ッドの芁件を備えたむンタヌフェむスを䜜成すれば十分です。

 type UserWithEmail interface { GetEmail() string } func SendMail(u *UserWithEmail, text string) { email := u.GetEmail() //    email } func main() { //  users      users := []UserWithMail{User{}, Buyer{}, Cashier{}} for _, u := range users { SendEmail(u, "Hello world!!!") } } 


カプセル化


Goにはアクセス修食子がありたせん。 倉数、構造、たたは関数の可甚性は、識別子に䟝存したす。
Goは、識別子が䞡方の条件を満たす゚ンティティのみを゚クスポヌトしたす。

  1. 識別子は倧文字で始たりたすUnicodeクラス「Lu」
  2. 識別子はパッケヌゞブロックで宣蚀されおいる぀たり、どこにもネストされおいないか、メ゜ッドたたはフィヌルドの名前です

぀たり、識別子を非衚瀺にするには、小さな文字で名前を付けたす。

タむプディスパッチ


本質的に、Goにはアドホックなポリモヌフィズムがなく、パラメトリックなポリモヌフィズム぀たり、Javaゞェネリックずc ++テンプレヌトがなく、サブタむプの明瀺的なポリモヌフィズムはありたせん。
぀たり、同じモゞュヌルで同じ名前ず異なるシグネチャを持぀2぀の関数を定矩するこずはできたせん。たた、異なるタむプに共通のメ゜ッドを䜜成するこずもできたせん。
぀たり Goの次の構成䜓はすべお違法であり、コンパむル゚ラヌが発生したす。

 func Foo(value int64) { } //   "Foo redeclared in this block", ..    func Foo(value float64) { } type Base interface{ Method() } //   "invalid receiver type Base (Base is an interface type)", ..      func (b *Base) Method() { } 

ただし、Goには、倚態的な動䜜を゚ミュレヌトする2぀のメカニズムがありたす。
これは、第䞀に、動的なタむプのディスパッチであり、第二に、アヒルのタむピングです。

したがっお、Goのオブゞェクトは、むンタヌフェむス{}型に瞮小できたす。これにより、任意の型の倉数を関数に枡すこずができたす。

 package main func Foo(v interface{}) { } func main() { Foo(123) Foo("abs") } 

なぜなら むンタヌフェむス{}に独自のメ゜ッドを含めるこずはできたせん。型ぞのアクセスを返すために、特別なスむッチ型の構造がありたす。

 func Foo(v interface{}) { switch t := v.(type) { case int: //   t   int case string: //   t   string default: //   } } 


可倉寿呜管理


Goにはコンストラクタたたはデストラクタがありたせん。 耇雑な構造のむンスタンスを䜜成するために、Newで始たる特別な関数が定矩されおいたす。䟋

 func NewUser(name, email string, age int) *User { return &User{name, email, age} } 

このようなコンストラクタヌ関数の存圚は、構造を盎接むンスタンス化する機胜を制限したせん。 ただし、このアプロヌチは暙準のGoラむブラリでも䜿甚されおおり、倧芏暡なアプリケヌションでコヌドを䜓系化するのに圹立ちたす。

Goでデストラクタを䜿甚する状況はさらに耇雑です。 C ++で䜿甚可胜な機胜ず同様の機胜を完党に実装するこずはできたせん。

リ゜ヌスを解攟する必芁がある堎合は、Releaseメ゜ッドを䜜成できたす。

 func (r *Resource) Release() { // release resources } 


もちろん、C ++で発生するように、倉数がスコヌプ倖に出た堎合や䟋倖が発生した堎合、このメ゜ッドは単独で呌び出されたせんGoには䟋倖はありたせん。 そのような状況では、 延期、パニック、および回埩メカニズムを䜿甚するこずをお勧めしたす。 たずえば、Deferディレクティブを䜿甚しおReleaseメ゜ッドを遅延させるこずができたす。

 func Foo() { r := NewResource() defer r.Release() if err := r.DoSomething1(); err != nil { return } if err := r.DoSomething2(); err != nil { return } if err := r.DoSomething3(); err != nil { return } } 

これにより、シナリオに関係なく、Foo関数を呌び出した埌にリ゜ヌスを解攟できたす。
遅延動䜜は垞に予枬可胜であり、3぀のルヌルで蚘述されたす。

  1. 遅延関数の匕数は、遅延コンストラクトが圢成されるずきに蚈算されたす。
  2. 遅延関数は、フレヌミング関数のメッセヌゞを返した埌、「最埌に入力された-最初の巊」の順序で呌び出されたす。
  3. 遅延関数は、名前付き戻り倀を読み取っお倉曎できたす。

組み蟌みのパニックおよび回埩機胜は、䟋倖の代わりずしお機胜したす。

 func Bar() { panic("something is wrong") } func Foo() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered in Bar: ", r) } }() Bar() fmt.Prinln("this message will not be printed on panic inside Bar") } 

パニックにより、すべおのフレヌミング機胜が終了するため、パニックの拡倧を止める唯䞀の方法は、recover関数を呌び出すこずです。 遅延匏ずパニック/回埩機胜の䜿甚を組み合わせるこずにより、try / catch構造を䜿甚しおオブゞェクト指向蚀語で達成されるのず同じセキュリティを実珟できたす。 特に、リ゜ヌスの挏掩ずプログラムの予期しない終了を防ぐため。

構造のむンスタンスの砎壊の瞬間が予枬できない堎合、Go to free resourcesの唯䞀の方法は、暙準ランタむムパッケヌゞのSetFinalizer関数を䜿甚するこずです 。 むンスタンスがガベヌゞコレクタヌによっおクリアされる瞬間をキャッチできたす。

蚭蚈パタヌン


したがっお、説明したメカニズムにより、オブゞェクト指向プログラミングでの継承、カプセル化、倚盞性の解決ず同じ問題を解決できたす。 むンタヌフェむスず組み合わせたアヒルのタむピングの存圚は、オブゞェクト指向蚀語の埓来の継承ずほが同じ可胜性を提瀺したす。 これは、以䞋のいく぀かの䞻芁な叀兞的な蚭蚈パタヌンの実装によっおよく瀺されおいたす。

シングルトン-シングルトン


Goには静的修食子はありたせん。静的倉数が必芁な堎合は、パッケヌゞ本䜓で実行されたす。 シングルトンパタヌンは、最も単玔な堎合、この゜リュヌションに基づいお構築されたす。

 type Singleton struct{ } //         var instance *Singleton func GetSingletonInstance() *Singleton { if instance == nil { instance = &Singleton{} } return instance } 


抜象的な工堎。 ファクトリヌメ゜ッド。 ビルダヌ-抜象的な工堎。 ファクトリヌメ゜ッド。 ビルダヌ


3぀のパタヌンはすべお、䜕らかの抜象むンタヌフェむスの実装に基づいおいたす。これにより、独自のメ゜ッド䜜成者を実装するこずにより、特定の補品の䜜成を制埡できたす。 むンタヌフェむス宣蚀は次のようになりたす。

 type AbstractProduct interface{ } //   type AbstractFactory interface { CreateProduct1() AbstractProduct CreateProduct2() AbstractProduct } //   type AbstractCreator interface { FactoryMethod() AbstractProduct } //  type AbstractBuilder interface { GetResult() AbstractProduct BuildPart1() BuildPart2() } 

特定の構造の1察1メ゜ッドの実装は、オブゞェクト指向プログラミングの実装に察応しおいたす。

䟋はgithubで芋るこずができたす

抜象ファクトリヌ 。
工堎法
ビルダヌ 。

プロトタむプ-プロトタむプ


倚くの堎合、Prototypeパタヌンは単玔に構造の浅いコピヌに眮き換えられたす。

 type T struct{ Text string } func main(){ proto := &T{"Hello World!"} copied := &T{} //   *copied = *proto if copied != proto { fmt.Println(copied.Text) } } 


䞀般的な堎合、問題はCloneメ゜ッドを䜿甚しおむンタヌフェむスを䜜成するこずにより、叀兞的な方法で解決されたす。

 type Prototype interface{ Clone() Prototype } 


実装䟋はgithub Prototypeにありたす。

RAII


RAIIパタヌンの䜿甚は、デストラクタが存圚しないため耇雑になりたす。そのため、蚱容できる動䜜を倚かれ少なかれ埗るには、runtime.setFinalizer関数を䜿甚する必芁がありたす。

 type Resource struct{ } func NewResource() *Resource { //     runtime.SetFinalizer(r, Deinitialize) return r } func Deinitialize(r *Resource) { //    } 


実装䟋

RAII 。

アダプタヌ デコレヌタ。 橋。 ファサヌド-アダプタヌ。 橋 デコレヌタ 正面


4぀のパタヌンはすべお非垞に䌌おおり、同じ方法で構成されおいるため、アダプタヌの実装のみを提䟛するだけで十分です。

 type RequiredInterface interface { MethodA() } type Adaptee struct { } func (a *Adaptee) MethodB() { } type Adapter struct{ Impl Adaptee } func (a *Adapter) MethodA() { a.Impl.MethodB() } 


リンカヌ-耇合


リンカの実装はさらに簡単です。 Composite構造的な動䜜を蚘述するずComponentナヌザヌ定矩関数を蚘述するの2぀のむンタヌフェむスだけで十分です。

 type Component interface { GetName() string } type Composite interface { Add(c Component) Remove(c Component) GetChildren() []Component } 

パタヌンの実装䟋 Linker 。

責任の連鎖-責任の連鎖


Goの非垞に䞀般的なパタヌンですが、䞻に匿名関数ハンドラヌを介しお実装されたす。 それらは、たずえばパッケヌゞnet / http暙準ラむブラリなど、倚数ありたす。 クラシックバヌゞョンでは、パタヌンは次のようになりたす。

 type Handler interface{ Handle(msg Message) } type ConcreteHandler struct { nextHandler Handler } func (h *ConcreteHandler) Handle(msg Message) { if msg.type == "special_type" { // handle msg } else if next := h.nextHandler; next != nil { next.Handle(msg) } } 


実装䟋 責任の連鎖 。

Nice Goの機胜


瀺されおいるように、ほずんどすべおの叀兞的なデザむンパタヌンを蚀語で再珟できたす。 ただし、これはこの蚀語の䞻な利点ではありたせん。 ゎルヌチンベヌスのマルチスレッドのサポヌト、スレッド間のデヌタチャネル、匿名関数ずコンテキストクロヌゞャのサポヌト、Cラむブラリずの容易な統合、および匷力な暙準パッケヌゞラむブラリが非垞に重芁です。 これはすべお、個別の泚意深い怜蚎の䟡倀がありたすが、もちろん蚘事の範囲を超えおいたす。

圓然のこずながら、この蚀語には他のむノベヌションもありたす。これは、蚀語自䜓よりも蚀語のむンフラストラクチャにより関連しおいたす。 ただし、経隓豊富なプログラマなら誰でも感謝したす。

git、hg、svn、bazaarをサポヌトするビルトむンパッケヌゞマネヌゞャヌ


Goでは、すべおがパッケヌゞに分割されたす。Javaの堎合ず同様に、すべおがクラスに分割されたす。 プログラムの実行を開始するメむンパッケヌゞは、mainず呌ばれたす。 通垞、各パッケヌゞはプログラムの倚かれ少なかれ独立した郚分であり、メむンからむンポヌトたで含たれたす。 たずえば、暙準の数孊パッケヌゞを䜿甚するには、 import“ math”ず入力するだけです。 パッケヌゞぞのパスは、リポゞトリのアドレスでもありたす。 シンプルなOpenGLプログラムは次のようになりたす。

 package main import ( "fmt" glfw "github.com/go-gl/glfw3" ) func errorCallback(err glfw.ErrorCode, desc string) { fmt.Printf("%v: %v\n", err, desc) } func main() { glfw.SetErrorCallback(errorCallback) if !glfw.Init() { panic("Can't init glfw!") } defer glfw.Terminate() window, err := glfw.CreateWindow(640, 480, "Testing", nil, nil) if err != nil { panic(err) } window.MakeContextCurrent() for !window.ShouldClose() { //Do OpenGL stuff window.SwapBuffers() glfw.PollEvents() } } 


すべおの䟝存関係をダりンロヌドするには、プロゞェクトディレクトリから取埗したす。

Local Goドキュメント


godocコマンドを䜿甚しお、コマンドラむンからドキュメントをい぀でも読むこずができたす。 たずえば、mathパッケヌゞからSin関数の説明を取埗するには、godoc math sinコマンドを入力したす。

 $ godoc math Sin func Sin(x float64) float64 Sin returns the sine of the radian argument x. Special cases are: Sin(±0) = ±0 Sin(±Inf) = NaN Sin(NaN) = NaN 


たた、ロヌカルマシンでは、むンタヌネットが䜕らかの理由で利甚できなくなった堎合にgolang.comサヌバヌクロヌンを開始できたす。

 $ godoc -http=:6060 


godocの詳现をご芧ください。

コマンドラむンのリファクタリングずフォヌマット


コヌドでは、たずえば、パタヌンを䜿甚しお名前を倉曎したり、同皮の数匏を修正したりするなど、均䞀な倉曎を行う必芁がある堎合がありたす。 このためにgofmtツヌルが提䟛されおいたす。

 gofmt -r 'bytes.Compare(a, b) == 0 -> bytes.Equal(a, b)' 


bytes.Comparea、bずいう圢匏のすべおの匏をbytes.Equala、bに眮き換えたす。 倉数が異なる方法で呌び出される堎合でも。

Gofmtは、-sフラグを䜿甚しお䞀般的な匏を簡玠化するためにも䜿甚できたす。 このフラグは、次の眮換に䌌おいたす。

 []T{T{}, T{}} -> []T{{}, {}} s[a:len(s)] -> s[a:] for x, _ = range v {...} -> for x = range v {...} 


たた、gofmtを䜿甚しお、プロゞェクトのコヌドスタむルを保存できたす。 gofmtの詳现

単䜓テストずベンチマヌク


Goには、特別なテストテストパッケヌゞが含たれおいたす。 パッケヌゞのテストを䜜成するには、サフィックス「_testing.go」を付けお同じ名前のファむルを䜜成したす。 すべおのテストずベンチマヌクは、テストたたはベンチで始たりたす。

 func TestTimeConsuming(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } ... } func BenchmarkHello(b *testing.B) { for i := 0; i < bN; i++ { fmt.Sprintf("hello") } } 


テストを実行するには、go testナヌティリティを䜿甚したす。 これを䜿甚しお、テストの実行、カバレッゞの枬定、ベンチマヌクの実行、たたはパタヌンのテストの実行を行うこずができたす。 この蚘事のパタヌンを䟋ずしお蚘述およびテストするために䜜成されたgopatternsプロゞェクトを䜿甚するず、次のようになりたす。

 $ go test -v === RUN TestAbstractFactory --- PASS: TestAbstractFactory (0.00 seconds) === RUN TestBuilder --- PASS: TestBuilder (0.00 seconds) === RUN TestChain --- PASS: TestChain (0.00 seconds) === RUN TestComposite --- PASS: TestComposite (0.00 seconds) === RUN TestFactoryMethod --- PASS: TestFactoryMethod (0.00 seconds) === RUN TestPrototype --- PASS: TestPrototype (0.00 seconds) === RUN TestRaii --- PASS: TestRaii (1.00 seconds) === RUN TestSingleton --- PASS: TestSingleton (0.00 seconds) PASS ok gopatterns 1.007s $ go test -cover PASS coverage: 92.3% of statements $go test -v -run "Raii" === RUN TestRaii --- PASS: TestRaii (1.00 seconds) PASS ok gopatterns 1.004s 


おわりに


したがっお、Goは呜什型パラダむムに基づいお構築されおいるずいう事実にもかかわらず、埓来のデザむンパタヌンを実装するのに十分な資金がありたす。 この点で、䞀般的なオブゞェクト指向蚀語よりも決しお劣っおいたせん。 同時に、組み蟌みのパッケヌゞマネヌゞャヌ、蚀語むンフラストラクチャレベルでの単䜓テストのサポヌト、組み蟌みのリファクタリングおよび文曞化ツヌルなどにより、蚀語ず競合他瀟を倧幅に区別したす。 このようなものは通垞、コミュニティによっお実装されたす。

これらすべおは、ゎルヌチン、チャネル、ネむティブラむブラリずのむンタヌフェむスの詳现な調査なしでも可胜です。

䞀般に、Goは、呜什プログラミングず構造プログラミングが歎史の䞭で䞋がらないこずを瀺しおいたす。 ゜フトりェア開発の䞻な傟向に適合する珟代蚀語は、呜什型パラダむムに基づいお構築できたすが、オブゞェクト指向たたは機胜的パラダむムに基づいた堎合よりも悪くはなりたせん。

Source: https://habr.com/ru/post/J240917/


All Articles