前言

工作中遇到转来使用Go语言的朋友或者同事,基本都是从现有语言,像C++和JAVA转来。所以在平时交流时候多少会需要引用到以前所学的一些概念和方法,而面向对象这一块就是经常碰到的讨论点之一。在面向对象设计中,Go语言有些实现并不能完全按照C++和JAVA的概念进行理解。所以本文介绍的准确说是依照Go语言自身方法,来对面向对象进行模拟实现。

Go是否是一个面向对象语言? 答案是: Go并不是一个纯面向对象语言。原由出自Go的FAQ

Is Go an object-oriented language? Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers. They are not restricted to structs (classes).

Also, the lack of a type hierarchy makes “objects” in Go feel much more lightweight than in languages such as C++ or Java.

三大特征

封装

把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏

由于Go无类的概念,但是我们有interface和结构体。在Go中可以通过结构体和接口进行封装操作。通过控制变量、结构体和方法首字母大小来实现内外部访问控制。

type MyStruct struct{
	id int64 //仅供内部访问
	Name string  //允许外部访问
}

//允许外部访问方法(public)
func NewObject(){
}

//仅允许包内访问方法(private)
func checkObject(){
}

//限定仅允许某对象示例访问方法
func (this *MyStruct) Clone() {
}

继承

可以让某个类型的对象获得另一个类型的对象的属性的方法。它支持按级分类的概念。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”

由于Go中无类的概念。但是与封装方法相同,我们还是以结构体为基础,使用[内嵌]功能实现对结构体进行组合,实现类似继承的效果。其中通过内嵌方法生成的新结构体允许访问原结构体的方法。

//基类
type MyStruct struct {
	id   int64
	Name string
}

//限定仅允许MyStruct结构体实例访问的方法
func (this *MyStruct) generateId() {
	this.id = time.Now().Unix()
	return
}

//子类(派生类)
type MyChildStruct struct {
	MyStruct //基类
	Phone string
}

多态

指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。

实现:

type Dog struct {
	Name string
}

func (this *Dog) String() string {
	return `Type: Dog , Name: ` + this.Name
}

type Cat struct {
	Name string
}

func (this *Cat) String() string {
	return `Type: Cat , Name: ` + this.Name
}

type Aninals interface {
	String() string
}

func main() {
	myDog := &Dog{Name: "W"}
	myCat := &Cat{Name: "W"}

	myAnimals := []Aninals{}
	myAnimals = append(myAnimals, Aninals(myDog))
	myAnimals = append(myAnimals, Aninals(myCat))
	for _, animal := range myAnimals {
		fmt.Println(animal.String())
	}

}

五大原则

单一职责

一个类的功能要单一,不能包罗万象

其核心是高内聚,低耦合。通过把不同功能的方法独立实现,以降低每个方法之前的聚合度。避免在后期改动时候的牵一发而动全身

错误例子:

type Student struct {
	Name string
	Birthday time.Time
}
//进行处理
//一个方法中实现多个功能的处理
func (this *Student)Process(method string,interface) interface{}{
	switch method{
		case "print":
		...
		case "modify":
		...
		case "clone":
		...
		default:
		break
	}
}

正确方式:

type Student struct {
	Name string
	Birthday time.Time
}

func (this *Student)print(){
}

func (this *Student)modify(){
}

func (this *Student)clone(){
}

开放封闭(OCP)

一个模块在扩展性方面应该是开放的而在更改性方面应该是封闭的

通俗地说,软件的实体应该是可扩展的而不可修改的。在Go语言中我们可以通过内嵌方式实现对原有结构体/方法的拓展

正确打开方式

type A struct{
	Name string
}

type AA struct {
	A
	NewName string
}

替换原则(LSP)

子类应当可以替换父类并出现在父类能够出现的任何地方

替换原则通常是作为对相关子类型与其基类关系的规范。但Go语言中没有类和继承。但是我们有很接近的interface接口类型。 通过定义接口,我们可以对他进行不同的实现。在Go语言中这些实现是可以互相替代的。

例子

type Animal interface {
	Yell()
}

type Cat struct {
}

func (this *Cat)Yell(){
	fmt.Println("cat yell")
}

type Dog struct {
}

func(this *Dog)Yell(){
	fmt.Prinln("dog yell)
}

func Call(a Animal){
	a.Yell()
}

func main(){
	Call(Animal(&Cat{}))
	Call(Animal(&Dog{}))
}

依赖倒置

具体依赖抽象,上层依赖下层。

具体依赖与抽象:

个人理解是在Go语言设计中,我们尽可能对功能点进行抽象,在实现中使用接口来进行抽象。比如在很多cache和session包的实现中都采用了这种思想。把存储模块进行抽象.如以下代码

type Storage interface {
	Add()
	Get()
	Fetch()
}

通过对存储抽象,用户可以对这个抽象进行不同的实现。比如使用MySQL、内存又或者是MQ组件。不同的实现,并不影响原来的功能代码。

type Cache struct {
	mem map[string]interface{}
	storage Storage
}

func (this *Cache) Fetch(){
	this.Storage.Fetch()
}

上层依赖下层:

在Go语言中package包管理有着严格上下层级的关系。即上级包调用了下级包之后,下级不能反调回上级。否则会引起回调错误。

接口隔离

模块间要通过抽象接口隔离开,而不是通过具体的类强耦合起来

在模块功能的实现上,可以通过使用特定小接口来实现高内聚。不必依赖于过大的对象和带有用不上方法的通用接口。

type Storage interface{
	Load()
	Save()
	...
}
type Loader interface {
	Load() []Byte
}

type Cache struct{
}

func (this *Cache)Loading(r Loader){
	rawData := r.Load()
	..
}

引用

文章目录