Go入门:四、面向对象

这是我的Go学习笔记的第四篇,面向对象!现代语言几乎都会面向对象进行了支持!当然,Go也具备面向对象的特性!

我的语言学习过程一般分为下面几个:

1. 变量和数据类型
2. 流程控制方法
3. 函数声明和调用
4. 面向对象
5. 语言特性
6. 标准库

Go语言中的面向对象有点特殊。在Go语言里面,没有显式的class、extends等面向对象语言经常使用的关键词,但是却有面向对象的特性。看看Go怎么实现的把!

创建一个类

按照我的理解,类实际上就是某种模板,这个模板里面含有有限多个属性和方法。在Go里面,定义这个模板的语法使用type来实现!

比如单个int类型可以构成一个类(没错,你甚至可以在int数据类型上定义一些操作)

type num int

比如某几种复合类型构成的类

type human struct {
    name string
    age int
    weight int
    stature int
    ...
}

上面的type姑且称之为类,方法之后会通过某种方式绑定在这个类上!

创建一个方法

函数声明的时,在其前方增加一个特定类型的变量,即创建了一个属于这个变量类型的方法。

这个特定类型的变量称为方法接收器(Receiver),可以类比为一些面向对象语言里面的this,self!通常的做法是用类型的首字母作为方法接收器的名字。

比如下面的一个例子,在函数toAbs前面增加一个命名为n的num类型变量,即在num类型上增加了一个toAbs方法。

package main

import "fmt"

type num int

// 在toAbs函数前增加一个变量n,就定一个num类型上的方法了!
func (n num) toAbs() num {
	if n > 0 {
		return n
	} else {
		n = -n
		return n
	}
}

func main() {
	// 调用上面的toAbs方法
	var i num = -1
	fmt.Printf("调用toAbs返回 %d\n", i.toAbs())
	fmt.Printf("原来的i为 %d\n", i)
}

//上面将会输出
// 调用toAbs返回 1
// 原来的i为 -1

上面使用的是变量作为接收器,另一种做法是使用变量的指针作为方法接收器。这两者的区别很明显:使用变量作为接收器,方法内会对变量做拷贝,对变量的修改不影响外部变量,使用变量指针作为接收器,方法内不会对变量做拷贝,对变量的修改会影响外部变量。

package main

import "fmt"

type num int

// 在toAbs函数前增加一个变量n,就定一个num类型上的方法了!
func (n *num) toAbs() num {
	if *n < 0 {
		*n = -*n
	}
	return *n
}

func main() {
	// 调用上面的toAbs方法
	var i num = -1
	fmt.Printf("调用toAbs返回 %d\n", i.toAbs())
	fmt.Printf("原来的i为 %d\n", i)
}

// 上面输出
// 调用toAbs返回 1
// 原来的i为 1

使用指针作为接收器,将会改变原先的值!

继承

Go语言实际上是没有继承这种写法的,为了让类能够复用,Go使用了组合!

在设计模式中,“多用组合,少用继承”是一种非常常见的思想。Go语言干脆走向了一个极端,没有继承,只有组合。

无论是继承还是组合,本质上都是让代码能够更好地复用,让结构更加清晰。Go这种设计总体来讲还是利大于弊。

组合

接上面,接下来学习Go语言中类的组合。

来一个简单的例子,通过制作三明治的过程来看看组合是怎么用的

package main

import "fmt"

// 面包
type Bread struct {
}

// 培根
type Bacon struct {
}

//生菜
type Lettuct struct {
}

// 鸡蛋
type Egg struct {
}

// 通过组合的方法来做一个简单的三明治
type Sandwich struct {
	bread   Bread
	bacon   Bacon
	lettuct Lettuct
	egg     Egg
}

func (b Bread) make() {
	fmt.Println("将面包切片, 得到一些面包片")
}

func (b Bacon) make() {
	fmt.Println("把培根放入面包片夹层中")
}

func (l Lettuct) make() {
	fmt.Println("将生菜洗干净放入面包夹层")
}

func (e Egg) make() {
	fmt.Println("将鸡蛋煎熟, 放入面包夹层")
}

func (s Sandwich) make() {
	s.bread.make()
	s.bacon.make()
	s.lettuct.make()
	s.egg.make()
	fmt.Println("对着弄好的材料斜着切一下")
	fmt.Println("得到两个三明治!")
}

func main() {
	var sandwich Sandwich
	sandwich.make()
}

上面的运行结果是

将面包切片, 得到一些面包片
把培根放入面包片夹层中
将生菜洗干净放入面包夹层
将鸡蛋煎熟, 放入面包夹层
对着弄好的材料斜着切一下
得到两个三明治!

封装

如果一个类里面的属性或者方法对外是不可见的,那么我们把这种做法叫做封装。Go语言只有一种封装的方法,那就是大写首字母字母开头标识符会被导出(公有),小写字母的则不会(私有),这个规则对struct也适用。

type Page struct {
    title string
    content string
}
// 这个结构体里面的title和content是不能被导出的
// 引用这个包直接对title和content赋值将会编译不通过
// 比如这个包叫page,那么可以直接给page.Page.title赋值将会编译失败

type Page struct {
    Title string
    Content string
}
//这个结构体里面的Title和Content是可以被导出的
// 在包外可以直接对Title和Content直接赋值
// 比如这个包叫page,那么可以直接给page.Page.Title赋值

接口

正如很多面向对象的语言一样,Go也拥有接口。接口实际上是一种契约,里面包含了一些必须要实现的方法,如果某个类实现了这个接口里面所有的方法,那么就称为这个类实现了这个接口。

我喜欢用实际生活中存在的事物去类比,比如锅盖。要怎么实现一个锅盖的接口,这个接口文字描述可以是:只要能够盖住一定大小范围内的锅,都是锅盖。至于用玻璃材质去实现还是用木头材质去实现还是其他材质,这个接口并不去关注。

接口的好处就是实现了这个接口的类可以互相替换,正如上面的锅盖,只要实现了锅盖接口,无论是铁的,玻璃的,木头的都可以互相替换。

Go语言的接口声明

package main

import "fmt"

// 声明一个锅盖接口
// 内有一个cover方法
// 只要实现了cover方法的类,都称可以称为锅盖
type PotCover interface {
	cover()
}

type GlassCover struct {
}

func (g GlassCover) cover() {
	fmt.Println("玻璃锅盖")
}

// 定义一个男人类型
type Man struct {
	name string
}

func main() {
	// 声明c是一个锅盖
	var c PotCover
	// 把玻璃锅盖赋值给c
	// 玻璃锅盖实现了cover方法
	// 因此是个锅盖
	c = new(GlassCover)
	c.cover()
        // 男人没有cover方法,没有实现锅盖接口
        // 这里将会编译出错
	c = new(Man)
}

空接口

interface{} 称为空接口,空接口是一个很有用的接口,因为它没有实现任何方法。所以任意变量都可以赋值给空接口。

比如我们定义一个map[string]int,这个map的值只能是int类型,不管怎么定义,map的值看起来都只能是一个单一的类型。这里空接口就能让map的值支持任意类型。

i := make(map[string]interface{})
i["0"] = 1
i["1"] = "hello"
fmt.Println(i)
// map[0:1 1:hello]

同样,函数的参数也可以使用空接口,这允许我们传入任意类型的参数。比如fmt.Println的参数就是个空接口,可以传入任意类型的参数。

你可能还喜欢下面这些文章

布隆过滤器(bloom filter)介绍以及php和redis实现布隆过滤器实现方法

引言在介绍布隆过滤器之前我们首先引入几个场景。场景一在一个高并发的计数系统中,如果一个key没有计数,此时我们应该返回0。但是访问的key不存在,相当于每次访问缓存都不起作用了。那么如何避免频繁访问数量为0的key而导致的缓存被击穿?有人说, 将这个key的值置为0存入缓存不就行了吗?这是确实是一种解决方案。当访问一个不存在的key的时候,设置一个带有过期时间的标志,然后放入缓存。不过这样做的缺点也很明显:浪费内存和无法抵御随机key攻击。场景二在一个黑名单系统中,我们需要设置很多黑名单内容。比如一个邮件系统,我们需要设置黑名单用户,当判断垃圾邮件的时候,要怎么去做。比如爬虫系统,我们要记录下

Go入门:一、变量和数据类型

这是学习Go语言的第一篇笔记,主要学习的是变量和基本数据类型。如果您也在开始学习Go语言,那么这篇笔记一定能帮助您学习的更快!我的语言学习过程一般分为下面几个:1. 变量和数据类型2. 流程控制方法3. 函数声明和调用4. 面向对象5. 语言特性6. 标准库变量声明Go语言的变量声明有三种第一种,var identifier type// 先声明后赋值var identifier typeidentifier = value// 声明并且赋值var identifier type = value开始实战一下!比如声明一个int类型变量var i inti = 1// 或者var i int =

linux shell 入门

从程序员的角度来看, Shell本身是一种用C语言编写的程序,从用户的角度来看,Shell是用户与Linux操作系统沟通的桥梁。用户既可以输入命令执行,又可以利用 Shell脚本编程,完成更加复杂的操作。在Linux GUI日益完善的今天,在系统管理等领域,Shell编程仍然起着不可忽视的作用。深入地了解和熟练地掌握Shell编程,是每一个Linux用户的必修 功课之一。Linux的Shell种类众多,常见的有:Bourne Shell(/usr/bin/sh或/bin/sh)、Bourne Again Shell(/bin/bash)、C Shell(/usr/bin/csh)、K Shel

gcc/g++编译参数详解

编译步骤gcc 与 g++ 分别是 gnu 的 c & c++ 编译器。gcc/g++ 在执行编译工作的时候,总共需要4步:预处理,生成 .i 的文件将预处理后的文件转换成汇编语言, 生成文件 .s 有汇编变为目标代码(机器代码)生成 .o 的文件连接目标代码, 生成可执行程序 参数详解-x language filename参数含义为指定文件所使用的语言。根据约定,C语言的后缀名称为".c",而 C++ 的后缀名为".cpp"或".cc",但如果你的源代码后缀不约定的那几种,那么需要使用-x参数来指定文件所使用的语言。这个参数对他后面的文件名都起作用。 可以使用的参数吗有下面的这些:

signal函数详解

signal作用是为信号注册一个处理器。这里的“信号”是软中断信号,这种信号来源主要有三种:程序错误:比如除0,非法内存访问。外部信号:终端Ctrl-C产生的SIGINT信号,定时器产生的SIGALERM。显示请求:kill函数发送的任意信号。当kill一个进程的时候,默认会发送SIGTERM信号,此时这个信号只有默认处理操作(SIG_DFL),直接中断进程执行。如果此时该进程正在执行一个任务,直接终止该进程会导致任务没有完成。这个时候为SIGTERM信号注册一个信号处理函数就十分有必要。介绍参数sig要设置信号处理函数的信号。它可以是实现定义值或下例值之一:SIGABRTSIGFPESIGI

C++入门:一、变量和数据类型

这是我的C++学习笔记第一篇,同所有的程序语言学习路径一样,首先学习的是变量和数据类型。我的学习路径如下:1. 变量和数据类型2. 流程控制3. 函数声明和调用4. 面向对象5. 标准库这一章,学习的是变量和数据类型,需要了解的有:了解这些,对于变量基本就够了。Hello world在开始之前,先写一个hello world来熟悉一下程序的主要结构以及如何打印一个变量。iostream提供标准输入输出的头文件,程序以main函数问入口,std为标准库的命名空间,“<<” 为输出操作符,std::cout为标准输出,std::endl为结束符,表示将等待输出的内容从内存传送到标准输出

php的empty,isset,is_null与!

来说说php的empty,isset,is_null 与!,这几个都是if语句中比较常见的判断逻辑。但是有时候用的很纠结,甚至看别人写的程序里面也很纠结。特地梳理梳理,避免踩坑先来定义一些东西<?php$a;$b=0;$c=array();$d='';$e=null;empty,用了会上瘾这是一个用了会上瘾的语言结构!多好,empty可接受的参数是一个变量,任意类型,哪怕是变量不存在,只要变量被boolean转换之后是false(参考:php的boolean都有哪些),那么empty返回的就是false,并且不会出现警告!等价于不过注意的是,empty里面不能使用表达式(在php<

shell 变量的定义

shell可以自定义变量,这为shell的编写带来很多方便定义变量定义变量时,变量名不加美元符号($),如:variableName="value"注意:变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样同时,变量名的命名须遵循如下规则。首个字符必须为字母(a-z,A-Z)。中间不能有空格,可以使用下划线(_)。不能使用标点符号。不能使用bash里的关键字(可用help命令查看保留关键字)。变量定义举例:myUrl="http://imhuchao.com/tag/bash"myNum=100使用变量使用一个定义过的变量,只要在变量名前面加美元符号($)即可,如:your_nam

Go语言的 make 和 new

new 和 make 是两个内置函数,主要用来创建并分配类型的内存。在我们定义变量的时候,可能会觉得有点迷惑,不知道应该使用哪个函数来声明变量,其实他们的规则很简单,new 只分配内存,make 只能用于 slice、map 和 channel 的初始化。下面我们就来具体介绍一下new在Go语言中,new 函数描述如下:从上面的代码可以看出,new 函数只接受一个参数,这个参数是一个类型,并且返回一个指向该类型内存地址的指针。同时 new 函数会把分配的内存置为零,也就是类型的零值。【示例】使用 new 函数为变量分配内存空间。当然,new 函数不仅仅能够为系统默认的数据类型,分配空间,自定义

设计模式:观察者模式介绍与应用场景

简短描述在观察者模式中,有一个主题和几个观察者,主题状态改变之后会通知到所有的观察者。现实中的观察者模式观察者模式和现实世界很好对应。比如我和我的朋友们购买了今年一年的报纸,每次报社印刷了今天的报纸之后就会往我这里送。在这个系统里面,“报社“是一个主题,“我和我的朋友们”就是一个观察者,当“报社”生产报纸之后,“我和我的朋友们”都能收到这份报纸。根据上面的描述,我们很容易写出一个观察者模式。首先定义一个报社作为主题,然后定义一些用户作为观察者。不过在此之前,我们先定义主题和观察者的接口,毕竟我们是面向接口编程。定义一个主题接口namespace ds\observer\contract;/**

赞赏

微信赞赏支付宝赞赏

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注