Golang实现两种FTP连接的组织方式:通用方法与特定方法

Golang实现两种FTP连接的组织方式:通用方法与特定方法 我有两个FTP连接(FTP1,FTP2)。每个连接都有一些共同的实现(如 connect()、download() 等),以及特定的实现(例如,FTP1 中的文件格式为 .gzipFTP2 中的文件格式为 .zip,因此有特定的 unzip() 方法)。

我希望能够像这样调用它们:

func main() {
	var acqs []Acquisition
	ftp, err := NewFTP(os.Getenv("FTP_USER"), os.Getenv("FTP_PASSWORD"), os.Getenv("FTP_HOST"), os.Getenv("FTP_PORT"))
	if err != nil {
		panic(err)
	}
	ftp1 := &FTP1{*ftp}
	ftp2 := &FTP2{*ftp}


	acqs = append(acqs, ftp1, ftp2)
	for _, acq := range acqs {
		meters, err := acq.FetchMeters()
		if err != nil {
			log.Warn(acq.Name(), " got error :", err)
		}
		log.Info(meters)
	}
}

其中:

type Acquisition interface {
	FetchMeters() ([]Meter, error)
	Name() string
}

type Meter struct {
	ID          string
	OperationID string
	Unit        string
}

type FTP struct {
	Username string
	Password string
	Url      string
	Port     string
	client   *goftp.FTP
}
type FTP1 struct {
	FTP
}
type FTP2 struct {
	FTP
}

func NewFTP(user, password, url, port string) (*FTP, error) {
	var err error
	ftp := &FTP{
		Username: user,
		Password: password,
		Url:      url,
		Port:     port,
	}
	if ftp.client, err = goftp.Connect(url + ":" + port); err != nil {
		return nil, err
	}
	if err = ftp.client.Login(user, password); err != nil {
		return nil, err
	}
	return ftp, nil
}

对于所有FTP连接,FetchMeters() 方法将是:

func (ftp FTP) FetchMeters() ([]Meter, error) {
	log.Info(ftp.Name(), " is running")
	file := ftp.Download("")
	file = ftp.Unzip("")   // 我有多个 Unzip 的实现
	log.Info(file)
	return nil, nil
}

其中 Download() 是所有 FTP 结构体共有的,但 Unzip() 是 ftp1 和 ftp2 特有的。

当然,在我的例子中,FTP 类型并没有 Unzip() 方法,所以会失败。

我应该做的是将接收器设置为一个抽象类型,即接口 FTPAcq

type FTPAcq interface {
	Unzip(file string) string
}

func (ftp FTPAcq) FetchMeters() ([]Meter, error) {
    ...
}

但编译器报错了。

我花了很长时间思考应该如何做,这看起来很简单,但我无法理解我遗漏了什么。

以下是完整的代码:

https://play.golang.org/p/6fsu5HIsP79


更多关于Golang实现两种FTP连接的组织方式:通用方法与特定方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html

11 回复

JuSun:

但是编译器报错了。

关于什么?

更多关于Golang实现两种FTP连接的组织方式:通用方法与特定方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这正是我想要实现的目标,但我却做不到 😞

也许传递一个实现了 FTP 接口的类型实例作为参数?

JuSun:

g

它显示:

invalid receiver type FTPAcq (FTPAcq is an interface type)

所以你想在接口 FTPAcq 上实现方法 FetchMeters

不,你尝试在接口上实现一个方法,这与传递参数不同。也许这样?

func FetchMeters(ftp FTPAcq) ([]Meter, error)

不,在FetchMeters中,我希望能够使用 file = ftp.Unzip(""),但 Unzip() 在FTP1和FTP2上有不同的实现。

是的,但现在,我该如何调用 Unzip() 而无需调用 ftp1.Unzip()ftp2.Unzip() 呢?

我希望有一个方法,它不关心自己管理的是哪种类型的 ftp,然后调用 ftp.Unzip(),这个调用应该能将代码重定向到正确的 Unzip() 实现。

嗯,编译器很可能在抱怨这个:

func (ftp FTPAcq) FetchMeters() ([]Meter, error) {

你试图在接口 FTPAcq 上实现方法 FetchMeters,这是没有意义的。在 Go 语言中,接口定义的是方法签名,而结构体类型负责实现它们。

删除

func (ftp FTPAcq) FetchMeters() ([]Meter, error) {
    ...
}

关于你的问题:你的接口 FTPAcq 定义了一个方法 Unzip(file string) string。如果你想为 FTP1FTP2 以不同的方式实现这个方法,可以这样做:

func (ftp1 FTP1) Unzip(file string) string {
    // 实现 1
}

func (ftp2 FTP2) Unzip(file string) string {
    // 实现 2
}

我可以这样做:

func (acq FTPAcq) FetchMeters(ftp FTP) ([]Meter, error) {
	log.Info(ftp.Name(), " is running")
	file := Download("")
	file = acq.Unzip("") // 我有多个Unzip的实现
	log.Info(file)
	return nil, nil
}

但现在,meters, err := acq.FetchMeters() 的签名与接口不匹配。

需要说明的是,我还有另一种采集类型:API。为了简化,我省略了它,但FetchMeters()将根据它是API还是FTP有两种不同的实现。然后我有多个APIAPI1API2)和FTPFTP1FTP2)的实现。

在写作过程中,我已经尝试了100种不同的方法,但找不到一种适用于所有情况的方法。

ftp.FetchMeters() 和 api.FetchMeters() 必须有两种不同的实现。ftp 将满足 FTPAcq 接口,而 api 将满足 APIAcq 接口。

type FTPAcq interface {
	Unzip(file string) string
}

type APIAcq interface {
	OtherMethod(file string) string
}

如你所见,我有点迷茫了。

在Go中,可以通过接口组合和嵌入来实现这种模式。以下是完整的解决方案:

package main

import (
	"fmt"
	"log"
)

// Meter 结构体
type Meter struct {
	ID          string
	OperationID string
	Unit        string
}

// Acquisition 接口
type Acquisition interface {
	FetchMeters() ([]Meter, error)
	Name() string
}

// FTP 基础结构体
type FTP struct {
	Username string
	Password string
	Url      string
	Port     string
	client   interface{} // 模拟FTP客户端
}

// FTPAcq 接口,定义特定方法
type FTPAcq interface {
	Acquisition
	Unzip(file string) string
	Download(file string) string
}

// FTP1 结构体
type FTP1 struct {
	FTP
}

// FTP2 结构体
type FTP2 struct {
	FTP
}

// NewFTP 创建基础FTP连接
func NewFTP(user, password, url, port string) (*FTP, error) {
	ftp := &FTP{
		Username: user,
		Password: password,
		Url:      url,
		Port:     port,
		client:   "connected",
	}
	return ftp, nil
}

// Download 通用方法
func (ftp *FTP) Download(file string) string {
	return fmt.Sprintf("Downloaded file from %s:%s", ftp.Url, ftp.Port)
}

// Name 方法
func (ftp *FTP) Name() string {
	return "BaseFTP"
}

// FTP1 的特定实现
func (ftp1 *FTP1) Unzip(file string) string {
	return "Unzipped with .gzip format"
}

func (ftp1 *FTP1) FetchMeters() ([]Meter, error) {
	log.Printf("%s is running", ftp1.Name())
	file := ftp1.Download("")
	unzipped := ftp1.Unzip(file)
	log.Printf("FTP1: %s", unzipped)
	
	return []Meter{
		{ID: "FTP1-001", OperationID: "OP1", Unit: "kWh"},
	}, nil
}

func (ftp1 *FTP1) Name() string {
	return "FTP1"
}

// FTP2 的特定实现
func (ftp2 *FTP2) Unzip(file string) string {
	return "Unzipped with .zip format"
}

func (ftp2 *FTP2) FetchMeters() ([]Meter, error) {
	log.Printf("%s is running", ftp2.Name())
	file := ftp2.Download("")
	unzipped := ftp2.Unzip(file)
	log.Printf("FTP2: %s", unzipped)
	
	return []Meter{
		{ID: "FTP2-001", OperationID: "OP2", Unit: "m³"},
	}, nil
}

func (ftp2 *FTP2) Name() string {
	return "FTP2"
}

func main() {
	var acqs []Acquisition
	
	// 创建基础FTP连接
	ftp, err := NewFTP("user", "password", "host", "21")
	if err != nil {
		panic(err)
	}
	
	// 创建特定FTP实例
	ftp1 := &FTP1{*ftp}
	ftp2 := &FTP2{*ftp}
	
	acqs = append(acqs, ftp1, ftp2)
	
	for _, acq := range acqs {
		meters, err := acq.FetchMeters()
		if err != nil {
			log.Printf("%s got error: %v", acq.Name(), err)
		}
		log.Printf("Meters: %v", meters)
	}
}

或者使用更简洁的接口组合方式:

package main

import (
	"fmt"
	"log"
)

// Unzipper 解压接口
type Unzipper interface {
	Unzip(file string) string
}

// Downloader 下载接口
type Downloader interface {
	Download(file string) string
}

// FTP 基础结构体实现通用方法
type FTP struct {
	Username string
	Password string
	Url      string
	Port     string
}

func (ftp *FTP) Download(file string) string {
	return fmt.Sprintf("Downloaded from %s", ftp.Url)
}

// FTP1 实现
type FTP1 struct {
	*FTP
}

func (f *FTP1) Unzip(file string) string {
	return "GZIP unzipped"
}

func (f *FTP1) FetchMeters() ([]Meter, error) {
	file := f.Download("")
	unzipped := f.Unzip(file)
	log.Printf("FTP1 processed: %s", unzipped)
	return []Meter{{ID: "1"}}, nil
}

func (f *FTP1) Name() string {
	return "FTP1"
}

// FTP2 实现
type FTP2 struct {
	*FTP
}

func (f *FTP2) Unzip(file string) string {
	return "ZIP unzipped"
}

func (f *FTP2) FetchMeters() ([]Meter, error) {
	file := f.Download("")
	unzipped := f.Unzip(file)
	log.Printf("FTP2 processed: %s", unzipped)
	return []Meter{{ID: "2"}}, nil
}

func (f *FTP2) Name() string {
	return "FTP2"
}

// 工厂函数
func NewFTP1(ftp *FTP) *FTP1 {
	return &FTP1{FTP: ftp}
}

func NewFTP2(ftp *FTP) *FTP2 {
	return &FTP2{FTP: ftp}
}

这样每个FTP类型都实现了自己的Unzip()方法,同时通过嵌入FTP结构体继承了通用的Download()方法。

回到顶部