很多人都说用go代替php或者java的最大短板就是写业务太反人类。经过最近的一些使用,发现确实与常见的java或者c++有些区别,在这里说明一下。

go继承多态的现状

go没有类的概念

也没有所谓的继承多态。所以按照常规用法开发相应的业务逻辑,确实不适用。

go只有struct和interface

struct类似于类,interface就是接口。没有c++这种,类里面有纯虚函数、虚函数的概念。

方法不需要写在struct中,定义方法时,指定是哪个struct的,就可以自动绑定。

在go中变量/函数名称大写,表示外部可见,小写就是不可见。可以认为用大小写表示了public和private的概念,没有protect的用法。

非侵入式

只要struct实现了interface所有方法,就自动帮你绑定,认为struct继承了interface,并不需要在struct中明确写出。这种叫做非侵入式继承,各有利弊。

type FI interface {
	T()
}

type DS struct {
	a int
}

// 直接实现接口方法,不用在结构体明确指出
func (s *DS) T() {
	fmt.Println(s.a)
}
案例

有一个公共模块A,A中有一个公共函数T,T中调用函数F,B继承A,并且重新实现了F,但是没有实现T。要求用B调用T的时候,可以调用到B实现的F。

C++实现

按照常见的有类概念的函数,实现如下

//.h
class A
{
public:
    int b{};
    void T();
    //纯虚函数和虚函数效果一样
    virtual void F() = 0;
};

class B : public A
{
public:
    void F() override;
};

//.cpp
void B::F()
{
    std::cout << b << std::endl;
}

void A::T()
{
    F();
}

//main
void test(A* ma)
{
    ma->T();
}

int main()
{
    B mb;
    mb.b = 11;
    test(&mb);
    return 0;
}
T

把统一调用函数写在父类;在子类对功能函数重新实现;调用逻辑都使用父类指针。

B1B2AFvoid test(A* ma)

go实现

interface 参数

//类似于父类
type DS struct {
	b int
}

//DS中公共调用的函数
func (s *DS) T() {
	s.F()
}

func (s *DS) F() {
	fmt.Println("DS")
}

type DS1 struct {
	DS
}

func (s *DS1) F() {
	fmt.Println("DS1")
}

func callfunc(s *DS) {
	s.T()
}

func main() {
	ds1 := DS1{}
	callfunc(&ds1)
}

上面会报错,*DS1不能转换为*DS,那么我们用go的interface进行转换,修改为如下

type DS struct {
	b int
}

func (s *DS) T() {
	s.F()
}

func (s *DS) F() {
	fmt.Println("DS")
}

type DS1 struct {
	DS
}

func (s *DS1) F() {
	fmt.Println("DS1")
}

func callfun(s interface{}) {
	switch s.(type) {
	case *DS1:
		ss := s.(*DS1)
		ss.T()
	}
}

func main() {
	ds1 := DS1{}
	callfun(&ds1)
}
DS1TDSDSFDS

再做如下修改

func callfun(s interface{}) {
	switch s.(type) {
	case *DS1:
		ss := s.(*DS1)
		ss.F()
	}
}

这样是可以了,只是还与S1有关系吗?

interface 接口

先看下面的实现

//类似父类
type DS struct {
	A int
}

//类似父类接口
type FI interface {
	F()
}

//子类实现
type DS1 struct {
	FI
	DS
}
func (s *DS1) F() {
	fmt.Println("111", s.A)
}

//子类实现
type DS2 struct {
	FI
	DS
}
func (s *DS2) F() {
	fmt.Println("222", s.A)
}

//统一调用
func T(f1 FI) {
	f1.F()
}
func test() {
	ds1 := &DS1{}
	ds2 := &DS2{}
	T(s1)
	T(s2)
}
TDS1/DS2T
Tinterface FIDS1DS2Tstruct DSDSinterface FIDSFDSinterface FIFinterface 参数TDSDSF

把子类作为interface放入到父类中

// 父类接口,定义重写的函数
type FI interface {
	F()
}

// 父类,把接口作为一个成员变量,类似于把子类指针作为成员变量
type DS struct {
	S FI
}
//父类实现统一调用函数
func (d *DS) T() {
    d.S.F()
}

//子类
type DS1 struct {
}
type DS2 struct {
}
// 子类实现重写方法
func (s *DS1) F() {
	fmt.Println("111")
}
func (s *DS2) F() {
	fmt.Println("222")
}

func test() {
	ds := DS{}
	ds1 := &DS1{}
	ds.S = ds1
    ds.T()
}
dsds1DS1Fint bFIDSDS1DS1DSDS1DS2
总结

go的设定,接口就是接口,结构体就是结构体,A就是A,B就是B,避免一个类中函数越来越多,越来越复杂,其他语言通过人为约定控制代码的混乱,go直接从语法自由度上做了限制。

go是为了解决并发、性能和C/C++低级语言的缺陷产生的,这就导致go即灵活,又不灵活。为了性能,必须要灵活,有指针的设定;为了避免编码错误,又要限制语法自由度,这就是为什么有人说用go写业务简直是反人类。

go适合做中间件,流媒体、网络数据通信等,逻辑单一,性能要求高。而对于大型复杂的互联网服务端,可能不太合适。