计划写一系列基于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中是没有类的概念的,而是使用结构体。如下是定义了一个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,总体来说到底是使用指针类型还是非指针类型遵循以下原则
如果想改变receiver的属性值就使用指针如果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语言中面向对象的一些特殊方式,下一篇将开始介绍设计模式,敬请期待