gRPC的发布订阅模式及REST接口和超时控制

前言

上篇文章 gRPC,爆赞 直接爆了,内容主要包括:简单的 gRPC 服务,流处理模式,验证器,Token 认证和证书认证。

在多个平台的阅读量都创了新高,在 oschina 更是获得了首页推荐,阅读量到了 1w+,这已经是我单篇阅读的高峰了。

看来只要用心写还是有收获的。

这篇咱们还是从实战出发,主要介绍 gRPC 的发布订阅模式,REST 接口和超时控制。

相关代码我会都上传到 GitHub,感兴趣的小伙伴可以去查看或下载。

发布和订阅模式

发布订阅是一个常见的设计模式,开源社区中已经存在很多该模式的实现。其中 docker 项目中提供了一个 pubsub 的极简实现,下面是基于 pubsub 包实现的本地发布订阅代码:

package main
import (
    "fmt"
    "strings"
    "time"
    "github.com/moby/moby/pkg/pubsub"
)
func main() {
    p := pubsub.NewPublisher(100*time.Millisecond, 10)
    golang := p.SubscribeTopic(func(v interface{}) bool {
        if key, ok := v.(string); ok {
            if strings.HasPrefix(key, "golang:") {
                return true
            }
        }
        return false
    })
    docker := p.SubscribeTopic(func(v interface{}) bool {
        if key, ok := v.(string); ok {
            if strings.HasPrefix(key, "docker:") {
                return true
            }
        }
        return false
    })
    go p.Publish("hi")
    go p.Publish("golang: https://golang.org")
    go p.Publish("docker: https://www.docker.com/")
    time.Sleep(1)
    go func() {
        fmt.Println("golang topic:", <-golang)
    }()
    go func() {
        fmt.Println("docker topic:", <-docker)
    }()
    <-make(chan bool)
}

这段代码首先通过 pubsub.NewPublisher 创建了一个对象,然后通过 p.SubscribeTopic 实现订阅,p.Publish 来发布消息。

执行效果如下:

docker topic: docker: https://www.docker.com/
golang topic: golang: https://golang.org
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
    /Users/zhangyongxin/src/go-example/grpc-example/pubsub/server/pubsub.go:43 +0x1e7
exit status 2

订阅消息可以正常打印。

但有一个死锁报错,是因为这条语句 <-make(chan bool) 引起的。但是如果没有这条语句就不能正常打印订阅消息。

这里就不是很懂了,有没有大佬知道,欢迎留言,求指导。

接下来就用 gRPC 和 pubsub 包实现发布订阅模式。

需要实现四个部分:

  • proto 文件;
  • 服务端: 用于接收订阅请求,同时也接收发布请求,并将发布请求转发给订阅者;
  • 订阅客户端: 用于从服务端订阅消息,处理消息;
  • 发布客户端: 用于向服务端发送消息。

proto 文件

首先定义 proto 文件:

syntax = "proto3";
package proto;
message String {
    string value = 1;
}
service PubsubService {
    rpc Publish (String) returns (String);
    rpc SubscribeTopic (String) returns (stream String);
    rpc Subscribe (String) returns (stream String);
}

定义三个方法,分别是一个发布 Publish 和两个订阅 Subscribe 和 SubscribeTopic。

Subscribe 方法接收全部消息,而 SubscribeTopic 根据特定的 Topic 接收消息。

服务端

package main
import (
    "context"
    "fmt"
    "log"
    "net"
    "server/proto"
    "strings"
    "time"
    "github.com/moby/moby/pkg/pubsub"
    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
)
type PubsubService struct {
    pub *pubsub.Publisher
}
func (p *PubsubService) Publish(ctx context.Context, arg *proto.String) (*proto.String, error) {
    p.pub.Publish(arg.GetValue())
    return &amp;proto.String{}, nil
}
func (p *PubsubService) SubscribeTopic(arg *proto.String, stream proto.PubsubService_SubscribeTopicServer) error {
    ch := p.pub.SubscribeTopic(func(v interface{}) bool {
        if key, ok := v.(string); ok {
            if strings.HasPrefix(key, arg.GetValue()) {
                return true
            }
        }
        return false
    })
    for v := range ch {
        if err := stream.Send(&amp;proto.String{Value: v.(string)}); nil != err {
            return err
        }
    }
    return nil
}
func (p *PubsubService) Subscribe(arg *proto.String, stream proto.PubsubService_SubscribeServer) error {
    ch := p.pub.Subscribe()
    for v := range ch {
        if err := stream.Send(&amp;proto.String{Value: v.(string)}); nil != err {
            return err
        }
    }
    return nil
}
func NewPubsubService() *PubsubService {
    return &amp;PubsubService{pub: pubsub.NewPublisher(100*time.Millisecond, 10)}
}
func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    // 简单调用
    server := grpc.NewServer()
    // 注册 grpcurl 所需的 reflection 服务
    reflection.Register(server)
    // 注册业务服务
    proto.RegisterPubsubServiceServer(server, NewPubsubService())
    fmt.Println("grpc server start ...")
    if err := server.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

对比之前的发布订阅程序,其实这里是将 *pubsub.Publisher 作为了 gRPC 的结构体 PubsubService 的一个成员。

然后还是按照 gRPC 的开发流程,实现结构体对应的三个方法。

最后,在注册服务时,将 NewPubsubService() 服务注入,实现本地发布订阅功能。

订阅客户端

package main
import (
    "client/proto"
    "context"
    "fmt"
    "io"
    "log"
    "google.golang.org/grpc"
)
func main() {
    conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()
    client := proto.NewPubsubServiceClient(conn)
    stream, err := client.Subscribe(
        context.Background(), &amp;proto.String{Value: "golang:"},
    )
    if nil != err {
        log.Fatal(err)
    }
    go func() {
        for {
            reply, err := stream.Recv()
            if nil != err {
                if io.EOF == err {
                    break
                }
                log.Fatal(err)
            }
            fmt.Println("sub1: ", reply.GetValue())
        }
    }()
    streamTopic, err := client.SubscribeTopic(
        context.Background(), &amp;proto.String{Value: "golang:"},
    )
    if nil != err {
        log.Fatal(err)
    }
    go func() {
        for {
            reply, err := streamTopic.Recv()
            if nil != err {
                if io.EOF == err {
                    break
                }
                log.Fatal(err)
            }
            fmt.Println("subTopic: ", reply.GetValue())
        }
    }()
    &lt;-make(chan bool)
}

新建一个 NewPubsubServiceClient 对象,然后分别实现 client.Subscribe 和 client.SubscribeTopic 方法,再通过 goroutine 不停接收消息。

发布客户端

package main
import (
    "client/proto"
    "context"
    "log"
    "google.golang.org/grpc"
)
func main() {
    conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()
    client := proto.NewPubsubServiceClient(conn)
    _, err = client.Publish(
        context.Background(), &amp;proto.String{Value: "golang: hello Go"},
    )
    if err != nil {
        log.Fatal(err)
    }
    _, err = client.Publish(
        context.Background(), &amp;proto.String{Value: "docker: hello Docker"},
    )
    if nil != err {
        log.Fatal(err)
    }
}

新建一个 NewPubsubServiceClient 对象,然后通过 client.Publish 方法发布消息。

当代码全部写好之后,我们开三个终端来测试一下:

终端1 上启动服务端:

go run main.go

终端2 上启动订阅客户端:

go run sub_client.go

终端3 上执行发布客户端:

go run pub_client.go

这样,在 终端2 上就有对应的输出了:

subTopic:  golang: hello Go
sub1:  golang: hello Go
sub1:  docker: hello Docker

也可以再多开几个订阅终端,那么每一个订阅终端上都会有相同的内容输出。

源码地址:

https://github.com/yongxinz/go-example/tree/main/grpc-example/pubsub

REST 接口

gRPC 一般用于集群内部通信,如果需要对外提供服务,大部分都是通过 REST 接口的方式。开源项目 grpc-gateway 提供了将 gRPC 服务转换成 REST 服务的能力,通过这种方式,就可以直接访问 gRPC API 了。

但我觉得,实际上这么用的应该还是比较少的。如果提供 REST 接口的话,直接写一个 HTTP 服务会方便很多。

proto 文件

第一步还是创建一个 proto 文件:

syntax = "proto3";
package proto;
import "google/api/annotations.proto";
message StringMessage {
  string value = 1;
}
service RestService {
    rpc Get(StringMessage) returns (StringMessage) {
        option (google.api.http) = {
            get: "/get/{value}"
        };
    }
    rpc Post(StringMessage) returns (StringMessage) {
        option (google.api.http) = {
            post: "/post"
            body: "*"
        };
    }
}

定义一个 REST 服务 RestService,分别实现 GET 和 POST 方法。

安装插件:

go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway

生成对应代码:

protoc -I/usr/local/include -I. \
    -I$GOPATH/pkg/mod \
    -I$GOPATH/pkg/mod/github.com/grpc-ecosystem/grpc-gateway@v1.16.0/third_party/googleapis \
    --grpc-gateway_out=. --go_out=plugins=grpc:.\
    --swagger_out=. \
    helloworld.proto

--grpc-gateway_out 参数可生成对应的 gw 文件,--swagger_out 参数可生成对应的 API 文档。

在我这里生成的两个文件如下:

helloworld.pb.gw.go
helloworld.swagger.json

REST 服务

package main
import (
    "context"
    "log"
    "net/http"
    "rest/proto"
    "github.com/grpc-ecosystem/grpc-gateway/runtime"
    "google.golang.org/grpc"
)
func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    mux := runtime.NewServeMux()
    err := proto.RegisterRestServiceHandlerFromEndpoint(
        ctx, mux, "localhost:50051",
        []grpc.DialOption{grpc.WithInsecure()},
    )
    if err != nil {
        log.Fatal(err)
    }
    http.ListenAndServe(":8080", mux)
}

这里主要是通过实现 gw 文件中的 RegisterRestServiceHandlerFromEndpoint 方法来连接 gRPC 服务。

gRPC 服务

package main
import (
    "context"
    "net"
    "rest/proto"
    "google.golang.org/grpc"
)
type RestServiceImpl struct{}
func (r *RestServiceImpl) Get(ctx context.Context, message *proto.StringMessage) (*proto.StringMessage, error) {
    return &amp;proto.StringMessage{Value: "Get hi:" + message.Value + "#"}, nil
}
func (r *RestServiceImpl) Post(ctx context.Context, message *proto.StringMessage) (*proto.StringMessage, error) {
    return &amp;proto.StringMessage{Value: "Post hi:" + message.Value + "@"}, nil
}
func main() {
    grpcServer := grpc.NewServer()
    proto.RegisterRestServiceServer(grpcServer, new(RestServiceImpl))
    lis, _ := net.Listen("tcp", ":50051")
    grpcServer.Serve(lis)
}

gRPC 服务的实现方式还是和以前一样。

以上就是全部代码,现在来测试一下:

启动三个终端:

终端1 启动 gRPC 服务:

go run grpc_service.go

终端2 启动 REST 服务:

go run rest_service.go

终端3 来请求 REST 服务:

$ curl localhost:8080/get/gopher
{"value":"Get hi:gopher"}
$ curl localhost:8080/post -X POST --data '{"value":"grpc"}'
{"value":"Post hi:grpc"}

源码地址:

https://github.com/yongxinz/go-example/tree/main/grpc-example/rest

超时控制

最后一部分介绍一下超时控制,这部分内容是非常重要的。

一般的 WEB 服务 API,或者是 Nginx 都会设置一个超时时间,超过这个时间,如果还没有数据返回,服务端可能直接返回一个超时错误,或者客户端也可能结束这个连接。

如果没有这个超时时间,那是相当危险的。所有请求都阻塞在服务端,会消耗大量资源,比如内存。如果资源耗尽的话,甚至可能会导致整个服务崩溃。

那么,在 gRPC 中怎么设置超时时间呢?主要是通过上下文 context.Context 参数,具体来说就是 context.WithDeadline 函数。

proto 文件

创建最简单的 proto 文件,这个不多说。

syntax = "proto3";
package proto;
// The greeting service definition.
service Greeter {
    // Sends a greeting
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
    string name = 1;
}
// The response message containing the greetings
message HelloReply {
    string message = 1;
}

客户端

package main
import (
    "client/proto"
    "context"
    "fmt"
    "log"
    "time"
    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
)
func main() {
    // 简单调用
    conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
    defer conn.Close()
    ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Duration(3*time.Second)))
    defer cancel()
    client := proto.NewGreeterClient(conn)
    // 简单调用
    reply, err := client.SayHello(ctx, &amp;proto.HelloRequest{Name: "zzz"})
    if err != nil {
        statusErr, ok := status.FromError(err)
        if ok {
            if statusErr.Code() == codes.DeadlineExceeded {
                log.Fatalln("client.SayHello err: deadline")
            }
        }
        log.Fatalf("client.SayHello err: %v", err)
    }
    fmt.Println(reply.Message)
}

通过下面的函数设置一个 3s 的超时时间:

ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Duration(3*time.Second)))
defer cancel()

然后在响应错误中对超时错误进行检测。

服务端

package main
import (
    "context"
    "fmt"
    "log"
    "net"
    "runtime"
    "server/proto"
    "time"
    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/reflection"
    "google.golang.org/grpc/status"
)
type greeter struct {
}
func (*greeter) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
    data := make(chan *proto.HelloReply, 1)
    go handle(ctx, req, data)
    select {
    case res := &lt;-data:
        return res, nil
    case &lt;-ctx.Done():
        return nil, status.Errorf(codes.Canceled, "Client cancelled, abandoning.")
    }
}
func handle(ctx context.Context, req *proto.HelloRequest, data chan&lt;- *proto.HelloReply) {
    select {
    case &lt;-ctx.Done():
        log.Println(ctx.Err())
        runtime.Goexit() //超时后退出该Go协程
    case &lt;-time.After(4 * time.Second): // 模拟耗时操作
        res := proto.HelloReply{
            Message: "hello " + req.Name,
        }
        // //修改数据库前进行超时判断
        // if ctx.Err() == context.Canceled{
        //  ...
        //  //如果已经超时,则退出
        // }
        data &lt;- &amp;res
    }
}
func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    // 简单调用
    server := grpc.NewServer()
    // 注册 grpcurl 所需的 reflection 服务
    reflection.Register(server)
    // 注册业务服务
    proto.RegisterGreeterServer(server, &amp;greeter{})
    fmt.Println("grpc server start ...")
    if err := server.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

服务端增加一个 handle 函数,其中 case <-time.After(4 * time.Second) 表示 4s 之后才会执行其对应代码,用来模拟超时请求。

如果客户端超时时间超过 4s 的话,就会产生超时报错。

下面来模拟一下:

服务端:

$ go run main.go
grpc server start ...
2021/10/24 22:57:40 context deadline exceeded

客户端:

$ go run main.go
2021/10/24 22:57:40 client.SayHello err: deadline
exit status 1

源码地址

https://github.com/yongxinz/go-example/tree/main/grpc-example/deadline

总结

本文主要介绍了 gRPC 的三部分实战内容,分别是:

  • 发布订阅模式
  • REST 接口
  • 超时控制

个人感觉,超时控制还是最重要的,在平时的开发过程中需要多多注意。

结合上篇文章,gRPC 的实战内容就写完了,代码全部可以执行,也都上传到了 GitHub。

大家如果有任何疑问,欢迎给我留言,如果感觉不错的话,也欢迎关注和转发。

题图 该图片由 Reytschl 在 Pixabay 上发布

源码地址:

https://github.com/yongxinz/go-example

https://github.com/yongxinz/gopher

以上就是gRPC的发布订阅模式及REST接口和超时控制的详细内容,更多关于gRPC发布订阅REST接口的资料请关注我们其它相关文章!

(0)

相关推荐

  • grpcurl通过命令行访问gRPC服务

    目录 前言 gRPC Server helloworld.proto: main.go grpcurl 安装 Mac Docker go tool grpcurl 使用 可能遇到的错误 总结: 前言 一般情况下测试 gRPC 服务,都是通过客户端来直接请求服务端.如果客户端还没准备好的话,也可以使用 BloomRPC 这样的 GUI 客户端. 如果环境不支持安装这种 GUI 客户端的话,那么有没有一种工具,类似于 curl 这样的,直接通过终端,在命令行发起请求呢? 答案肯定是有的,就是本文要介

  • Go中的gRPC入门详解

    Go GRPC 入门 1,安装包 grpc golang-grpc 包提供了 gRPC 相关的代码库,通过这个库我们可以创建 gRPC 服务或客户端,首先需要安装他. go get -u google.golang.org/grpc 协议插件 要玩 gRPC,自然离不开 proto 文件,需要安装两个包,用于支持 protobuf 文件的处理. go get -u github.com/golang/protobuf go get -u github.com/golang/protobuf/pr

  • .NET Core(.NET6)中gRPC使用实践

    目录 一.简介 二.创建gRPC服务端 1.创建gRPC项目 2.编写自己的服务 三.创建gRPC客户端 1.创建客户端项目 2.grPC服务https的调用 3.gRPC内网http调用 4.IOC注入的方式调用gRPC 四.webapi中加入gRPC 一.简介 简单解析一下gRPC,gRPC 是一个由Google开源的,跨语言的,高性能的远程过程调用(RPC)框架. 特点: 跨语言 内容protobuf格式(比json体积小),网络传输快 使用HTTP/2进行传输 适合高性能轻量的微服务,一

  • golang下grpc框架的使用编写示例

    目录 1. 什么是grpc和protobuf 1.1 grpc 1.2 protobuf 2.go下grpc 2.1官网下载protobuf工具 2.2 下载go的依赖包 2.3 编写proto文件 2.4 生成hello.pb.proto文件 2.5 编写server端代码 2.6 编写client端代码 2.7 python和go相互调用实践(跨语言调用) 1. 什么是grpc和protobuf 1.1 grpc gRPC是一个高性能.开源和通用的RPC框架,面向移动和HTTP/2设计. 目

  • Go语言程序开发gRPC服务

    目录 前言 介绍 入门 proto server client 流方式 proto server client 验证器 proto Token 认证 认证函数: 拦截器: 初始化: 实现接口: 连接: 单向证书认证 生成证书 gRPC 代码 双向证书认证 生成带 SAN 的证书 gRPC 代码 Python 客户端 总结 前言 gRPC 这项技术真是太棒了,接口约束严格,性能还高,在 k8s 和很多微服务框架中都有应用. 作为一名程序员,学就对了. 之前用 Python 写过一些 gRPC 服务

  • go实现grpc四种数据流模式

    目录 2.1 简单模式 2.2 服务端数据流模式 2.3 客户端数据流模式 2.4 双向数据流 3.1 代码目录 3.2 编写stream.proto文件 3.3 编写server文件 3.4 编写client文件 1. 什么是数据流 grpc中的stream,srteam顾名思义就是一种流,可以源源不断的推送数据,很适合传输一些大数据,或者服务端和客户端长时间数据交互,比如客户端可以向服务端订阅一个数据,服务端就可以利用stream,源源不断地推送数据. 底层还原成socket编程 2. gr

  • gRPC的发布订阅模式及REST接口和超时控制

    前言 上篇文章 gRPC,爆赞 直接爆了,内容主要包括:简单的 gRPC 服务,流处理模式,验证器,Token 认证和证书认证. 在多个平台的阅读量都创了新高,在 oschina 更是获得了首页推荐,阅读量到了 1w+,这已经是我单篇阅读的高峰了. 看来只要用心写还是有收获的. 这篇咱们还是从实战出发,主要介绍 gRPC 的发布订阅模式,REST 接口和超时控制. 相关代码我会都上传到 GitHub,感兴趣的小伙伴可以去查看或下载. 发布和订阅模式 发布订阅是一个常见的设计模式,开源社区中已经存

  • JavaScript中发布/订阅模式的简单实例

    上次研究观察者模式,很多文章说它也叫Subscribe/Publish(发布/订阅模式).可在<Javascript设计模式>一书中,这两种模式还是有些区别的.书中原话如下: 1.Observer模式要求希望接收到主题通知者的观察者必须订阅内容改变的事件. 2.Subscribe/Publish模式使用了一个主题/事件通道,这个通道介于订阅者和发布者之间.该事件系统允许代码定义应用程序的特定事件,该事件可以传递自定义参数,自定义参数包含订阅者所需要的值.其目的是避免订阅者和发布者产生依赖关系.

  • 发布订阅模式在vue中的实际运用实例详解

    订阅发布模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象.这个主题对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态. 比如addEventListener 这个api就是个发布订阅模式 如果用过vue的同学,可以把他类比于 watch 下面我们看一个例子 var observe={ fnsObj:{}, // 订阅方法 on:function(key,fn){ if(!observe.fnsObj[key]){ observe.fnsObj[key]

  • JavaScript设计模式之观察者模式与发布订阅模式详解

    本文实例讲述了JavaScript设计模式之观察者模式与发布订阅模式.分享给大家供大家参考,具体如下: 学习了一段时间设计模式,当学到观察者模式和发布订阅模式的时候遇到了很大的问题,这两个模式有点类似,有点傻傻分不清楚,博客起因如此,开始对观察者和发布订阅开始了Google之旅.对整个学习过程做一个简单的记录. 观察者模式 当对象间存在一对多关系时,则使用观察者模式(Observer Pattern).比如,当一个对象被修改时,则会自动通知它的依赖对象.观察者模式属于行为型模式.在观察模式中共存

  • JAVA | Guava EventBus 使用 发布/订阅模式的步骤

    前言 EventBus 是 Guava 的事件处理机制,是观察者模式(生产/消费模型)的一种实现. 观察者模式在我们日常开发中使用非常广泛,例如在订单系统中,订单状态或者物流信息的变更会向用户发送APP推送.短信.通知卖家.买家等等:审批系统中,审批单的流程流转会通知发起审批用户.审批的领导等等. Observer模式也是 JDK 中自带就支持的,其在 1.0 版本就已经存在 Observer,不过随着 Java 版本的飞速升级,其使用方式一直没有变化,许多程序库提供了更加简单的实现,例如 Gu

  • node.js 发布订阅模式的实例

    实例如下: //导入内置模块 let EventEmitter = require('events'); let util=require('util'); //Man继承EventEmitter util.inherits(Man,EventEmitter); //创建一个函数 function Man(){} //实例化函数 let man=new Man(); function findGirl() { console.log('找新的女朋友') } function saveMoney(

  • js 发布订阅模式的实例讲解

    废话不多说,直接上代码 //发布订阅模式 class EventEmiter{ constructor(){ //维护一个对象 this._events={ } } on(eventName,callback){ if( this._events[eventName]){ //如果有就放一个新的 this._events[eventName].push(callback); }else{ //如果没有就创建一个数组 this._events[eventName]=[callback] } } e

  • 浅谈发布订阅模式与观察者模式

    背景 设计模式并非是软件开发的专业术语,实际上,"模式"最早诞生于建筑学. 设计模式的定义是:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案.通俗一点说,设计模式是在某种场合下对某个问题的一种解决方案.如果再通俗一点说,设计模式就是给面向对象软件开发中的一些好的设计取个名字. 这些"好的设计"并不是谁发明的,而是早已存在于软件开发中.一个稍有经验的程序员也许在不知不觉中数次使用过这些设计模式.GoF(Gang of Four--四人组,<设计模式&

  • JavaScript实现与使用发布/订阅模式详解

    本文实例讲述了JavaScript实现与使用发布/订阅模式.分享给大家供大家参考,具体如下: 一.发布/订阅模式简介 发布/订阅模式(即观察者模式): 设计该模式背后的主要动力是促进形成松散耦合.在这种模式中,并不是一个对象调用另一个对象的方法,而是一个订阅者对象订阅发布者对象的特定活动,并在发布者对象的状态发生改变后,订阅者对象获得通知.订阅者也称为观察者,而被观察的对象称为发布者或主题.当发生了一个重要的事件时,发布者将会通知(调用)所有订阅者,并且可能经常以事件对象的形式传递消息. 基本思

  • JavaScript事件发布/订阅模式原理与用法分析

    本文实例讲述了JavaScript事件发布/订阅模式原理与用法.分享给大家供大家参考,具体如下: 1.发布/订阅模式也是诸多设计模式当中的一种: 2.这种方式可以在es5下相当优雅地处理异步操作: 3.什么是发布/订阅呢?我们举个栗子: 假设fn1,fn2,fn3都可以视作一个事件的发布者,执行它,就会发布一个事件.这个时候,我们可以通过一个事件的订阅者去批量订阅并处理这些事件,包括它们的先后顺序.增加一个消息订阅者的方法: class AsyncFunArr { constructor (..

随机推荐