Golang面向接口編程

1. 接口[多態]

​多態性(polymorphisn)是允許你將父對象設置成爲和一個或更多的他的子對象相等的技術,賦值之後,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。

簡而言之,就是允許將子類類型的指針賦值給父類類型的指針。

即一個引用變量倒底會指向哪個類的實例對象,該引用變量發出的方法調用到底是哪個類中實現的方法,必須在由程序運行期間才能決定。不修改程序代碼就可以改變程序運行時所綁定的具體代碼,讓程序可以選擇多個運行狀態,這就是多態性。多態分爲編譯時多態(靜態多態)和運行時多態(動態多態),編譯時多態一般通過方法重載實現,運行時多態一般通過方法重寫實現。

1.1 接口概念

接口類型可以看作是類型系統中一種特殊的類型,而實例就是實現了該接口的具體結構體類型。

接口類型與實現了該接口的結構體對象之間的關係好比變量類型與變量之間的關係。

​ 接口即一組方法定義的集合,定義了對象的一組行爲,由具體的類型實例實現具體的方法。換句話說,一個接口就是定義(規範或約束),而方法就是實現,接口的作用應該是將定義與實現分離,降低耦合度。習慣用“er”結尾來命名,例如“Reader”。接口與對象的關係是多對多,即一個對象可以實現多個接口,一個接口也可以被多個對象實現。

​ 接口是Go語言整個類型系統的基石,其他語言的接口是不同組件之間的契約的存在,對契約的實現是強制性的,必須顯式聲明實現了該接口,這類接口稱之爲“侵入式接口”。而Go語言的接口是隱式存在,只要實現了該接口的所有函數則代表已經實現了該接口,並不需要顯式的接口聲明

  • 接口的比喻

​ 你的電腦上只有一個USB接口。這個USB接口可以接MP3,數碼相機,攝像頭,鼠標,鍵盤等。。。所有的上述硬件都可以公用這個接口,有很好的擴展性,該USB接口定義了一種規範,只要實現了該規範,就可以將不同的設備接入電腦,而設備的改變並不會對電腦本身有什麼影響(低耦合)

  • 面向接口編程

​ 接口表示調用者和設計者的一種約定,在多人合作開發同一個項目時,事先定義好相互調用的接口可以大大提高開發的效率。接口是用類來實現的,實現接口的類必須嚴格按照接口的聲明來實現接口提供的所有功能。有了接口,就可以在不影響現有接口聲明的情況下,修改接口的內部實現,從而使兼容性問題最小化。

​ 面向接口編程可以分爲三方面:制定者(或者叫協調者),實現者(或者叫生產者),調用者(或者叫消費者)

​ 當其他設計者調用了接口後,就不能再隨意更改接口的定義,否則項目開發者事先的約定就失去了意義。但是可以在類中修改相應的代碼,完成需要改動的內容。

1.2 非侵入式接口

非侵入式接口:一個類只需要實現了接口要求的所有函數就表示實現了該接口,並不需要顯式聲明

type File struct{
  //類的屬性
}
//File類的方法
func (f *File) Read(buf []byte) (n int,err error)
func (f *File) Write(buf []byte) (n int,err error)
func (f *File) Seek(off int64,whence int) (pos int64,err error)
func (f *File) Close() error
//接口1:IFile
type IFile interface{
  Read(buf []byte) (n int,err error)
  Write(buf []byte) (n int,err error)
  Seek(off int64,whence int) (pos int64,err error)
  Close() error
}
//接口2:IReader
type IReader interface{
  Read(buf []byte) (n int,err error)
}
//接口賦值,File類實現了IFile和IReader接口,即接口所包含的所有方法
var file1 IFile = new(File)
var file2 IReader = new(File)

1.3 接口賦值

只要類實現了該接口的所有方法,即可將該類賦值給這個接口,接口主要用於多態化方法。即對接口定義的方法,不同的實現方式。

接口賦值: 
1)將對象實例賦值給接口

type IUSB interface{
    //定義IUSB的接口方法
}
//方法定義在類外,綁定該類,以下爲方便,備註寫在類中
type MP3 struct{
    //實現IUSB的接口,具體實現方式是MP3的方法
}
type Mouse struct{
    //實現IUSB的接口,具體實現方式是Mouse的方法
}
//接口賦值給具體的對象實例MP3
var usb IUSB =new(MP3)
usb.Connect()
usb.Close()
//接口賦值給具體的對象實例Mouse
var usb IUSB =new(Mouse)
usb.Connect()
usb.Close()

2)將接口賦值給另一個接口

  1. 只要兩個接口擁有相同的方法列表(與次序無關),即是兩個相同的接口,可以相互賦值
  2. 接口賦值只需要接口A的方法列表是接口B的子集(即假設接口A中定義的所有方法,都在接口B中有定義),那麼B接口的實例可以賦值給A的對象。反之不成立,即子接口B包含了父接口A,因此可以將子接口的實例賦值給父接口。
  3. 即子接口實例實現了子接口的所有方法,而父接口的方法列表是子接口的子集,則子接口實例自然實現了父接口的所有方法,因此可以將子接口實例賦值給父接口。
type Writer interface{    //父接口
    Write(buf []byte) (n int,err error)
}
type ReadWriter interface{    //子接口
    Read(buf []byte) (n int,err error)
    Write(buf []byte) (n int,err error)
}
var file1 ReadWriter=new(File)   //子接口實例
var file2 Writer=file1           //子接口實例賦值給父接口

1.4 接口查詢

若要在 switch 外判斷一個接口類型是否實現了某個接口,可以使用“逗號 ok ”。

value, ok := Interfacevariable.(implementType)

其中 Interfacevariable 是接口變量(接口值),implementType 爲實現此接口的類型,value 返回接口變量實際類型變量的值,如果該類型實現了此接口返回 true。

//判斷file1接口指向的對象實例是否是File類型
var file1 Writer=...
if file5,ok:=file1.(File);ok{  
  ...
}

1.5 接口類型查詢

在 Go 中,要判斷傳遞給接口值的變量類型,可以在使用 type switch 得到。(type)只能在 switch 中使用。

// 另一個實現了 I 接口的 R 類型
type R struct { i int }
func (p *R) Get() int { return p.i }
func (p *R) Put(v int) { p.i = v }

func f(p I) {
    switch t := p.(type) { // 判斷傳遞給 p 的實際類型
        case *S: // 指向 S 的指針類型
        case *R: // 指向 R 的指針類型
        case S:  // S 類型
        case R:  // R 類型
        default: //實現了 I 接口的其他類型
    }
}

1.6 接口組合

//接口組合類似類型組合,只不過只包含方法,不包含成員變量
type ReadWriter interface{  //接口組合,避免代碼重複
  Reader      //接口Reader
  Writer      //接口Writer
}

1.7 Any類型[空接口]

每種類型都能匹配到空接口:interface{}。空接口類型對方法沒有任何約束(因爲沒有方法),它能包含任意類型,也可以實現到其他接口類型的轉換。如果傳遞給該接口的類型變量實現了轉換後的接口則可以正常運行,否則出現運行時錯誤。

//interface{}即爲可以指向任何對象的Any類型,類似Java中的Object類
var v1 interface{}=struct{X int}{1}
var v2 interface{}="abc"

func DoSomething(v interface{}) {   //該函數可以接收任何類型的參數,因爲任何類型都實現了空接口
   // ...
}

1.8 接口的代碼示例

//接口animal
type Animal interface {
    Speak() string
}
//Dog類實現animal接口
type Dog struct {
}

func (d Dog) Speak() string {
    return "Woof!"
}
//Cat類實現animal接口
type Cat struct {
}

func (c Cat) Speak() string {
    return "Meow!"
}
//Llama實現animal接口
type Llama struct {
}

func (l Llama) Speak() string {
    return "?????"
}
//JavaProgrammer實現animal接口
type JavaProgrammer struct {
}

func (j JavaProgrammer) Speak() string {
    return "Design patterns!"
}
//主函數
func main() {
    animals := []Animal{Dog{}, Cat{}, Llama{}, JavaProgrammer{}}  //利用接口實現多態
    for _, animal := range animals {
        fmt.Println(animal.Speak())  //打印不同實現該接口的類的方法返回值
    }
}

2. client-go中接口的使用分析

以下以k8s.io/client-go/kubernetes/typed/core/v1/pod.go的pod對象做分析。

2.1 接口設計與定義

2.1.1 接口組合

// PodsGetter has a method to return a PodInterface.
// A group's client should implement this interface.
type PodsGetter interface {
    Pods(namespace string) PodInterface
}

2.1.2 接口定義

// PodInterface has methods to work with Pod resources.
type PodInterface interface {
    Create(*v1.Pod) (*v1.Pod, error)
    Update(*v1.Pod) (*v1.Pod, error)
    UpdateStatus(*v1.Pod) (*v1.Pod, error)
    Delete(name string, options *meta_v1.DeleteOptions) error
    DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error
    Get(name string, options meta_v1.GetOptions) (*v1.Pod, error)
    List(opts meta_v1.ListOptions) (*v1.PodList, error)
    Watch(opts meta_v1.ListOptions) (watch.Interface, error)
    Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Pod, err error)
    PodExpansion
}

PodInterface接口定義了pod對象所使用的方法,一般爲增刪改查等。其他kubernetes資源對象的接口定義類似,區別在於入參和出參與對象相關。例如Create(*v1.Pod) (*v1.Pod, error)方法定義的入參出參爲*v1.Pod。如果要實現該接口,即實現該接口的所有方法。

2.2 接口的實現

2.2.1 結構體的定義

// pods implements PodInterface
type pods struct {
    client rest.Interface
    ns     string
}

2.2.2 new函數[構造函數]

// newPods returns a Pods
func newPods(c *CoreV1Client, namespace string) *pods {
    return &pods{
        client: c.RESTClient(),
        ns:     namespace,
    }
}

2.2.3 方法的實現

Get

// Get takes name of the pod, and returns the corresponding pod object, and an error if there is any.
func (c *pods) Get(name string, options meta_v1.GetOptions) (result *v1.Pod, err error) {
    result = &v1.Pod{}
    err = c.client.Get().
        Namespace(c.ns).
        Resource("pods").
        Name(name).
        VersionedParams(&options, scheme.ParameterCodec).
        Do().
        Into(result)
    return
}

List

// List takes label and field selectors, and returns the list of Pods that match those selectors.
func (c *pods) List(opts meta_v1.ListOptions) (result *v1.PodList, err error) {
    result = &v1.PodList{}
    err = c.client.Get().
        Namespace(c.ns).
        Resource("pods").
        VersionedParams(&opts, scheme.ParameterCodec).
        Do().
        Into(result)
    return
}

Create

// Create takes the representation of a pod and creates it.  Returns the server's representation of the pod, and an error, if there is any.
func (c *pods) Create(pod *v1.Pod) (result *v1.Pod, err error) {
    result = &v1.Pod{}
    err = c.client.Post().
        Namespace(c.ns).
        Resource("pods").
        Body(pod).
        Do().
        Into(result)
    return
}

Update

// Update takes the representation of a pod and updates it. Returns the server's representation of the pod, and an error, if there is any.
func (c *pods) Update(pod *v1.Pod) (result *v1.Pod, err error) {
    result = &v1.Pod{}
    err = c.client.Put().
        Namespace(c.ns).
        Resource("pods").
        Name(pod.Name).
        Body(pod).
        Do().
        Into(result)
    return
}

Delete

// Delete takes name of the pod and deletes it. Returns an error if one occurs.
func (c *pods) Delete(name string, options *meta_v1.DeleteOptions) error {
    return c.client.Delete().
        Namespace(c.ns).
        Resource("pods").
        Name(name).
        Body(options).
        Do().
        Error()
}

2.3 接口的調用

示例:

// 創建clientset實例
clientset, err := kubernetes.NewForConfig(config)
// 具體的調用
pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})

clientset實現了接口InterfaceInterface是個接口組合,包含各個client的接口類型。例如CoreV1()方法對應的接口類型是CoreV1Interface

以下是clientset的CoreV1()方法實現:

// CoreV1 retrieves the CoreV1Client
func (c *Clientset) CoreV1() corev1.CoreV1Interface {
    return c.coreV1
}

該方法可以理解爲是一個構造函數。構造函數的返回值類型是一個接口類型CoreV1Interface,而return的返回值是實現了該接口類型的結構體對象c.coreV1

接口類型是一種特殊的類型,接口類型與結構體對象之間的關係好比變量類型與變量之間的關係。其中的結構體對象必須實現了該接口類型的所有方法。

所以clientset的CoreV1()方法實現是返回一個CoreV1Client結構體對象。該結構體對象實現了CoreV1Interface接口,該接口也是一個接口組合。

type CoreV1Interface interface {
    RESTClient() rest.Interface
    ComponentStatusesGetter
    ConfigMapsGetter
    EndpointsGetter
    EventsGetter
    LimitRangesGetter
    NamespacesGetter
    NodesGetter
    PersistentVolumesGetter
    PersistentVolumeClaimsGetter
    PodsGetter
    PodTemplatesGetter
    ReplicationControllersGetter
    ResourceQuotasGetter
    SecretsGetter
    ServicesGetter
    ServiceAccountsGetter
}

而實現的Pods()方法是其中的PodsGetter接口。

Pods()CoreV1()一樣是個構造函數,構造函數的返回值類型是PodInterface接口,返回值是實現了PodInterface接口的pods結構體對象。

func (c *CoreV1Client) Pods(namespace string) PodInterface {
    return newPods(c, namespace)
}

PodInterface接口定義參考接口定義pods對象實現了PodInterface接口的方法,具體參考接口的實現

最終調用了pods對象的List()方法。

pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})

即以上代碼就是不斷調用實現了某接口的結構體對象的構造函數,生成具體的結構體對象,再調用結構體對象的某個具體方法。

3. 通用接口設計

3.1 接口定義

// ProjectManager manage life cycle of Deployment and Resources
type PodInterface interface {
    Create(*v1.Pod) (*v1.Pod, error)
    Update(*v1.Pod) (*v1.Pod, error)
    UpdateStatus(*v1.Pod) (*v1.Pod, error)
    Delete(name string, options *meta_v1.DeleteOptions) error
    DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error
    Get(name string, options meta_v1.GetOptions) (*v1.Pod, error)
    List(opts meta_v1.ListOptions) (*v1.PodList, error)
    Watch(opts meta_v1.ListOptions) (watch.Interface, error)
    Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Pod, err error)
    PodExpansion
}

3.2 結構體定義

// pods implements PodInterface
type pods struct {
    client rest.Interface
    ns     string
}

3.3 構造函數

// newPods returns a Pods
func newPods(c *CoreV1Client, namespace string) *pods {
    return &pods{
        client: c.RESTClient(),
        ns:     namespace,
    }
}

3.4 結構體實現

List()

// List takes label and field selectors, and returns the list of Pods that match those selectors.
func (c *pods) List(opts meta_v1.ListOptions) (result *v1.PodList, err error) {
    result = &v1.PodList{}
    err = c.client.Get().
        Namespace(c.ns).
        Resource("pods").
        VersionedParams(&opts, scheme.ParameterCodec).
        Do().
        Into(result)
    return
}

3.5 接口調用

pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})

3.6 其他接口設計示例

type XxxManager interface {
    Create(args argsType) (*XxxStruct, error)
    Get(args argsType) (**XxxStruct, error)
    Update(args argsType) (*XxxStruct, error)
    Delete(name string, options *DeleleOptions) error
}

type XxxManagerImpl struct {
    Name string
    Namespace string
    kubeCli *kubernetes.Clientset
}

func NewXxxManagerImpl (namespace, name string, kubeCli *kubernetes.Clientset) XxxManager {
    return &XxxManagerImpl{
        Name name,
        Namespace namespace,
        kubeCli: kubeCli,
    }
}

func (xm *XxxManagerImpl) Create(args argsType) (*XxxStruct, error) {
    //具體的方法實現
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章