开篇先抛出问题,Go语言算不算是一门面向对象编程语言?
要回答这个问题,我查阅了许多的外文资料,发现对于面向对象编程语言,并不存在一个严格的定义。
但从实际的角度出发,只要一门语言拥有类,对象的概念,以及提供了对应的语法,就可以用来实现面向对象编程了,所以从这个角度来看,Go语言是可以被认为是一门面向对象编程语言的。
那么抛出第二个问题,Go语言有没有实现面向对象的四大特性? 封装,抽象,继承,多态?
这个问题我不准备全部回答,但关于「多态」,Go语言很明确地提供了非常灵活的语法支持。
本篇文章通过Go的多态实现思路,来探讨如何理解「多态」,以及如何真正地在项目中使用,而不是随处可见的「小狗,动物,吠」这种虽然浅显易懂但不够实际落地的例子。
一,多态是什么?
多态的定义比较抽象,这里我贴一下百度词条的
多态(Polymorphism)按字面的意思就是“多种状态”。在面向对象语言中,接口的多种不同的实现方式即为多态。
还是用具体的例子来理解吧,Go的fmt包相信大家都用过,其中fmt.Printf()更是常用的函数,我们先跳到fmt包里,去看一眼这个方法
// Fprintf formats according to a format specifier and writes to w.
// It returns the number of bytes written and any write error encountered.
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintf(format, a)
n, err = w.Write(p.buf)
p.free()
return
}
// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.
func Printf(format string, a ...interface{}) (n int, err error) {
return Fprintf(os.Stdout, format, a...)
}
复制代码
可见到,Printf基于Fprintf实现,在Printf中,显式调用了Fprintf(), 并传入了 os.Stdout
我们看一下Fprintf对os.Stdout的使用方式,可见到Fprintf认为os.Stdout实现了io.Writer,所以认为可以调用io.Writer身上应该具有的Write()方法。
这里有两个比较重要的信息,一个是Go语言的接口语法,一个是Duck Typing。
Go语言的接口语法我不深入介绍,就贴一下源码
type Writer interface {
Write(p []byte) (n int, err error)
}
复制代码
可见Writer接口只具有一个方法,Write(..),实现它很简单,因为接口是面对功能,不面对实现,所以你完全可以写一个类似下面的例子来实现Write()
func Write(p []byte) (n int, err error) {
return len(p), nil
}
复制代码
好了,这就一个常见的接口语法,只不过在Go语言中,实现一个接口,并不用显式说明,什么是显式说明,举个例子
比如在PHP中,如果某个类实现了一个接口,必须显式做出如下声明
class UriResolver implements UriResolverInterface {...}
复制代码
但是在Go语言中,是采用Duck Typing来实现接口实现,什么是Duck Typing?
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
具体到上面Fprintf的例子,也就是说:只要传进来的参数对象实现了Write()方法,就可以认为它是一个io.Writer。
这对Go实现多态带来了极大的方便,下面我们实现一个简单的例子,写一个Sprintf()
func Sprintf(format string, args ...interface{}) string {
var buf bytes.Buffer
Fprintf(&buf, format, args...)
return buf.String()
}
复制代码
在Sprintf中,我们显示调用Fprintf,并且传入的io.Writer实现者是一个bytes.Buffer对象,这个对象不出意外,也实现了Write()。
所以对于多态,我个人的片面理解就是,对于一个通俗功能,有着「统一的调用方式」和「多种形态的实现」。
比如对于图片的存储功能,有着本地存,云端存等不同形态的实现,但它们被调用的方式统一是 local.Save()和Cloud.Save()
二,多态的意义
从上面的例子可以解读出两个现实意义:
1,因为有了Fprintf,在屏幕上打印(Stdout)和字符串生成(bytes.Buffer)可以不用分别实现「打印」这个功能,不用写StdoutPrintf和BufferPrintf,这就有利于代码复用;
2,以后如果想把数据发送到某个文件里,就可以给Fprintf传入一个file.file之类的对象,只要file.file实现了Write(),这就有利于代码拓展;
三, 多态的实现方式
其实在第一点的例子里已经可以看到,上述的接口(php显式声明),Duck Typing(隐式实现)都是多态的实现方式。
如果你是Java,PHP开发,你也可以通过「继承」->「重写方法」来实现多态。
比如 local和cloud都是继承自storage.
local和cloud都分别重写了storage中的save()方法,那么在就可以在如下的调用中实现多态:
<?php
Class Storage {
public save(string $pictureUrl) {
//...
}
}
Class Local extends Storage {
public save(string $pictureUrl) {
$this->saveToLocal($pictureUrl);
}
}
Class Cloud extends Storage {
public save(string $pictureUrl) {
$this->saveToOss($pictureUrl);
}
}
function savePicture(Storage $saver, string $url) {
$saver->save($url);
}
$url = '../xxx.jpg';
$local = new Local();
$cloud = new Cloud();
savePicture($local, $url);
savePicture($cloud, $url);
复制代码
到此,本篇就结束了。
参考
- Go程序设计语言 The Go Programming Language
- 百度词条
- en.wikipedia.org/wiki/Duck_t….
有疑问加站长微信联系(非本文作者)