应使用工厂方法而非直接 new Client(),因微服务通信协议多样(gRPC/HTTP/Thrift/WebSocket),硬编码创建耦合协议细节,导致业务代码难以维护和测试;工厂方法通过统一接口、配置驱动和注册机制实现协议解耦与灵活切换。

为什么不能直接 new Client() 而要工厂方法?
微服务间通信协议不止 gRPC,还常混用 HTTP、Thrift 甚至 WebSocket;硬编码 NewGRPCClient() 或 NewHTTPClient() 会让调用方耦合协议细节,一换协议就得改业务代码。工厂方法把“创建谁”和“怎么用”解耦——业务只认 Client 接口,不关心底层是 grpc.ClientConn 还是 http.Client。
常见错误:在 service 层直接 import github.com/grpc 并 new 实例,导致测试时无法 mock,上线后协议切换成本极高。
- 工厂函数返回统一接口(如
type RPCClient interface { Call(ctx context.Context, req interface{}) error }) - 每个协议实现该接口,各自处理序列化、连接池、超时等细节
- 工厂入参用配置 struct(非字符串),避免拼错协议名或漏字段
工厂函数如何支持 gRPC + HTTP 同时注册?
Go 没有类继承,但可通过 map 注册不同协议的构造器。核心是定义一个全局 registry:var clientFactories = make(map[string]func(config Config) (RPCClient, error)),然后在 init 或模块加载时注册:
// grpc.go
func init() {
RegisterClientFactory("grpc", func(cfg Config) (RPCClient, error) {
conn, err := grpc.Dial(cfg.Addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, err
}
return &GRPCClient{conn: conn}, nil
})
}
// http.go
func init() {
RegisterClientFactory("http", func(cfg Config) (RPCClient, error) {
return &HTTPClient{client: &http.Client{Timeout: cfg.Timeout}}, nil
})
}
调用方只需传 "grpc" 或 "http" 字符串,工厂自动路由到对应构造逻辑。注意:字符串 key 必须全小写且固定,否则运行时报 panic: no factory registered for "GRPC"。
Config 结构体怎么设计才不踩坑?
协议差异大,但共性字段(Addr、Timeout、Retry)必须抽出来;协议特有字段(如 gRPC 的 TLSCertPath、HTTP 的 Header)用嵌套 struct 隔离,避免字段爆炸。
type Config struct {
Protocol string // "grpc", "http"
Addr string
Timeout time.Duration
Retry RetryConfig
GRPC *GRPCConfig `json:"grpc,omitempty"`
HTTP *HTTPConfig `json:"http,omitempty"`
}
type GRPCConfig struct {
TLSCertPath string
KeepAlive KeepAliveConfig
}
type HTTPConfig struct {
Header map[string]string
Method string // "POST", "PUT"
}
关键点:
- 所有指针字段设
omitempty,避免 JSON 反序列化时把空对象当零值 -
Protocol字段必须存在且校验非空,否则工厂找不到匹配项 - gRPC 的
KeepAlive和 HTTP 的Method绝对不要塞进顶层 Config,否则其他协议使用者会困惑
模块化时如何避免循环依赖?
工厂函数放在 client/factory.go,各协议实现放在 client/grpc/、client/http/ 子包,但子包若 import factory 包来调用 RegisterClientFactory,而 factory 又 import 子包的类型定义,就形成循环。
解法:用空 import + init 函数,让子包自己注册,factory 包不 import 任何子包。
// client/factory.go
func RegisterClientFactory(proto string, f func(Config) (RPCClient, error)) {
clientFactories[proto] = f
}
// client/grpc/register.go
import _ "yourmodule/client/grpc" // 触发 init
func init() {
factory.RegisterClientFactory("grpc", newGRPCClient)
}
这样 client/factory 只依赖标准库,各协议子包只依赖 client/factory 和自身协议 SDK,模块边界清晰。最容易被忽略的是:忘记在 main.go 里 import 协议子包的 register 文件,结果运行时报 no factory registered for "grpc" 却查不出原因。

















