从动态/静态语言角度理解接口

pFN2Bon.webp

为什么需要接口

什么是接口

在编程语言中,接口(interface)是一种规范或契约,用于定义类或对象应该提供哪些方法、属性或事件。接口提供了一种抽象的方式来描述类或对象的行为,使得不同的类或对象可以通过实现相同的接口来实现相同的功能。

接口通常包含一组方法和属性的声明,但不包含任何具体的实现。通过实现相同的接口,不同的类或对象可以实现相同的功能,从而减少了代码的重复,提高了代码的可重用性和可维护性。

在一些编程语言中,如 Java 和 Go,接口是一种独立的类型,可以被类或对象实现。在其他编程语言中,如 Python 和 Javascript,接口通常被视为约定或协议,而不是独立的类型。

接口的作用

抽象类的行为:接口提供了一种抽象的方式来描述类或对象的行为,使得开发人员可以更加清晰地理解和设计类或对象的行为。

规范类或对象的行为:接口定义了类或对象应该提供哪些方法、属性或事件,从而规范了类或对象的行为。

提高代码的可重用性和可维护性:通过实现相同的接口,不同的类或对象可以实现相同的功能,从而减少了代码的重复,提高了代码的可重用性和可维护性。

降低代码的耦合性:通过接口,类或对象之间的依赖关系得到了解耦,从而提高了代码的灵活性和可扩展性。

不同编程语言中接口的表现

java 中的接口

java 属于静态语言,其变量的类型在编译时就已经确定。在实际的业务场景中,经常会有某个变量要根据实际的情况调用不同类型对象的方法,如果这个变量依赖于具体的类,那么是很难实现的。

因此在 java 中,相同场景的类一般都会继承同一个接口,这样变量的类型被定义成接口的类型,这个变量就依赖于抽象而不是具体,在真正调用的时候根据实际情况选择不同的实现类进行调用。

在下面的代码中 Desktop 和 Laptop 都实现了 Computer 这个接口并强制实现了接口定义的方法 getContent()。

public interface Computer {
    public String getContent(String param);
}

public class Desktop implements Computer {
    @Override
    public String getContent(String param) {
        return "Get content from Desktop: " + param;
    }
}

public class Laptop implements Computer {
    @Override
    public String getContent(String param) {
        return "Get content from Desktop: " + param;
    }
}

在对 Desktop 和 Laptop 的实际使用中(见下面代码)。根据不同的业务需求通过工厂方法 getComputer 返回 Desktop 或 Laptop 的实例,变量 c 的类型是 Computer 接口类,也就是 c 依赖于抽象的接口而不是具体的 Desktop 或者 Laptop。这样提升了代码的灵活性,同时实现了代码的解耦。

public class Calc {
    public static Computer getComputer(int cond) throws Exception {
        if (cond == 1) {
            return new Desktop();
        } else if (cond == 2) {
            return new Laptop();
        } else {
            throw new Exception("Wrong cond...");
        }
    }

    public static void main(String[] args) throws Exception {
        int cond = 1;
        Computer c = getComputer(1);
        String str = c.getContent("http://abc.com/");
        System.out.println(str);
    }
}

值得注意的是,在 java 中对接口的定义一般是从定义者出发的。在下面的代码中,main 方法中 c 需要的是一个实现了 Computer 接口的实现类,也就是说 Computer 是提前约定好的,c 只会接受实现了 Computer 接口的类,其他的类是不行的。

go 中的接口

go 也属于静态语言。如同 java 语言一样,也需要接口来提高程序的灵活性和依赖的解耦。

在下面代码中,定义 Desktop 和 Laptop 两个结构体,并都定义 GetContent 方法。

type Laptop struct{}

func (Laptop) GetContent(param string) string {
	return "Get content from Laptop: " + param
}

type Desktop struct{}

func (Desktop) GetContent(param string) string {
	return "Get content from Desktop: " + param
}

在实际业务中(见下面代码),需要根据不同的场景来选择使用 Desktop 或者 Laptop 的 GetContent 方法。

与 java 不同的是,虽然 c 同样要依赖于抽象的接口而非具体的实现类,但是 Desktop 和 Laptop 这两个实现类并不需要同时实现定义出来的 ComputerIntl 接口。ComputerIntl 接口在这里更像是一种约定或协议,只要是满足这种协议的具体类就可以看作这个接口的“实现”。这种定义也叫做鸭子类型。

但是 go 的类型并不是真正的鸭子类型,是类鸭子类型。原因在于 go 的类型不是动态绑定的,而是编译时绑定的。

type ComputerIntl interface {
	GetContent(string) string
}

func getComputer(cond int) ComputerIntl {
	if cond == 1 {
		return Desktop{}
	} else if cond == 2 {
		return Laptop{}
	} else {
		panic("Wrong cond...")
	}
}

func MainCalc() {
	cond := 1
	var c ComputerIntl = getComputer(cond)
	resStr := c.GetContent("http://abc.com/")
	fmt.Println(resStr)
}

除此之外,go 的接口定义和 java 不同的是,其接口类型的定义是从使用者角度出发的。由于在 go 中没有必要要求一组类(结构体实现)必须实现某个接口才表明这组类有相同的接口行为,使用者可以根据实际情况对一组含有相同行为的类定义接口,这更好的提高了程序的灵活性。

python 中的接口

python 属于动态语言,并且在 python 中没有接口定义的关键词。原因在于对于 python 语言来说,其接口的使用是约定性的。

在下面的代码中,我们同样定义 Desktop 和 Laptop 两个计算机类,并且定义一个工厂函数,这个工厂函数根据不同的业务条件来返回不同的计算机类。

class Laptop:
    def get_content(self, param):
        return "Get content from Laptop: " + param


class Desktop:
    def get_content(self, param):
        return "Get content from Desktop: " + param


def get_computer(cond: int):
    if cond == 1:
        return Desktop()
    elif cond == 2:
        return Laptop()
    else:
        raise Exception("Wrong cond...")

在下面程序时候的时候,由于 python 是鸭子类型的语言,变量类型是动态绑定的,computer 变量可以接受任何类型的值,因此不需要显示的使用接口来定义变量的类型,至于 computer 是否可以真正的调用 get_content 方法,是开发者在开发的时候就通过约定或协议声明好的。

if __name__ == "__main__":
    cond = 1
    computer = get_computer(cond)
    res = computer.get_content("https://abc.com/")
    print(res)
javascript 中的接口

和 python 一样 javascript 也是动态类型语言。在下面代码中同样定义 Desktop 和 Laptop 两个计算机类,并且定义一个工厂函数,这个工厂函数根据不同的业务条件来返回不同的计算机类。

class Desktop {
  constructor() {}

  getContent(param) {
    return "Get content from Desktop: " + param;
  }
}

class Laptop {
  constructor() {}

  getContent(param) {
    return "Get content from Laptop: " + param;
  }
}

function getComputer(cond) {
  if (cond == 1) {
    return new Desktop();
  } else if (cond == 2) {
    return new Laptop();
  } else {
    throw new Error("Wrong cond...");
  }
}

由于 javascript 是鸭子类型的语言,变量类型是动态绑定的,computer 变量可以接受任何类型的值,因此不需要显示的使用接口来定义变量的类型,至于 computer 是否可以真正的调用 get_content 方法,是开发者在开发的时候就通过约定或协议声明好的。

function main() {
  cond = 1;
  let c = getComputer(cond);
  let resStr = c.getContent("http://abc.com/");
  console.log(resStr);
}

main();

附录

什么是鸭子类型

鸭子类型用来描述事物的外部行为而非内部结构。

比如有一只“橡皮鸭”,它是不是鸭子从使用者的角度来看,而不是定义者的角度。也就是说,从吃货角度看,这不是鸭子,因为橡皮鸭不能吃。但是从长相和行为角度,橡皮鸭长的像鸭子,并且能“游泳”,那么它就是鸭子。

静态语言和动态语言

静态语言在编译期间进行类型检查,即编译器会检查代码中的类型错误,并在编译时发现并报告这些错误。这意味着在编译时就能够发现类型错误,而不需要在运行时进行类型检查。静态类型语言的优点是可以提前发现一些常见的编程错误,例如类型不匹配、变量未初始化等,并且可以在编译时进行优化,提高程序的性能。

动态语言在运行时进行类型检查,即在程序执行期间检查代码中的类型错误,并在运行时发现并报告这些错误。这意味着在运行时才能够发现类型错误,而不是在编译时。动态类型语言的优点是编写代码更加灵活、简单,因为不需要考虑类型检查的问题,可以更快地进行开发和测试。

参考文档:

N/A

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值