Go 开发关键技术指南

177 阅读4分钟

Interfaces Go 在类型和接口上的思考是:

Go 类型系统并不是一般意义的 OO,并不支持虚函数; Go 的接口是隐含实现,更灵活,更便于适配和替换; Go 支持的是组合、小接口、组合+小接口; 接口设计应该考虑正交性,组合更利于正交性。 Type System Go 的类型系统是比较容易和 C++/Java 混淆的,特别是习惯于类体系和虚函数的思路后,很容易想在 Go 走这个路子,可惜是走不通的。而 interface 因为太过于简单,而且和 C++/Java 中的概念差异不是特别明显,所以本章节专门分析 Go 的类型系统。

先看一个典型的问题 Is it possible to call overridden method from parent struct in golang? 代码如下所示:

package main

import (
  "fmt"
)

type A struct {
}

func (a *A) Foo() {
  fmt.Println("A.Foo()")
}

func (a *A) Bar() {
  a.Foo()
}

type B struct {
  A
}

func (b *B) Foo() {
  fmt.Println("B.Foo()")
}

func main() {
  b := B{A: A{}}
  b.Bar()
}

本质上它是一个模板方法模式 (TemplateMethodPattern),A 的 Bar 调用了虚函数 Foo,期待子类重写虚函数 Foo,这是典型的 C++/Java 解决问题的思路。

我们借用模板方法模式 (TemplateMethodPattern) 中的例子,考虑实现一个跨平台编译器,提供给用户使用的函数是 crossCompile,而这个函数调用了两个模板方法 collectSource 和 compileToTarget:

public abstract class CrossCompiler {
  public final void crossCompile() {
    collectSource();
    compileToTarget();
  }
  //Template methods
  protected abstract void collectSource();
  protected abstract void compileToTarget();
}

C 版,不用 OOAD 思维参考 C: CrossCompiler use StateMachine,代码如下所示:

// g++ compiler.cpp -o compiler && ./compiler
#include <stdio.h>

void beforeCompile() {
  printf("Before compile\n");
}

void afterCompile() {
  printf("After compile\n");
}

void collectSource(bool isIPhone) {
  if (isIPhone) {
    printf("IPhone: Collect source\n");
  } else {
        printf("Android: Collect source\n");
    }
}

void compileToTarget(bool isIPhone) {
  if (isIPhone) {
    printf("IPhone: Compile to target\n");
  } else {
        printf("Android: Compile to target\n");
    }
}

void IDEBuild(bool isIPhone) {
  beforeCompile();

  collectSource(isIPhone);
  compileToTarget(isIPhone);

  afterCompile();
}

int main(int argc, char** argv) {
  IDEBuild(true);
  //IDEBuild(false);
  return 0;
}

C 版本使用 OOAD 思维,可以参考 C: CrossCompiler,代码如下所示:

// g++ compiler.cpp -o compiler && ./compiler
#include <stdio.h>

class CrossCompiler {
public:
  void crossCompile() {
    beforeCompile();

    collectSource();
    compileToTarget();

    afterCompile();
  }
private:
  void beforeCompile() {
    printf("Before compile\n");
  }
  void afterCompile() {
    printf("After compile\n");
  }
// Template methods.
public:
  virtual void collectSource() = 0;
  virtual void compileToTarget() = 0;
};

class IPhoneCompiler : public CrossCompiler {
public:
  void collectSource() {
    printf("IPhone: Collect source\n");
  }
  void compileToTarget() {
    printf("IPhone: Compile to target\n");
  }
};

class AndroidCompiler : public CrossCompiler {
public:
  void collectSource() {
      printf("Android: Collect source\n");
  }
  void compileToTarget() {
      printf("Android: Compile to target\n");
  }
};

void IDEBuild(CrossCompiler* compiler) {
  compiler->crossCompile();
}

int main(int argc, char** argv) {
  IDEBuild(new IPhoneCompiler());
  //IDEBuild(new AndroidCompiler());
  return 0;
}

我们可以针对不同的平台实现这个编译器,比如 Android 和 iPhone:

public class IPhoneCompiler extends CrossCompiler {
  protected void collectSource() {
    //anything specific to this class
  }
  protected void compileToTarget() {
    //iphone specific compilation
  }
}

public class AndroidCompiler extends CrossCompiler {
  protected void collectSource() {
    //anything specific to this class
  }
  protected void compileToTarget() {
    //android specific compilation
  }
}

在 C++/Java 中能够完美的工作,但是在 Go 中,使用结构体嵌套只能这么实现,让 IPhoneCompiler 和 AndroidCompiler 内嵌 CrossCompiler,参考 Go: TemplateMethod,代码如下所示:

package main

import (
  "fmt"
)

type CrossCompiler struct {
}

func (v CrossCompiler) crossCompile() {
  v.collectSource()
  v.compileToTarget()
}

func (v CrossCompiler) collectSource() {
  fmt.Println("CrossCompiler.collectSource")
}

func (v CrossCompiler) compileToTarget() {
  fmt.Println("CrossCompiler.compileToTarget")
}

type IPhoneCompiler struct {
  CrossCompiler
}

func (v IPhoneCompiler) collectSource() {
  fmt.Println("IPhoneCompiler.collectSource")
}

func (v IPhoneCompiler) compileToTarget() {
  fmt.Println("IPhoneCompiler.compileToTarget")
}

type AndroidCompiler struct {
  CrossCompiler
}

func (v AndroidCompiler) collectSource() {
  fmt.Println("AndroidCompiler.collectSource")
}

func (v AndroidCompiler) compileToTarget() {
  fmt.Println("AndroidCompiler.compileToTarget")
}

func main() {
  iPhone := IPhoneCompiler{}
  iPhone.crossCompile()
}

执行结果却让人手足无措:


# Expect
IPhoneCompiler.collectSource
IPhoneCompiler.compileToTarget

# Output
CrossCompiler.collectSource
CrossCompiler.compileToTarget

Go 并没有支持类继承体系和多态,Go 是面向对象却不是一般所理解的那种面向对象,用老子的话说“道可道,非常道”。

实际上在 OOAD 中,除了类继承之外,还有另外一个解决问题的思路就是组合 Composition,面向对象设计原则中有个很重要的就是 The Composite Reuse Principle (CRP),Favor delegation over inheritance as a reuse mechanism,重用机制应该优先使用组合(代理)而不是类继承。类继承会丧失灵活性,而且访问的范围比组合要大;组合有很高的灵活性,另外组合使用另外对象的接口,所以能获得最小的信息。

C++ 如何使用组合代替继承实现模板方法?可以考虑让 CrossCompiler 使用其他的类提供的服务,或者说使用接口,比如 CrossCompiler 依赖于 ICompiler:

public interface ICompiler {
  //Template methods
  protected abstract void collectSource();
  protected abstract void compileToTarget();
}

public abstract class CrossCompiler {
  public ICompiler compiler;
  public final void crossCompile() {
    compiler.collectSource();
    compiler.compileToTarget();
  }
}

C 版本可以参考 C: CrossCompiler use Composition,代码如下所示:

// g++ compiler.cpp -o compiler && ./compiler
#include <stdio.h>

class ICompiler {
// Template methods.
public:
  virtual void collectSource() = 0;
  virtual void compileToTarget() = 0;
};

class CrossCompiler {
public:
  CrossCompiler(ICompiler* compiler) : c(compiler) {
  }
  void crossCompile() {
    beforeCompile();

    c->collectSource();
    c->compileToTarget();

    afterCompile();
  }
private:
  void beforeCompile() {
    printf("Before compile\n");
  }
  void afterCompile() {
    printf("After compile\n");
  }
  ICompiler* c;
};

class IPhoneCompiler : public ICompiler {
public:
  void collectSource() {
    printf("IPhone: Collect source\n");
  }
  void compileToTarget() {
    printf("IPhone: Compile to target\n");
  }
};

class AndroidCompiler : public ICompiler {
public:
  void collectSource() {
      printf("Android: Collect source\n");
  }
  void compileToTarget() {
      printf("Android: Compile to target\n");
  }
};

void IDEBuild(CrossCompiler* compiler) {
  compiler->crossCompile();
}

int main(int argc, char** argv) {
  IDEBuild(new CrossCompiler(new IPhoneCompiler()));
  //IDEBuild(new CrossCompiler(new AndroidCompiler()));
  return 0;
}

我们可以针对不同的平台实现这个 ICompiler,比如 Android 和 iPhone。这样从继承的类体系,变成了更灵活的接口的组合,以及对象直接服务的调用:

public class IPhoneCompiler implements ICompiler {
  protected void collectSource() {
    //anything specific to this class
  }
  protected void compileToTarget() {
    //iphone specific compilation
  }
}

public class AndroidCompiler implements ICompiler {
  protected void collectSource() {
    //anything specific to this class
  }
  protected void compileToTarget() {
    //android specific compilation
  }
}

更多Go 开发技术知识请查看Go 开发关键技术指南