Go 开发关键技术指南
Go 开发指南
点击 查看原文 可查看清晰知识大图!
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