Go面向对象

本来想写下相关的内容,找材料时,发现了这篇不错的内容,强烈推荐,关于面向对象的基本用法都讲到了。

原文:Go面向对象
原英文:Go Object Oriented Design

正文:

Go是一个完全面向对象的语言。例如,它允许基于我们定义的类型的方法,而没有像其他语言一样的装箱/拆箱操作。

Go没有使用classes,但提供很多相似的功能:

·通过嵌入实现的自动消息委托

·通过接口实现多台

·通过exports实现的命名空间

Go语言中没有继承。忘记is-a的关系,而是就组合而言的面向对象设计。

“使用经典的继承始终是可选的;每个问题都可以通过其他方法得到解决” - Sandi Metz

通过例子说明组合

最近阅读了一篇Ruby的面向对象编程实践, 我决定使用Go语言翻译这个例子。

Chapter 6说明了一个简单的问题。维修工需要知道自行车出行需要带上的备件,决定于哪一辆自行车已经被租出去。问题可以通过经典的继承来解决,山地车和公路自行车是自行车基类的一个特殊化例子。Chapter 8使用组合改写了同一个例子。我很高兴这个例子翻译成Go。让我们看看。

Packages(包)

  1. package main
  2. import "fmt"

包提供了命名空间概念. main() 函数是这个包的入口函数. fmt包提供格式化功能

Types(类型)

  1. type Part struct {
  2.     Name        string
  3.     Description string
  4.     NeedsSpare  bool
  5. }

我们定义了一个新的类型名为Part, 非常像c的结构体

  1. type Parts []Part

Parts类型是包含Part类型的数组切片, Slice可以理解为动态增长的数组, 在Go中是很常见的.

我们可以在任何类型上声明方法,  所以我们不需要要再去封装 []Part, 这意味着 Parts 会拥有slice的所有行为, 再加上我们自己定义的行为方法.

方法

  1. func (parts Parts) Spares() (spares Parts) {
  2.     for _, part := range parts {
  3.         if part.NeedsSpare {
  4.             spares = append(spares, part)
  5.         }
  6.     }
  7.     return spares
  8. }

Go中定义方法就像一个函数,除了它有一个显式的接收者,紧接着func之后定义。这个函数利用命名返回变量,并为我们初始化备件。

方法的主体十分简单。我们重复parts,忽略索引的位置(_),过滤parts后返回。append builtin 需要分配和返回一个大的切片,因为我们并没有预先分配好它的容量。

这段代码没有ruby代码来得优雅。在Go语言中有过滤函数,但它并非是builtin.

内嵌

  1. type Bicycle struct {
  2.     Size string
  3.     Parts
  4. }

自行车由Size和Parts组成。没有给Parts指定一个名称,我们是要保证实现 内嵌。这样可以提供自动的委托,不需特殊的声明,例如bike.Spares()和bike.Parts.Spares()是等同的。

如果我们向Bicycle增加一个Spares()方法,它会得到优先权,但是我们仍然引用嵌入的Parts.Spares()。这跟继承十分相似,但是内嵌并不提供多态。Parts的方法的接收者通常是Parts类型,甚至是通过Bicycle委托的。

与继承一起使用的模式,就像模板方法模式,并不适合于内嵌。就组合和委托而言去考虑会更好,就如我们这个例子一样。

Composite Literals(复合语义)

  1. var (
  2.     RoadBikeParts = Parts{
  3.         {"chain""10-speed"true},
  4.         {"tire_size""23"true},
  5.         {"tape_color""red"true},
  6.     }
  7.     MountainBikeParts = Parts{
  8.         {"chain""10-speed"true},
  9.         {"tire_size""2.1"true},
  10.         {"front_shock""Manitou"false},
  11.         {"rear_shock""Fox"true},
  12.     }
  13.     RecumbentBikeParts = Parts{
  14.         {"chain""9-speed"true},
  15.         {"tire_size""28"true},
  16.         {"flag""tall and orange"true},
  17.     }
  18. )

Go提供优美的语法,来初始化对象,叫做 composite literals。使用像数组初始化一样的语法,来初始化一个结构,使得我们不再需要ruby例子中的Parts工厂。

  1. func main() {
  2.     roadBike := Bicycle{Size: "L", Parts: RoadBikeParts}
  3.     mountainBike := Bicycle{Size: "L", Parts: MountainBikeParts}
  4.     recumbentBike := Bicycle{Size: "L", Parts: RecumbentBikeParts}

Composite literals(复合语义)同样可以用于字段:值的语法,所有的字段都是可选的。

简短的定义操作符(:=)通过Bicycle类型,使用类型推论来初始化roadBike,和其他。

输出

  1. fmt.Println(roadBike.Spares())
  2. fmt.Println(mountainBike.Spares())
  3. fmt.Println(recumbentBike.Spares())

我们将以默认格式打印 Spares 的调用结果:

  1. [{chain 10-speed true} {tire_size 23 true} {tape_color red true}]
  2. [{chain 10-speed true} {tire_size 2.1 true} {rear_shock Fox true}]
  3. [{chain 9-speed true} {tire_size 28 true} {flag tall and orange true}]

组合 Parts

  1.     comboParts :Parts{}
  2.     comboParts = append(comboParts, mountainBike.Parts...)
  3.     comboParts = append(comboParts, roadBike.Parts...)
  4.     comboParts = append(comboParts, recumbentBike.Parts...)
  5.     fmt.Println(len(comboParts), comboParts[9:])
  6.     fmt.Println(comboParts.Spares())
  7. }

Parts 的行为类似于 slice。按照长度获取切片,或者将数个切片结合。Ruby 中的类似解决方案就数组的子类,但是当两个 Parts 连接在一起时,Ruby 将会“错置” spares 方法。

“……在一个完美的面向对象的语言,这种解决方案是完全正确的。不幸的是,Ruby语言并没有完美的实现……”
—— Sandi Metz

在 Ruby 中有一个那看的解决方法,使用 Enumerable、forwardable,以及 def_delegators。 Go有没有这样的缺陷。 []Part 正是我们所需要的,且更为简洁(更新:Ruby 的 SimpleDelegator 看上去好了一点)。

接口 Interfaces

Go的多态性由接口提供。不像JAVA和C#,它们是隐含实现的,所以接口可以为不属于我们的代码定义。

和动态类型比较,接口是在它们声明过程中静态检查和说明的,而不是通过写一系列响应(respond_to)测试完成的。

“不可能不知不觉的或者偶然的创建一个抽象;在静态类型语言中定义的接口总是有倾向性的。” - Sandi Metz

给个简单的例子,假设我们不需要打印Part的NeedsSpare标记。我们可以写这样的字符串方法:

  1. func (part Part) String() string {
  2.     return fmt.Sprintf("%s: %s", part.Name, part.Description)
  3. }

然后对上述Print的调用将会输出这样的替代结果:

  1. [chain: 10-speed tire_size: 23 tape_color: red]
  2. [chain: 10-speed tire_size: 2.1 rear_shock: Fox]
  3. [chain: 9-speed tire_size: 28 flag: tall and orange]

这个机理是因为我们实现了fmt包会用到的Stringer接口。它是这么定义的:

  1. type Stringer interface {
  2.     String() string
  3. }

接口类型在同一个地方可以用作其它类型。变量与参数可以携带一个Stringer,可以是任何实现String() string方法签名的接口。

Exports 导出

Go 使用包来管理命名空间, 要使某个符号对其他包(package )可见(即可以访问),需要将该符号定义为以大写字母开头,  当然,如果以小写字母开关,那就是私有的.包外不可见.

  1. type Part struct {
  2.     name        string
  3.     description string
  4.     needsSpare  bool
  5. }

为了对Part类型应用统一的访问原则(uniform access principle), 我们可以改变Part类型的定义并提供setter/getter 方法,就像这样:

  1. func (part Part) Name() string {
  2.     return part.name
  3. }
  4. func (part *Part) SetName(name string) {
  5.     part.name = name
  6. }

这样可以很容易的确定哪些是public API, 哪些是私有的属性和方法, 只要通过字母的大小写.(例如(part.Name()vs.part.name)

注意 我们不必要对 getters 加前Get, (例如.GetName),Getter不是必需,特别是对于字符串,当我们有需要时,我们可以使用满足Stringer 类型接口的自定义的类型去改变Name 字段。

找到一些私有性

私有命名(小写字母)可以从同一个包的任何地方访问到,即使是包含了跨越多个文件的多个结构。如果你觉得这令人不安,包也可以像你希望的那么小。

可能的情况下用(更稳固的)公共API是一个好的实践,即使是来自经典语言的同样的类中。这需要一些约定,当然这些约定可以应用在GO中。

最大的好处

组合,内嵌和接口提供了Go语言中面向对象设计的强大工具。继承概念的思想真的不起什么作用。相信我,我尝试了

习惯Go需要思维的改变,当触及到Go对象模型的力量时,我非常高兴的吃惊于Go代码的简单和简洁。

Go编程tips-1

结构体的初始化

示例代码中,前者是创建普通对象,后者是创建指针

package main

import (
    "fmt"
)

type Rect struct {
    x, y          float64
    width, height float64
}

func main() {
    // common object
    rect2 := Rect{}
    rect3 := Rect{0, 0, 100, 200}
    rect4 := Rect{width: 100, height: 200}
    fmt.Println(rect2, rect3, rect4)

    /*
        // pointer objects
        rect1 := new(Rect)
        rect2 := &Rect{}
        rect3 := &Rect{0, 0, 100, 200}
        rect4 := &Rect{width: 100, height: 200}
        fmt.Println(rect1, rect2, rect3, rect4)
    */
}

变参

go支持变参,变参中所有参数的类别必须是同一种,且必须是最后一个形参。使用方法如下:“…type”表示具有不定个type类型的参数,不定参数实质上是一个slice类型,故可以使用range对其参数进行取值。如下例子。

func myfunc(args ...int) {
    for _, arg := range args {
        fmt.Println(arg)
    }
}

defer 与return执行顺序

当函数执行到最后时,先执行defer语句,然后才执行return语句.所以可以利用这个来进行资源安全关闭,解加锁,记录执行情况等。defer是采用先进后出的模式的,这种情形与栈的情况一致。注意:定义的defer延迟操作,如有提供参数会发生值的拷贝,尽管这些函数在退出时才执行,但所使用的参数是在定义时就进行拷贝,拷贝的原则和变量的赋值原则一个,slice,map,channel是指针拷贝.如下例子:

package main

import (
    "fmt"
)

func main() {
    x := 1
    defer func(a int) { //直接将x=1赋值给a,虽然他在后面才执行.
        fmt.Println("a=", a)
    }(x)
    defer func() {
        fmt.Println("x=", x) //经过x++后,x值变为2
    }()
    x++
}

运行结果:
x= 2
a= 1

临时变量的作用域

go语言中对于堆和栈的内存分配没有严格区分,在go中返回一个局部变量的地址是绝对没有问题的,变量关联的存储在函数返回后依然存在.(注:尤其对由C/C++转过来的程序员,开始肯定不是很适应,但是go这种内存分配方式解放了程序员,使得程序员能够专注做事情,而不用花费太多的时间在堆和栈的内存分配上).更直接的说,在go语言中,如果一个局部变量在函数返回后仍然被使用,那么这个变量会在堆heap,而不是栈stack中进行内存分配.详情参考How do I know whether a variable is allocated on the heap or the stack?

Mac下零基础学习go语言-2-开发环境的搭建

环境介绍

  • 系统:OS X EI Capitan 10.11.1
  • go version:1.5.1

安装

下载最新的安装包 https://golang.org/dl/

mac下安装

直接点击,按照引导就可以完成安装。

linux下安装

linux下实际就是手动设置文件路径了,大概的动作如下:

#!/bin/bash
tar -C /usr/local -xzf go*.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin' >> /etc/profile

最后重启,或者:

source /etc/profile

验证版本

终端验证,输入

go version
go version go1.11.2 linux/amd64

配置环境变量

设置环境变量,go默认安装在/usr/local/go 下面,参见:Mac OS X 配置环境变量

所以我们修改 .bash_profile:

export GOPATH=/Users/alex/dev/go
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOBIN

注意:

  • 默认情况下GOROOT已经在安装时指定为安装目录了,是不需要设置的
  • GOPATH 设置成你的本地开发路径,而不是安装路径

设置完成后可以用 go env 命令检测

测试

在 GOPATH下 的src目录下创建项目demo。注意这个将是默认的bin档名。 然后在demo目录下创建main.go

package main

import (
  "fmt"
)

func main() {
  fmt.Println("hello world");
}

然后运行 go build demo,如果在demo项目目录下,直接 go build,完成后,在项目目录下生成 demo

直接运行 ./demo,就会输出 :hello world

DEBUG

初学者建议在LiteIDE下面debug,因为IDE直接UI支持,熟悉了以后也可以在命令行下面直接debug。第一次试用时要解决GDB的证书签名问题,可以参看网上同学给出的操作流程。

下面终于可以开始coding了!

Mac下零基础学习go语言-1-开篇

缘由

一直以来都在慢慢悠悠地关注各种go的相关主题,因为本职还是做c/c++相关的工作,所以一致以来都没有系统的对go进行系统的学习,仅局限于对相关的知识点进行进行简单的了解和学习。最近由于工作变动原因,稍息赋闲,打算对go做一个系统的学习,所以开此主题,希望能详细记录下学习的细节,能对后来的新人有个引路的作用

为什么学习go

这个应该是最先要回答的问题,也是群里经常争论的问题,为什么要选go,而不是php,java。
首先说下go的几个特点

  • 静态语言
  • 有高级语法,又不是非常复杂晦涩
  • 高并发,非常小的协程开销,非常适合服务器场景
  • 其他的网友自行google,这里不详细展开

我自己的观点是,如果你是c,c++的背景,要提高后台开发的效率,go是不二的选择,go的语法复杂度,介于c和c++之间,功能上长于后端开发,也有完备的库,开发效率上会有很大提高。
如果你原来就是php,java,node的背景,你所做的工作都是基于接口的数据开发,也建议尝试go。go天生适合做后台接口,开发效率高,性能也不错。
但如果你想要做一个web系统,其实go就目前的情况看,并不比php,java,node更合适,不必盲目跟风。
最后,建议初学者读一下这篇
Donovan/Kernighan大神们关于go语言的问答
其中关于go的特点,潜力,为什要选go等都有涉及。
其他的问题参见go的FAQ

环境

mac air
go version go1.5.1 darwin/amd64

主要学习材料

  1. google go官网教程
  2. 谢大的书籍:
    go web编程
    go实践开发
  3. 无闻的<<go编程基础>>视频教程
  4. 雨痕的笔记
  5. 其他:go学习资料链接汇总

最后,由于本人在go方面也是小白,难免水平有限,如果有大牛路过,一定不要怜惜您的键盘,狠狠的拍吧

OOSMOS介绍

原英文 oosmos 官网
无意中看见大牛的微博提及,正好物联网也是本人感兴趣的领域,正好周末闲暇,翻译下文章,切换下心情。水平有限,还请路过的大侠多指点。

正文:

  1. 什么是OOSMOS
    OOSMOS(Object-Oriented State Machine Operating System)代表面向对象的状态机操作系统。它是一个以基本的上下文为对象的操作系统,而不象传统的以线程为对象。

    因为没有线程,所以没有线程栈(译者:消耗较小资源),所以OOSMOS是一个理想的用于那些传统的基于线程的操作系统无法满足的内存紧张的平台环境。

    OOSMOS 有着强大的分级状态机引擎可以管理非常复杂的事件驱动系统。

  2. OOSMOS的优点

  • 很适合:
    物联网(IOT)
    教育-STEM
    创客活动
    医疗设备
    自动化设备
    控制系统
    航天系统
    企业
    逻辑控制/工作流
    各种界面切换控制
    机器人
    基础的只要是事件驱动的系统
  • 免费
  • 适合 c 甚至 c++
  • 封装简单
  • 对CPU消耗低
  • 没有线程,也就没有线程栈
  • 特别的进行了面相对象的封装和无关信息隐藏
  • 灵活扩展
  • 高效,对象的时间和内存消耗固定
  • 强大的状态机引擎
    支持不限数量的当前对象超时。
  • 支持状态查询
  • 支持异步函数
  • 支持正交区域
  • 支持状态机调试
  • 支持事件管理
    事件码通过对象的类来管理
    事件支持参数传递
    事件支持发布/订阅模式
    事件对象有自己的事件队列
  • 移植方便,基于c 89,还在以下的环境测试:
    MPLABX(包含ChipKit的PIC32系列)
    Arduino(AVR,ARM)
    ChipKit(PIC32,使用类Arduino或者MPLAB X)
    Energia(MSP430,使用类Arduino IDE)
    Intel Galileo
    mbed(ARM,使用web IDE)
    MSP430
    LightBlue Bean
    ESP8266
    Linux(树莓派)
    Windows(Visual Studio)
  1. OOSMOS是怎么工作的-简介
    OOSMOS由对象组成,每一个对象可以有一个状态机能反映对象当前的状态。
    工作时,最重要的是可视化,所以让我们看一些已经运行一段时间的状态机(图 1)

    图1 两个运行态的对象状态切换图
    上图中,红框代表对象的当前状态,我们看到A的状态是On,B的状态是Off。
    OOSMOS循环地一个一个运行每一个对象。
    当OOSMOS反转对象A时,A就变成On状态,唯一退出On状态的方式是tm(m_TimeOnMS)超时,超时前,每一次都运行A。OOSMOS会检查对象是否超时,如果超时,将会向对象发送TIMEOUT事件,对象将会调用oosmos_Transition方法,状态由On切换为Off。在切换的同时,OOSMOS将会在On状态调用EXIT事件,然后调用ENTER事件,变成Off状态。如果没有超时,切换就不会发生,对象会一直保持当前状态。

  2. 学习OOSMOS的最好方式
    学习OOSMOS最好的方式是下载和解压项目,然后编译其中的例子,选择你最合适和熟悉的平台。修改下代码,看下新的项目行为,从而通过比较学习。
    开始学习时,Windows或者Linux例子是不错的选择,你可以用源码级别的调试工具去设置断点和现实变量。
    OOSMOS用户手册涵盖所有的概念和技术点。OOSMOS API参考手册提供快速的函数和宏查看。

over,have fun!

go基础笔记

本文不讲解详细的语法,详细的知识请参见 tour.go-zh.org,只是纪录本人学习中觉得需要注意或者有意思的点

  • switch 可以不带条件,用来简化冗长的 if-else if-else
  • 结构体初始化参数可变,缺省有零值。支持 Name:特定字段赋值
  • slice 区间表示时左闭右开,可以省略上标或下标
  • _ 用来在 slice 值对中忽略序号和值
  • make 用来创建slice map channel ,其他的用new
  • map 可以同时获取值和行为结果(true/falses)
  • 值可以隐式表示函数
  • 闭包的概念

MAC下go code安装问题

在安装go lang时,已经设置过GOPATH,go 也运行正常,但是安装 gocode时提示:

Agreeing to the Xcode/iOS license requires admin privileges, please re-run as root via sudo.
package github.com/nsf/gocode: exit status 69

当我们使用sudo 时,提示:

package github.com/nsf/gocode: cannot download, $GOPATH not set. For more details see: go help gopath

虽然之前已经设置,但是当切换权限时,也切换了环境变量,所以当用sudo管理员权限时提示没有设置环境变量。

解决:

sudo env GOPATH=/Users/alex/dev/go 
go get -u github.com/nsf/gocode

在命令行里临时指定env变量,完成

You have not agreed to the Xcode license agreements

在安装gocode时,提示You have not agreed to the Xcode license agreements 相关的错误提示:
You have not agreed to the Xcode license agreements, please run 'xcodebuild -license' (for user-level acceptance) or 'sudo xcodebuild -license' (for system-wide acceptance) from within a Terminal window to review and agree to the Xcode license agreements.
根据提示,执行 sudo xcodebuild -license 或者直接通过系统运行 xcode,会出现 license界面,点击同意,再次安装就ok了

Mac OS X 配置环境变量

/etc/profile;/etc/bashrc 是针对系统所有用户的全局变量,只有root用户才能修改这两个文件,对一般用户来说是他们是只读的。一般用户要想修改它们,可以在命令前加sudo,意思是以Root身份执行,比如:sudo vi /etc/profile ,然后按照提示输入密码即可。

Mac OS X 10.3之后默认的是Bourne Shell。因此,对于一般用户而言,通常我们建议去修改~/.bash_profile来设置环境变量,它是用户级的设置,只对当前用户有效。

增加环境变量

操作步骤:
1. 打开Terminal(终端)
2. 输入:vi ~/.bash_profile
3. 设置PATH:export PATH=/usr/local/mysql/bin:$PATH
4. 输入::wq //保存并退出vi
5. 修改立即生效:source ~/.bash_profile
6. 查看环境变量的值:echo $PATH

删除环境变量

unset DYLD_LIBRARY_PATH,其中DYLD_LIBRARY_PATH(环境变量名)
删除环境变量之后需要先logout,关闭terminal,再重新打开terminal

恢复系统默认环境变量

PATH=$(getconf PATH),执行此命令将环境变量恢复到系统初始值