对C、C++、golang几种语言接口的理解

        之前只用过C和C++,对于接口这个概念只存在一个逻辑上的认识,认为它是连接代码写作方和使用方的一个渠道,现在接触到了GO语言,接口(interface{})是GO的一个重要特性,因此决定对C、C++和GO的接口特性进行一番梳理和对比。

C语言

        C语言的接口概念较为简单,在只讨论C语言基础用法(不掺杂模拟面向对象的用法)的情况下,其接口与使用方的耦合性最高,如果用户想要调用某块功能代码,通常需要通过包含头文件去获取接口类型。接口使用方不需要关心接口的具体实现,只要在保证接口不变(头文件内的部分)的前提下我们可以自由地修改接口实现代码。C就是一把么得感情的尖刀,他为你提供相当具体的方法,使用方法单一,可扩展性较差(好像有点偏题。。。不过就权当又讨论了一番面向过程和面向对象的区别好了)。举个例子,我们现在需要实现一个使用不同工具去为不同食物剥皮的程序:

#include 

//接口
typedef struct apple {  //苹果
    //
    const char* name;
    int size;
}apple;

typedef struct nut {   //坚果
    //
    const char* name;
    int size;
}nut;

typedef struct hammer {
    const char* name;
    int power;  //力量度
    int sharpness; //锋利度
}hammer;

typedef struct knife {
    const char* name;
    int power;
    int sharpness;
}knife;

void peelApple_hammer(apple* tar, hammer* tool){
    if(!tar || !tool){
        return;
    }
    if (tool->sharpness == 0){
        printf("Peel %s by %s Failed.\n", tar->name, tool->name);
        return;
    }
    printf("Peel %s with %s. take %d seconds.\n", tar->name, tool->name, tar->size/tool->sharpness);
    return;
}

void peelApple_knife(apple* tar, knife* tool){
    if(!tar || !tool){
        return;
    }
    if (tool->sharpness == 0){
        printf("Peel %s by %s Failed.\n", tar->name, tool->name);
        return;
    }
    printf("Peel %s with %s. take %d seconds.\n", tar->name, tool->name, tar->size/tool->sharpness);
    return;
}

void peelNut_hammer(nut* tar, hammer* tool){
    if(!tar || !tool){
        return;
    }
    if (tool->power == 0){
        printf("Peel %s by %s Failed.\n", tar->name, tool->name);
        return;
    }
    printf("Peel %s with %s. take %d seconds.\n", tar->name, tool->name, tar->size/tool->power);
    return;
}

void peelNut_knife(nut* tar, knife* tool){
    if(!tar || !tool){
        return;
    }
    if (tool->power == 0){
        printf("Peel %s by %s Failed.\n", tar->name, tool->name);
        return;
    }
    printf("Peel %s with %s. take %d seconds.\n", tar->name, tool->name, tar->size/tool->power);
    return;
}

外部程序
int main(){
    struct apple strap = {"Apple", 10};
    struct nut strnut = {"Nut", 5};
    struct hammer strham = {"hammer", 5, 0};
    struct knife strkni = {"knife", 1, 5};
    peelApple_hammer(&strap, &strham);
    peelApple_knife(&strap, &strkni);
    peelNut_hammer(&strnut, &strham);
    peelNut_knife(&strnut, &strkni);
    return 0;
}

打印:

Peel Apple by hammer Failed.
Peel Apple with knife. take 2 seconds.
Peel Nut with hammer. take 1 seconds.
Peel Nut with knife. take 5 seconds.

        此时,如果我们想要增加一种水果或一种剥皮工具,就至少需要增加一个数据类型和1*n(当前水果或工具的种类数)个函数接口。

 

C++

        C++的接口可被称之为抽象类,即设定一个抽象基类去描述一组基本行为(接口),然后通过多样化的派生类去完成该接口。接口作为不同组件之间的契约存在,且对契约的实现是强制的,即语言必须声明实现了该接口。

#include 

class fruit;

接口
class toolsak {
    public:
        toolsak(const char* na, int pow, int shar){
            name = na;
            power = pow;
            sharpness = shar;
        }
        const char* name;
        int power;
        int sharpness;
};

class fruit {
    public:
        fruit(const char* na, int siz){
            name = na;
            size = siz;
        }
        const char* name;
        int size;
    public:
        virtual void peel(toolsak* tool) = 0;
};

class hammer : public toolsak {
    public:
	hammer(const char* name, int power, int sharp):toolsak(name, power, sharp){}
};

class knife : public toolsak {
    public:
	knife(const char* name, int power, int sharp):toolsak(name, power, sharp){}
};

class apple : public fruit {
    public:
	apple(const char* name, int size):fruit(name, size){}
        void peel(toolsak* tool){
            if(!tool || tool->sharpness == 0){
                return;
            }
            printf("Peel the %s with %s. take %d seconds\n", name, tool->name, size/tool->sharpness);
        }
};

class nut : public fruit {
    public:
	nut(const char* name, int size):fruit(name, size){}
        void peel(toolsak* tool){
            if(!tool || tool->power == 0){
                return;
            }
            printf("Peel the %s with %s. take %d seconds\n", name, tool->name, size/tool->power);
        }
};

外部程序
int main(){
    toolsak* tptr = new hammer("hammer", 5, 0);
    fruit* fptr = new apple("apple", 10);
    toolsak* tptr2 = new knife("knife", 1, 5);
    fruit* fptr2 = new nut("nut", 5);
    fptr->peel(tptr);
    fptr->peel(tptr2);
    fptr2->peel(tptr);
    fptr2->peel(tptr2);
    return 0;
}

打印:

Peel the apple with knife. take 2 seconds
Peel the nut with hammer. take 1 seconds
Peel the nut with knife. take 5 seconds

 

golang

        实现类和抽象接口之间不需要硬性连接(即声明继承或虚函数),只要你的实现类实现了某接口规定的方法,那么该类的使用方就可以实例化该类并赋值给接口,然后可以通过接口直接调用具体方法。

package main

import (
   "errors"
   "fmt"
)

type tool struct {
   name string
   power int
   sharpness int
}

type fruit struct {
   name string
   size int
}

type toolIf interface {
   getName() string
   getPower() int
   getSharpness() int
}

type fruitIf interface {
   peel(too interface{toolIf}) error
}

type hammer struct {
   *tool
}

type knife struct {
   *tool
}

type apple struct {
   *fruit
}

type nut struct {
   *fruit
}

func (a *apple) peel(too interface {toolIf}) error {
   if too.getSharpness() == 0 {
      return errors.New("xxxx")
   }
   //do that
   fmt.Printf("Peel the %v with %v, take %v seconds\n", a.name, too.getName(), a.size/too.getSharpness())
   return nil
}

func (n *nut) peel(too interface{toolIf}) error {
   if too.getPower() == 0 {
      return errors.New("xxxx")
   }
   //do that
   fmt.Printf("Peel the %v with %v, take %v seconds\n", n.name, too.getName(), n.size/too.getPower())
   return nil
}

func (t *tool) getName() string {
   return t.name
}

func (t *tool) getPower() int {
   return t.power
}

func (t *tool) getSharpness() int {
   return t.sharpness
}

func main() {
   var (
      appFr fruitIf = &apple{&fruit{"apple", 10}}
      appNu fruitIf = &nut{&fruit{"nut", 5}}
      toolHam = &hammer{&tool{"hammer", 5, 0}}
      toolKni = &knife{&tool{"knife", 1, 5}}
   )
   appFr.peel(toolHam)
   appFr.peel(toolKni)
   appNu.peel(toolHam)
   appNu.peel(toolKni)
   return
}

打印:

Peel the apple with knife, take 2 seconds
Peel the nut with hammer, take 1 seconds
Peel the nut with knife, take 5 seconds

        tips :需要注意,在GO语言中,使用实例对接口赋值最好用指针而不用对象本身,不然有可能编译报错:因为你可以为某个类型或类型指针去定义方法,如:

type fruitIf interface {
   getsize() error
   getname() error
}

type apple struct{}

func (a apple) getsize() error {
   return nil
}

func (a *apple) getname() error {
    return nil    
}

func  main ()  {
   //var appl apple
   var frIf fruitIf = &apple{}
   frIf.getsize()
}

        如上代码编译可以通过,是因为Go可以根据函数1⃣去自动生成一个新方法:

func (a *apple)getsize() error {
    return *a.getsize()
}

        显然,GO为我们自动做了封装,反之则不然:

ttype fruitIf interface {
   getsize() error
}

type apple struct{}

func (a apple) getsize() error {
   return nil
}

func (a *apple) getname() error {
    return nil    
}

func  main ()  {
   var appl apple
   var frIf fruitIf = appl
   frIf.getsize()
}

        这样子是不能编译通过的,是因为GO不能根据对象为你生成以下新方法:

func (a apple)getname()error {
    return &a.getname()
}

因为GO函数参数全都是按值传递的,为其做操作不能对外部真实对象造成影响。

 

总结

        接口是一种规范,一个协议,一个抽象出来的方法集合,单纯它本身词语的含义上,上述几种语言并无高下,即使他们存在面向对象和面向过程的区别,但是“可以承受需求变化的接口”就要求编程语言有更合理的接口设计,在这个范畴内,个人认为:虽然C语言的接口最为简单,因其语言层面提供的可扩展性较低,显得更容易理解,可是如果需要扩展接口,那么这将是一个极为痛苦的过程;C++通过继承和虚函数大大提升了接口的可扩展性,代码简洁明了,前提是需要显式声明;Go直接在语言层面支持接口的设计,并且使用接口的过程被极大简化,如果你的某个类完全实现了某接口内的方法集,那么你就相当于实现了该接口,即使二者的定义过程并无关联,这就是所谓的“非侵入式接口”。内中含义需慢慢体会~

你可能感兴趣的:(Golang)