开发项目时为了易于扩展和复用代码使用到了接口,对接口的几点思考做一下简单总结
接口和方法
接口中可以定义一些方法,当某个对象具备其中的某个或某些方法时,则就说这个对象实现了此接口,如
http_server.go
package main
import (
"fmt"
"net/http"
)
type Counter struct {
n int
}
func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
ctr.n++
fmt.Fprintf(w, "counter = %d\n", ctr.n)
}
func main() {
ctr := new(Counter)
http.Handle("/counter", ctr)
}
http.handle的第二个参数是接口类型,定义如下,定义了ServerHTTP函数指纹,而Counter对象实现了此方法,那么就可以说Counter实现了Handler接口类型,所以ctr可以作为http.Handle的参数
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
就这个case来说,对于http请求的处理,net/http包提供了许多方法,包括net/http已定义和上面自定义的方法。所以接口就像一副勾勒成型的简笔画,我们可以根据形体涂上合适的颜色。
使用接口的小例子
以下是部署服务的大概代码逻辑,一般部署服务都包含相同的几步,如初始化安装包、创建目录、初始化配置文件、脚本启动文件、启动服务,所以将这共有的部分,所以可以将这些方法指纹定义在一个接口中,然后具备这些相同方法的服务的对象(比如不同服务的参数对象)可以定义这些方法,进而这些对象也就实现了此接口;
方法DeployServerCommon通过Deploy接口实现这些公共的属性方法;
type serverAInstance common.ServerAInstance
type Deploy interface {
Init(serverPort int32)
InitPackage(agentAddr string) error
RenderConf(agentAddr string) error
RenderScirpt(agentAddr string) error
CreateDir(agentAddr string) error
Start(agentAddr string) error
}
type ClusterAlter interface {
DeployServer(dp Deploy, cluster_name string) error
}
type ServerAConf struct {
Port int32 `json:"port"`
BaseDir string `json:"baseDir"`
DeployDir string `json:"deployDir"`
PidDir string `json:"pidDir"`
DataDir string `json:"dataDir"`
LogFileDir string `json:"logFileDir"`
ConfDir string `json:"confDir"`
ScriptDir string `json:"scriptDir"`
PackageDir string `json:"packageDir"`
ServerControlFile string `json:"serverControlFile"`
ConfFile string `json:"confFile"`
}
//生成部署目录相关的变量
func (r *ServerAConf) Init(port int32){
r.DeployDir = "/test"
r.Port = port
r.BaseDir = fmt.Sprintf("%v/ServerA_%v", r.DeployDir, r.Port)
r.PidDir = fmt.Sprintf("%v/var", r.BaseDir)
r.DataDir = fmt.Sprintf("%v/data", r.BaseDir)
r.LogFileDir = fmt.Sprintf("%v/log", r.BaseDir)
r.ScriptDir = fmt.Sprintf("%v/script", r.DeployDir)
r.ServerControlFile = fmt.Sprintf("%v/service.sh", r.ScriptDir)
r.ConfFile = fmt.Sprintf("%v/conf.conf", r.ConfDir)
return err
}
//渲染服务配置文件并调用agent生成配置文件
func (serverAConf *ServerAConf) RenderConf(agentAddr string) error {
var (
serverAConfText string
err error
buf bytes.Buffer
)
t, err := template.New("serverAConf").Parse(ServerAConfTemplate)
if err != nil {
common.Log.Warn("fail to parse ServerA conf template. error=[%v]", err)
return err
}
err = t.Execute(&buf, serverAConf)
serverAConfText = buf.String()
if err != nil {
common.Log.Warn("fail to execute ServerA conf template. error=[%v]", err)
return err
}
// 创建配置文件
cmd := fmt.Sprintf("echo '''%v''' >%v", serverAConfText, serverAConf.ConfFile)
_, err = networker.SendShellExecMsgToServer(cmd, agentAddr)
if err != nil {
common.Log.Warn("fail to exec shell cmd. cmd=[%v] error=[%v]", cmd, err)
return err
}
common.Log.Notice("serverA generate config file successfully.")
return err
}
//调用agent创建 server启停脚本
func (serverAConf *ServerAConf) RenderScirpt(agentAddr string) ...
}
//创建目录如果不存在
func (config *ServerAConf) CreateDir(agentAddr string) error {
...
}
// 初始化安装包
func (config *ServerAConf) InitPackage(agentAddr string) error {
...
}
// 将部署服务中公共的的逻辑抽取到一个函数中
func DeployServerCommon(dp Deploy, serverPort int32, agentAddr string) error {
var err error
//初始化配置
err = dp.Init(serverPort)
if err != nil {
common.Log.Warn("fail to init configure. err=[%v]", err)
return err
}
// 下载安装包
err = dp.InitPackage(agentAddr)
if err != nil {
common.Log.Warn("fail to init package. error=[%v]", err)
}
//创建相关目录
err = dp.CreateDir(agentAddr)
if err != nil {
common.Log.Warn("fail to create dir. error=[%v]", err)
return err
}
//生成配置文件
err = dp.RenderConf(agentAddr)
if err != nil {
common.Log.Warn("fail to generate config. error=[%v]", err)
}
//生成脚本
err = dp.RenderScirpt(agentAddr)
if err != nil {
common.Log.Warn("fail to generate server control script. error=[%v]", err)
}
//启动服务
err = dp.Start(agentAddr)
if err != nil {
common.Log.Warn("fail to start server. error=[%v]", err)
return err
}
return err
}
//部署单个ServerA服务
func (instance *ServerAInstance) DeployServer(dp Deploy, clusterName string) error {
var (
err error
)
serverPort := instance.Port
agentAddr := fmt.Sprintf("%v:%v", instance.IP, instance.AgentPort)
err = DeployServerCommon(dp, clusterName, serverPort, agentAddr)
if err != nil {
common.Log.Warn("fail to deploy server. serverAInstance=[%s:%d] error=[%v]",
instance.IP, serverPort, err)
return err
}
common.Log.Notice("deploy server successfully. edisInstance=[%s:%d]",
instance.IP, serverPort)
return err
}
通过调用上述方法部署服务serverA
func TestDeployServer(t *testing.T) {
Convey("deploy serverA server success", t, func() {
var (
ca ClusterAlter
instance = new(ServerAInstance)
serverAConf = new(ServerAConf)
)
instance.IP = "127.0.0.1"
instance.Port = 6000
instance.AgentPort = 8000
ca = instance
err := ca.DeployServer(serverAConf, "demo_1")
So(err, ShouldBeNil)
})
}
空接口
由于空接口没有定义任何方法指纹,所以任何类型都可以实现空接口,这也就是如果不确定参数类型可以使用interface{}的原因,不过当需要操作此类型时,需要先对interface{}进行类型断言