Golang系列之从方法和接口重新看代码设计(二)

oop.jpg

设计和过度设计是一个永恒的话题

想去饭馆吃个饭,要先出门,左拐,拿单车,解锁,上车,前进,遇到一间饭馆,停车,上锁,进门,点菜,吃,付钱,原路返回。这是面向过程编程

想去饭馆吃个饭,需要一辆单车来代步,需要一个钱包来付钱,还需要挑一间不错的饭馆来吃。这是面向对象编程的封装

想去饭馆吃个饭,需要个代步工具,代步工具是车。单车是车,汽车也是车,小电摩也是可以,它们都有个轮子可以跑,都能让你不用走路。还需要一个钱包,功能是给钱,钱包可以是真皮包,可以是假皮包,还可以是支付宝,微信,当然裤袋也不是不行,只要里面有钱,就行。最后需要个饭馆,饭馆可以是大排档,可以是大酒楼,只要里面有东西可以吃。一个物体衍生自另一个更基本的物体,这是面向对象编程的继承

想去饭馆吃个饭,你需要一个东西,这个东西能载人,能跑。还需要一个东西,唯一的功能是能给钱。最后一个东西功能是能给饭吃。给你辆单车,一个真皮钱包,一间猪脚饭的地址,ok,这没问题,你要的功能都全了,我还可以把单车换成汽车,或者换成卡车都行,钱包也是一样,任意换品种,只要能给钱。系统功能是一系列行为的总和,而行为动态的取决于具体的执行对象。这是面向对象编程的多态

上述的设计是经典的而且有效的,因为遵循了一个开闭原则,对于扩展是开放的,对于修改是关闭的。你可以发明其他类型的车,只要它还是能载人能跑,就能替换任何用到车的地方。但是不可以对单车进行更改,不能将两个轮子换成三个轮子 ,不能将人力换成电动。一个是为了应对需求的变更,一个是为了保护之前的工作不被破坏。

那怎样才是过度设计呢?将车进一步解耦封装,车是由轮子,发动机,座位,车身等等组成,每个组件都给定义个接口,然后实现接口,然后继承到更大的组件,最后成为车。车还要有一堆对外的接口,加油的,充电的,还要加气的以备扩展等等。对于只想吃个饭的人来说,这是过度设计了,而对于造车的人,这确是合理的设计。所以,良好的设计和过度的设计都是相对的,不能一概而论,然而,受不良书籍和不良大学课堂的影响,滥用面向对象导致过度设计的项目却到处可见,尤其是由Java写的项目 : )

相信或不相信程序员是另一个永恒的话题

C语言选择相信,Java选择不相信,C++让你自己选择相信自己或不相信自己,动态语言如Python则选择放纵程序员。

在C里面,你可以做任何你想做的事情,也要承受任何将导致的后果。你完全可以自己实现一套封装,继承,多态,只要你相信自己并且相信你的队友。

在Java里面,除了要确认你自己不值得信任外,还要明白你的队友也是不可信任的。你要写出这样的代码:即使你队友是猪,也不会写出危害系统运行的代码(因为即使有,也会编译不通过不让有运行的机会)。基于此目的,你要做的工作成倍增加,解耦解耦,封装封装,class到处都是。也因为此,Java天生适合有猪队友参与的大项目。

其中的区别就在,语言本身语法对你限制的多少和编译器为你做的工作的多少。

语言不只是一个工具,它会影响你的思维

语言确实是一门工具,却不只是一门工具。语言本身时时刻刻在影响着你的项目设计和代码设计。

我想说的观点是,不要陷进一门语言里,以为全世界的代码都应该按一种方式来写。

我喜欢Golang的一个点,就是它提供了另外一种思考的方式。

Golang的方法

"Although there is no universally accepted definition of object-oriented programming, for our purposes, an object is simply a
value or variable that has methods, and a method is a function assiociated with a particular type."

Golang没有提供像class这样的声明方式,而是为类型提供方法的声明来实现面向对象的。
举个例子:

package main
import "fmt"
type Vehicle struct {
    Name string
    Seats  int
}
func (v Vehicle) Run {
    fmt.Println("i am running")
}
func main() {
    v := Vehicle{"bicycle",1}
    v.Run()
}

方法的声明比函数的声明多了个接受者(receiver): (v Vehicle)
接收者有两种形式,一种是(t T),另外一种是(t *T)。不难理解,前一种是只能引用值,后一种才能改变值。
关于接受者用哪种方式,记得这两条就好了:

类型 *T的可调用方法集包含接受者为 *T或T的所有方法集
类型T的可调用方法集包含接受者为T的所有方法

Golang的接口

沿着上面的例子

type Transportation interface {
    Run()
}
func GoToEatBy(tr Transportation) {
    tr.Run()
}
func main(){
    v := Vehicle{"bicycle",1}
    GoToEatBy(tr)
}

我们声明了一个Transportation接口,这个接口要求一个Run方法,而函数GoToEatBy需要一个实现了这个接口的对象,而不管这个对象究竟是什么类型,只要它实现了这个接口就可以。

You don't need to know what it is, but you know what it can do.

灵活运用上述两种方式,写出面向对象的代码不在话下。结构体和方法提供了封装和继承(通过在一个struct/interface嵌入其他struct/interface来实现)的功能,接口提供了多态的功能。虽然都是在实现OOP,但是和C++/Java写出来的实现却会很不一样。所以,代码,不是只有一种写法。Golang提供的方式,简洁却不简单。

不同的思维方式

有没有发现?我们是实现Vehicle的Run在先,后定义的Transportation接口。Vehicle的实现者甚至都不用知道有朝一日它被用于需要Transportation的地方,这就是被Golang发明者津津乐道之处。又要拿Java举例子了,在Java中,对接口的实现是要显示声明的,你要明明白白地说明,我现在要实现这个接口了,当你要为一个类添加对一个接口的实现的时候,你就不得不去修改源码,这就破坏了开闭原则。而在Golang中,你要为一个类型实现一个新的接口,你压根不用管原先的代码,你只要为其添加必须的方法,然后就可以了,是不是很自由?

如果让你来设计语言,你要怎么选择呢,一个类实现一个接口,究竟需不需要在类中显示的去声明,明明白白告诉编译器我知道我在做什么?

回答这个问题要先回答开头的两个大问题:
1,你希望程序员怎样用你的语言来设计代码。
2,你相不相信这些用你语言的程序员。

借用温赵轮中的老赵(他明确反对Golang的接口)举过的一个例子来说明相信不相信程序员的区别。

interface IPainter {
    void Draw();
}
interface ICowBoy {
    void Draw();
}

在英语中Draw同时具有“画画”和“拔枪”的含义,因此对于画家(Painter)和牛仔(Cow Boy)都可以有Draw这个行为,
但是两者的含义截然不同。假如我们实现了一个“小明”类型,他明明只是一个画家,但是我们却让他去跟其他牛仔决斗,这样
就等于让他去送死嘛。

简单地说,假如接口不能保证行为特征,则“面向接口编程”没有意义。

老赵的观点是,不能只从表面去理解一个接口,还要关注这个接口规定了的每个行为的“特征”。不然,会产生很多误用。

这其实就归结到,程序员他究竟知不知道他在干什么?如果他睡迷糊了用"小明"类型去决斗,Java的编译器会阻止他,而Golang不会。一个牺牲了灵活性,一个牺牲了安全性。

当然,我选择简洁的方式,有猪队友参与的时候例外。


原文转自谢培阳的博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,822评论 25 707
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • 第五个梦境代表最后一个梦境 它代表不幸 代表我的学号 代表我的终了 后来他们是这样说的 「就让不幸结束不幸」 第五...
    季节死阅读 392评论 0 2
  • 当你得意的时候,危险可能正来临。 那一年,我将自己的外贸公司的营业额做到了9位数。一切超乎我的想像。 那天,看着电...
    沈默君阅读 248评论 0 0
  • 踏上列车,一路驰骋,直指心之所向。疾驰而行的列车带着回乡的人儿,冲破黑暗无边的夜幕,迎来黎明的第一缕曙光,映入眼帘...
    憨憨憨霓儿阅读 357评论 0 0