goのインターネット

15150 ワード

9年のC++、1年のC#を使って、最近go言語の開発に変えて、go言語の設計が簡単であることを深く感じて、その設計の趣旨もless is moreで、それは極めて開発のスピードを速めました.
go言語は多くの現代言語の利点を吸収して、1つの比較的重要な特性はインタフェースに基づいてプログラミングして、関数はプログラムの世界の第1公民で、これは少しscalar言語に似ています.このインタフェースを実現する言語の原型はinterfaceである.
インタフェースベースプログラミング
C++ではインタフェースがサポートされていません.インタフェースの実現方式は純粋な虚関数で実現されています.C#はインタフェースを持っていますが、インタフェースはオブジェクトの能力であり、これは大きな進歩ですが、柔軟性がありません.例えば、
public Interface IFile
{
    public int Read(string filePath, int len)
    public int Write(string filePath, int len)
}
              len     ,       ,    len     ,           
public class CDataNode:IFile
{
    public int Read(string filePath, int len){...}
    public int Write(string filePath, int len){...}
}
  CDataNode      IFile         

このようなコードは長い間書かれていましたが、IFIleという粒度が大きすぎて、Readだけが必要なところもあれば、Writeが必要なところもあり、C#では
public Interface IRead
public Interface IWrite
public Interface IFile:IRead,IWrite

goではそうする必要はありません.IreadとIWriteインタフェースを宣言するだけで、IFIleはIreadとIWriteに直接変換することができ、より柔軟になります.
package main

import (
    "fmt"
)

type iFile interface {
    Read(int) int
    Write(int) int
}

type rwFile struct {
}

func (rw rwFile) Read(int) int {
    fmt.Println("rwFile read")
    return 0
}
func (rw rwFile) Write(int) int {
    fmt.Println("rwFile write")
    return 0
}

type iRead interface {
    Read(int) int
}

type iWrite interface {
    Write(int) int
}

func TestInterface() {
    var rwInterface iFile = rwFile{}
    rwInterface.Read(0)
    rwInterface.Write(0)
    var rInterface iRead = rwInterface
    rInterface.Read(0)
    var wInterface iWrite = rwInterface
    wInterface.Write(0)
}

モジュール間はインタフェースに依存し,goは継承をサポートせず,組合せのみをサポートし,継承は強い関係であり,いったん決定されると変更が困難である,あるいは変更コストが非常に高く,組合せは弱い関係であり,より緩やかである.
継承とマルチステートプロパティ
継承とマルチステートは、特定の実装に依存するのではなく、モジュール間をインタフェースに依存させるより抽象的なフレームワークをよりよく設計するオブジェクト向けに非常に良い特性です.
package main

import (
    "fmt"
)

type iAnimal interface {
    speak() string
}

type dog struct {
}

func (d dog) speak() string {
    return "Woof!"
}

type cat struct {
}

func (c cat) speak() string {
    return "Meow!"
}

type coder struct {
}

func (c coder) speak() string {
    return "Hello world!"
}

type superCoder struct {
    coder//          ,       coder coder   ,                 ,             ,      .coder   。
}

func (s superCoder) sayHi() {
    fmt.Println("super coder say hi")
}

func Polymorphic() {
    animals := []iAnimal{dog{}, cat{}, coder{}, superCoder{}}
    for _, animal := range animals {
        fmt.Println(animal.speak())
    }
}

インタフェースに依存して実装され,このインタフェースを実装すれば,このインタフェースに値を付与し,動的バインドを実装すると考えられる.superCoderはここでは継承とcoderと考えられるが,新しい方法SayHiが追加され,従来のspeak方法は変化していないので書き換える必要もない.goでは継承されておらず、匿名メンバー変数も文法糖にすぎず、継承されていない.
type cat struct{
    age int
}
type dog struct{
    cat
}
var c cat = dog{}//    
var pc *cat = &dog{}//    
var pc *cat = (*cat)(&dog{})//    

C++ではベースクラスポインタでサブクラスのオブジェクトを指すことができますが、goでは2種類と考えられ、変換できません.根本的にstructはgoに虚表ポインタがなく,この特性はinterfaceのみであるため,互いに変換できない2つの独立したタイプである.
interfaceの使用
interfaceプロパティは、*は関数署名のセットです*はタイプです
interface{}
interface{}は、どのタイプも実装されていないため、すべてのタイプの変数に値を割り当てることができます.C#のobjectに似ています.これはすべてのオブジェクトのベースクラスです.
package main

import (
    "fmt"
)

func PrintAll(vals []interface{}) {
    for _, val := range vals {
        fmt.Println(val)
    }
}

func main() {
    names := []string{"stanley", "david", "oscar"}
    PrintAll(names)
}

最初はなぜコンパイルできないのか不思議でしたが、よく考えてみるとコンパイルすべきでしたが、ここでPrintAllは「[]interface{}」タイプを受信し、入力したパラメータは「[]string」タイプで、2つは異なるタイプで、付与できません.正しいやり方は
package main

import (
    "fmt"
)

func PrintAll(vals []interface{}) {
    for _, val := range vals {
        fmt.Println(val)
    }
}

func main() {
    names := []string{"stanley", "david", "oscar"}
    //PrintAll(names)
    vals := make([]interface{}, len(names))
    for i, v := range names {
        vals[i] = v
    }
    PrintAll(vals)
}

[]interface{}も一種のタイプであるため、interface{}に値を割り当てることができる.
interface{}の使い方
json解析をmapと呼ぶ必要がある.データフォーマットが多変するため、interface{}を採用し、ライブラリ関数UnmarshalJSONを呼び出す.
package main

import (
    "encoding/json"
    "fmt"
    "reflect"
)

// start with a string representation of our JSON data
var input = `
{
    "created_at": "Thu May 31 00:00:01 +0000 2012"
}
`

func main() {
    // our target will be of type map[string]interface{}, which is a
    // pretty generic type that will give us a hashtable whose keys
    // are strings, and whose values are of type interface{}
    var val map[string]interface{}

    if err := json.Unmarshal([]byte(input), &val); err != nil {
        panic(err)
    }

    fmt.Println(val)
    for k, v := range val {
        fmt.Println(k, reflect.TypeOf(v))
    }
}
    
map[created_at:Thu May 31 00:00:01 +0000 2012]
created_at string

ここまでの時間は特殊なフォーマットのtimeであり、通常とは異なり、timeタイプではなくstringタイプが返される.mapのデータをtimeに変更しても問題があります.標準的なtimeフォーマットではないため、異常が放出されます.jsonを実現するインタフェースが考えられる
type Unmarshaler interface {
    UnmarshalJSON([]byte) error
}
func (t *Timestamp) UnmarshalJSON(b []byte) error {
    v, err := time.Parse(time.RubyDate, string(b[1:len(b)-1]))
    if err != nil {
        return err
    }
    *t = Timestamp(v)
    return nil
}

上のmapは正常で、返すタイプもtimeタイプです.
##interface{}は一般的に関数のエントリパラメータとして使用できますが、関数の戻り値として使用しないほうがいいです.APIを設計し、httpリクエストをデータ型フォーマットに変換するには、フォーマットに多くの種類がある可能性があります.返されるデータフォーマットが決まっていないので、interface{}フォーマットが望ましい.
GetEntity(*http.Request) (interface{}, error)

caller:
switch v:Get(r).type(){
    int:
        ...
    string:
        ...
}

新しいタイプを追加するたびに、呼び出し元が変更され、開閉の原則を満たさないため、正しい方法は
type Entity interface {
    UnmarshalHTTP(*http.Request) error
}
func GetEntity(r *http.Request, v Entity) error {
    return v.UnmarshalHTTP(r)
}

caller:
var u User
if err := GetEntity(req, &u); err != nil {
    // ...
}
  User        
func (u *User) UnmarshalHTTP(r *http.Request) error {
   // ...
}

値を返すのではなく、interface{}をパラメータとして使用すると、GetEntityの内部実装を変更することなく、新しいタイプを追加するにはEntityインタフェースを実装するだけでよいので、GetEntityの内部実装を変更する必要はありません.
interfaceの最下位実装
まず一つの例を見てみましょう.この例からどのように実現したのかを見てみましょう.
type Stringer interface {
    String() string
}

type Binary uint64

func (i Binary) String() string {
    return strconv.Uitob64(i.Get(), 2)
}

func (i Binary) Get() uint64 {
    return uint64(i)
}

初期値200のuint 64を作成
b := Binary(200)

メモリモデルは次のとおりです.このとき、bをインタフェースに割り当てます.
s := Stringer(b)//         exception

interface sは2つのメンバー、*1つ目はitableで、C++のvtableに似ていて、一連の関数オブジェクトを保存しています.1つ目はtypeで、それによってどのタイプに属しているかを見つけることができます.これはすべてのinterfaceのitableの最初の値で、他はこのinterfaceタイプに必要な関数に対応する実際の実装です.String()のBinary実装を見ることができます.BinaryではStringerのインタフェースではないのでGet関数を実現しています.itableはどのように生成されますか?これは実行時に動的に生成され、まず各タイプのコンパイラがmetaを生成します.このmetaにはサポートされているすべての関数とメンバー変数が含まれています.オブジェクトがinterfaceに値を割り当てると、runtimeはオブジェクト全体の関数署名を巡ってこのinterfaceに一致する関数署名を見つけ、すべて一致すれば実行を続けます.異常を投げ出す(チェックに失敗しなければ)、同じように1つのinterfaceを別のinterfaceに割り当てる.変数のタイプにm個の関数があり、interfaceにn個のサポートが必要な署名があると仮定すると、アルゴリズムを用いてそれぞれ1回マッチングし、複雑度はO(mn)であり、goでは各関数の署名集合を署名する.このようにマッチングすると複雑度はO(m+n)になる.*もう1つは実際のデータdataで、ここでは新しい値が作成されるので、bの変更はインタフェースに影響しません.
func (i *Binary) String() string {
    return strconv.Uitob64(i.Get(), 2)
}
ps := Stringer(&b)

この場合psのdata部分に保存されるのはbを指すポインタです.変換をするときに伝わるのもポインタですから.
まとめ
  • go言語すべての付与値は値コピー
  • です.
  • interfaceは関数集合のタイプ
  • である.
  • interfaceのitableにはこのinterfaceの具体的な実装が保存され、idataには付与データのコピー
  • が保存されている.
  • interfaceは、パラメータのエントリ関数として使用され、戻り値として使用されるべきではありません.