看啥推荐读物
专栏名称: NKCCDD
今天看啥  ›  专栏  ›  NKCCDD

Go 语言设计模式系列之一 ——Go 语言中的面向对象

NKCCDD  · CSDN  ·  · 2020-10-23 00:15

计划写一系列基于golang语言面向对象和设计模式的文章,此系列将结合golang代码实现介绍一些常用的设计模式。设计模式主要包括Gang of Four的经典书籍里面的三大类型包括创建型( Creational )行为型(Behavioral)和结构型(Structural),本篇为开篇第一篇。首先介绍一下面向对象和go语言中面向对象的方法。

面向对象

面向对象(OOP)的编程方法是当前高级语言编程比如C++, Java,python等常用的编程思想。面向对象网上资料很多这里仅作简单的介绍。面向对象编程的核心思想就是将代码分成许多小的对象(object),每个对象都有自己的属性和行为。属性描述了对象当前的状态,行为描述了对象可以做什么事情。行为通常被抽象成一个函数供调用,在OOP中这个函数也被称为方法(method)。

类(class)是具有相同属性和方法的一组对象的集合,它可以用来创建并初始化对象。在设计类的时候一个关键的指导原则就是封装。封装简单来说就是将代码及其处理的数据绑定在一起,形成一个独立单位,对外实现完整功能,并尽可能隐藏对象的内部细节。在代码实现过程中我们并不能将所有的实现都封装在一个大类里面,这样写出的代码是几乎无法维护的。而应该是将其拆成许多可维护的小类。

在有些时候我们会发现一些有相同的属性和方法的类或对象,例如我们在开发一个web后端的时候经常会用到缓存,缓存的种类很多例如 Redis,Memcahe等。而对缓存的操作基本就是固定的GET SET RPUSH等,因此可以创建一个Cache的父类其他的子类继承这个父类即可,这就是面向对象的继承(Inheritance)。

然而继承在使用的时候往往会遇到一些问题,继承使得类之间的层级关系变得复杂,这样超级父类(Superclass)会变得很脆弱,微小的改动可能会带来子类上的一些问题。为了解决这个问题就引入了组合(Composition )的概念,现在大部分人在编程的时候一般都秉承Composition Over Inheritance的基本原则。组合使用的方式

1.父类定义接口,子类实现这些接口

2.Method的复用采用调用的方式而不是继承

Golang 中的面向对象的方法

上面罗嗦了这么多接下来是golang中面向对象的代码实现

结构体

golang中是没有类的概念的,而是使用结构体。如下是定义了一个Cluster的结构体(注意代码摘自一些项目仅为解释概念使用并不能直接运行)。

type cluster struct {
	remote   pb.ClusterClient
	callOpts []grpc.CallOption
}

通过结构体可以实例化一个对象

api := cluster{remote: RetryClusterClient(c)}

这样我们就可以访问对象的变量了

fmt.Println(api.remote)

Go 语言也提供了指针的访问方法,同样也是用点来访问变量,这点跟C语言的指针是不一样的如下函数

func NewCluster(c *Client) Cluster {
	api := &cluster{remote: RetryClusterClient(c)}
	if c != nil {
		api.callOpts = c.callOpts
	}
	return api
}

结构体Method通过func来定义,结构体的方法与普通函数不一样在func与函数名之间需要有一个receiver。如下面的例子

func (c *cluster) memberAdd(ctx context.Context, peerAddrs []string, isLearner bool) (*MemberAddResponse, error) {
	// fail-fast before panic in rafthttp
	if _, err := types.NewURLs(peerAddrs); err != nil {
		return nil, err
	}

上面的例子中*cluster 是一个receiver

当然也可以使用非指针类型的receiver,指针类型的receiver提供了Pass-By-Reference 机制,而非指针则是Pass-By-Value,总体来说到底是使用指针类型还是非指针类型遵循以下原则

  1. 如果想改变receiver的属性值就使用指针
  2. 如果receiver很大使用deepcopy的时候会占用很大的资源就使用指针

可见性

go语言中没有public 和private,但是go语言提供了定义公有变量和私有变量的方法:变量首字母法。如果一个变量的首字母是大写则为public 反之则为private。同样首字母法也适用与method

接口

接口是go语言里面非常重要的一个用法,也是实现面向对象编程的关键。go语言中采用的是隐式接口实现,如果一个对象实现了一个接口的所有的method那么这个对象就实现了这个接口,通过接口go语言实现了多态。

如下我们定义一个接口Cluster 里面包含了六个method

type Cluster interface {
	MemberList(ctx context.Context) (*MemberListResponse, error)
	MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error)
	MemberAddAsLearner(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error)
	MemberRemove(ctx context.Context, id uint64) (*MemberRemoveResponse, error)
	MemberUpdate(ctx context.Context, id uint64, peerAddrs []string) (*MemberUpdateResponse, error)
	MemberPromote(ctx context.Context, id uint64) (*MemberPromoteResponse, error)
}

我们的实际应用中会有HttpCluster和GrpcCluster两种实现,因此我们需要定义两个struct然后分别实现接口中的六个Method即可

GrpcCluster:

// 定义结构体
type GrpcCluster struct {
	remote   pb.ClusterClient
	callOpts []grpc.CallOption
}
// 实现接口
func (c *GrpcCluster) MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error)
 {

    res,err := memberAdd()
    return res,err
 }
func (c *GrpcCluster) MemberList(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error)
 {

    res,err := memberlist()
    return res,err
 }

func (c *GrpcCluster) MemberAddAsLearner(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error)
 {

    res,err := addaslearner()
    return res,err
 }
func (c *GrpcCluster) MemberRemove(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error)
 {

    res,err := remove()
    return res,err
 }
func (c *GrpcCluster) MemberUpdate(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error)
 {

    res,err := update()
    return res,err
 }
func (c *GrpcCluster) MemberPromote(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error)
 {

    res,err := promote()
    return res,err
 }

HttpCluster 同样定义结构体并实现方法即可,实现方法类似此处省略了伪代码。

定义好之后就可以在代码中使用了如下片段为如何使用

var clt Cluster
service_type := getserviceType()
switch service_type {
case "http":
	clt = HttpCluster{}
case "grpc"
	clt = GrpcCluster{}
	
}
exeute(clt)

继承和组合

Goglang中没有extends关键字,继承是通过结构体嵌套( Struct embeding )实现的

如果一个struct嵌套了另一个匿名结构体,那么这个结构可以直接访问匿名结构体的方法,从而实现继承

如果一个struct嵌套了另一个【有名】的结构体,那么这个模式叫做组合
如下两个结构体

type SetSlackServiceOptions struct {
    WebHook  *string `url:"webhook,omitempty" json:"webhook,omitempty" `
    Username *string `url:"username,omitempty" json:"username,omitempty" `
    Channel  *string `url:"channel,omitempty" json:"channel,omitempty"`
}
type ExtendedStruct struct {
  SetSlackServiceOptions
  MoreValues []string
}

结构体ExtendedStruct中隐式声明了SetSlackServiceOptions因此ExtendedStruct就自动拥有了SetSlackServiceOptions所有的方法因此达到了继承的功能。

另外接口也可以组合使用

type Reader interface {
    Read(p []byte) (n int, err error)
}
 
type Writer interface {
    Write(p []byte) (n int, err error)
}
 
type ReadWriter interface {
    Reader
    Writer
}

其实很简单, 就是把 Reader , Writer 嵌入到 ReadWriter 中, 这样 ReadWriter 就拥有了 Reader Writer 的方法。

总结

这篇作为开篇主要介绍了面向对象的编程方式以及go语言中面向对象的一些特殊方式,下一篇将开始介绍设计模式,敬请期待




原文地址:访问原文地址
快照地址: 访问文章快照