美文网首页Golang合集Go首页投稿(暂停使用,暂停投稿)
Golang系列之从方法和接口重新看代码设计(二)

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

作者: 谢培阳 | 来源:发表于2016-05-05 19:02 被阅读1471次
    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不会。一个牺牲了灵活性,一个牺牲了安全性。

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


    原文转自谢培阳的博客

    相关文章

      网友评论

      • dcd48d64d277: :joy: 初学GO,觉得最骚的就是接口这个概念
      • MobileTimes:我极度认同老赵的观点,我才刚刚学习Go语言,但我对Go的这种隐式接口强烈质疑,网上一大堆人云亦云,但这些所谓的粉丝居然没有一个人给出合理的解释,到底不同功能的接口使用了同名方法要怎么解决?那请本文的作者,你给我一个答案吧!这不是一句“猪队友”可以一笔带过的,大型工程不是一个人的工程,团队内不同的人员负责定义不同的接口有什么奇怪?而不同的接口里面有相同的方法名又有什么奇怪? 我甚至怀疑那群人云亦云的人连基本的架构思想都没有,而老赵正好点中要害。
        本文作者,你能给我一个答案吗?
        Mpc2017:我觉得这个不能单从接口来讲,就如上文的例子来说,只是说了在一个包内定义接口产生冲突的情况,但是你别忘了,golang还有package啊。我只需要定义一个画家的package和一个牛仔的package,我爱用哪个包的接口便用哪个包的接口了,所以这个也是一个不存在的问题了

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

      本文链接:https://www.haomeiwen.com/subject/euidrttx.html