一、 其他语言的接口
面向对象编程(OOP)中三个基本特征分别是封装,继承,多态。在 Go 语言中封装和继承是通过 struct 来实现的,而多态则是通过接口(interface)来实现的。
接口是一种重要的实现运行时多态的方式,也是一种协议。它规范了传入的对象所必须具备的某些特征,从而保证在调用时既不会发生错误又不需要提前检查。虽然继承也能起到相同的效果,但是很多场景下使用继承会导致逻辑关系混乱,不利于抽象。
1.1 C#接口
官网对C#接口的定义如下:接口定义协定。 实现该协定的任何 class 或 struct 必须提供接口中定义的成员的实现。 从 C# 8.0 开始,接口可为成员定义默认实现。 它还可以定义 static 成员,以便提供常见功能的单个实现。 从 C# 11 开始,接口可以定义 static abstract 或 static virtual 成员来声明实现类型必须提供声明的成员。 通常,static virtual 方法声明实现必须定义一组重载运算符。
interface ISampleInterface
{
void SampleMethod();
}
class ImplementationClass : ISampleInterface
{
// Explicit interface member implementation:
void ISampleInterface.SampleMethod()
{
// Method implementation.
}
static void Main()
{
// Declare an interface instance.
ISampleInterface obj = new ImplementationClass();
// Call the member.
obj.SampleMethod();
}
}
1.2 C++接口
C++接口的广泛定义如下:所谓的接口,即将内部实现细节封装起来,外部用户用过预留的接口可以使用接口的功能而不需要知晓内部具体细节。C++中,通过类实现面向对象的编程,而在基类中只给出纯虚函数的声明,然后在派生类中实现纯虚函数的具体定义的方式实现接口,不同派生类实现接口的方式也不尽相同,从而实现多态。
#include <iostream>
using namespace std;
class Shape
{
public:
// 提供接口框架的纯虚函数
virtual int getArea() = 0;
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// 派生类
class Rectangle: public Shape
{
public:
int getArea()
{
return (width * height);
}
};
class Triangle: public Shape
{
public:
int getArea()
{
return (width * height)/2;
}
};
二、构建对象
在基础篇一:变量类型 – 码学堂 (newxuetang.com)中我们介绍了struct,在C#、C++和Java中有对象的定义方法。在Go语言中,用结构体代替对象的声明,用接口代替对象的方法。
构建struct的method样例如下:
package main
import "fmt"
type Triangle struct {
A float64 `json:"a"`
B float64 `json:"b"`
C float64 `json:"c"`
}
func isTriangle(triangle Triangle) bool {
if triangle.A+triangle.B > triangle.C && triangle.A-triangle.B < triangle.C {
return true
}
return false
}
func area(triangle Triangle) float64 {
if isTriangle(triangle) {
//海伦公式:s=sqrt(p*(p-a)(p-b)(p-c))
p := (triangle.A + triangle.B + triangle.C) / 2
a := triangle.A
b := triangle.B
c := triangle.C
return math.Sqrt(p * (p - a) * (p - b) * (p - c))
} else {
return 0
}
}
func main() {
triangle := Triangle{A: 3, B: 4, C: 5}
fmt.Println("三角形的边长为:", triangle)
fmt.Println("三角形的面积=", area(triangle))
}
这段代码可以计算出任意边长组合的三角形,但是area()不是Triangle的方法实现的,而是将Triangle对象作为参数传入函数,在函数内部计算得到返回结果。
这样实现对象的方法有简单方便的特点,但是当需要添加圆形、四边形等其他平面图形的时候,就需要替换函数名或者添加不同名的包名。
2.1 添加接口
接口的引入,很好的解决了上述的麻烦。因为函数的发出对象不同,所以Go语言可以识别同名的函数。
为struct添加接口的样例如下:
package main
import "fmt"
//公共接口
type TriangleMethod interface {
IsTriangle() bool
Area() float64
Say()
}
//三角形类
type Triangle struct {
A float64 `json:"a"`
B float64 `json:"b"`
C float64 `json:"c"`
}
//Say()方法
func (triangle Triangle) Say() {
if triangle.IsTriangle() {
fmt.Println("I am a triangle!")
} else {
fmt.Println("I am not a triangle!")
}
}
//IsTriangle()方法,判断三条边能否构成三角形
func (triangle Triangle) IsTriangle() bool {
if triangle.A+triangle.B > triangle.C && triangle.A-triangle.B < triangle.C {
return true
}
return false
}
//Area()方法,计算三角形面积
func (triangle Triangle) Area() float64 {
if triangle.IsTriangle() {
//海伦公式:s=sqrt(p*(p-a)(p-b)(p-c))
p := (triangle.A + triangle.B + triangle.C) / 2
a := triangle.A
b := triangle.B
c := triangle.C
return math.Sqrt(p * (p - a) * (p - b) * (p - c))
} else {
return 0
}
}
func main() {
Triangle := Triangle{A: 10, B: 10, C: 10}
trianglemethod := TriangleMethod(Triangle)
trianglemethod.Say()
fmt.Println(trianglemethod.Area())
}
2.2 接口赋值
在Go语言中接口赋值分为如下两种情况:
- 将对象实例赋值给接口;
- 将一个接口赋值给另一个接口。
将某种类型的对象实例赋值给接口,这要求对象实例实现了接口要求的所有方法,样例如下:
package main
import "fmt"
type Integer int
func (a Integer) Less(b Integer) bool {
return a < b
}
func (a *Integer) Add(b Integer) {
*a += b
}
//定义接口LessAdder
type LessAdder interface {
Less(b Integer) bool
Add(b Integer)
}
func main() {
var a Integer = 5
//将对象实例赋值给接口
var b LessAdder = &a
//5 < 10 ,true
fmt.Println(b.Less(10))
//a = 5 + 10 = 15
b.Add(10)
//15 < 10 ,false
fmt.Println(b.Less(10))
}
接下来讨论另外一种情况;将一个接口赋值给另一个接口。在Go语言中,只要两个接口拥有相同的方法列表(无次序要求),那么它们就是等同的,可以相互赋值。
2.3 接口查询
package main
import "fmt"
type IFile interface {
Read()
Write()
}
type IReader interface {
Read()
}
type File struct {
}
func (f *File) Read() {
}
func (f *File) Write() {
}
func main() {
f := new(File)
var f1 IFile = f // ok 因为FIle实现了IFile中的所有方法
var f2 IReader = f1 // ok 因为IFile中包含IReader中所有方法
// var f3 IFile = f2 // error 因为IReader并不能满足IFile(少一个方法)
//
var f3 IReader = new(File) // ok 因为File实现了IReader中所有方法
// var f4 IFile = f3 // error 因为IReader并不能满足IFile 同上..如何解决呢? 要用接口查询
// 接口查询
// 这个if语句检查file1接口指向的对象实例是否实现了IFile接口
// 如果实现了
// 则执行特定的代码。
// 注意:这里强调的是对象实例,也就是new(File)
// File包含IFile里所有的方法
// 所以ok = true
if f5, ok := f3.(IFile); ok {
fmt.Println(f5)
fmt.Println(".............")
}
// 询问接口它指向的对象是否是某个类型
// 这个if语句判断file1接口指向的对象实例是否是*File类型
// 依然ok
if f6, ok := f3.(*File); ok {
fmt.Println(f6)
}
fmt.Println(f1, f2, f3)
if b := 1; true {
fmt.Println(b, "判断1==1;true")
}
if c := 1; false {
fmt.Println(c, "判断1==1;false")
}
}
2.4 接口组合
像类型组合一样,Go语言同样支持接口组合。io包定义了写入器(Writer)、关闭器(Closer)和写入关闭器(WriterCloser)3个接口,代码如下:
// 写入接口
type Writer interface {
Write(p []byte)
}
// 关闭接口,实现内存资源的释放
type Closer interface {
Close() error
}
// 写入关闭接口,同时拥有Writer和Closer的特性
type WriteCloser interface {
//有两个接口嵌套组合,生成扩展接口
Writer
Closer
}
在应用中使用接口组合,样例如下:
package main
import (
"io"
"testing"
)
// // io包中的写入接口
// type Writer interface {
// Write(p []byte)
// }
// // io包中的关闭接口,实现内存资源的释放
// type Closer interface {
// Close() error
// }
// // io包中的写入关闭接口,同时拥有Writer和Closer的特性
// type WriteCloser interface {
// Writer // 由两个接口嵌套组合,生成扩展接口
// Closer
// }
// 声明一个空的设备结构体,用于模拟一个虚拟设备
type device struct{}
// 向结构体绑定类似io.Writer的Write方法
func (d *device) Write(p []byte) (n int, err error) {
return 0, nil
}
// 向结构体绑定类似io.Closer的Close方法
func (d *device) Close() error {
return nil
}
func main() {
// device结构体实例化wc,由于device类型已经实现了io.WriteCloser的所有内嵌接口,因此wc的类型可以看作是io.WriteCloser接口类型(被隐式转换了)
var wc io.WriteCloser = new(device)
// wc实例调用Write方法,写入数据
wc.Write(nil)
// wc实例调用Close方法,关闭设备
wc.Close()
// 声明写入器writeOnly,类型是io.Writer,并赋值device新实例
var writeOnly io.Writer = new(device)
// writeOnly实例只调用Write方法,写入数据
writeOnly.Write(nil)
}