之前对Golang的面向对象理解的不是很深刻。在实际项目中,有遇到这样的情况,才真正理解了面向对象编程带来的好处。
项目是这样的:需要写一个串口服务程序,用来收发数据。
我先选了一个比较简陋的库,可能后续会换。因此需要写一些通用的代码,这样后续换库的时候,可以不用对服务程序进行任何代码修改。
设计了一个接口SerialOperation,包含了一些串口操作的基本方法,我的串口服务程序要用到的方法。因此只要实现了这些方法的串口库,就可以不用任何代码修改,就能直接进行使用。
serialAT.go
package serialAT
import (
"fmt"
"time"
)
const (
SERIAL_BUFF_LEN = 65535
)
type SerialOperation interface {
Open() error
Close() error
Write(data []byte) (n int, err error)
Read(data []byte) (n int, err error)
Flush() error
}
type SerialReceiver struct {
Sender chan []byte
Data []byte
}
func Start_serial_at_service(iser SerialOperation, serialReceiver chan SerialReceiver) {
if err := iser.Open(); err != nil {
fmt.Println(err)
return
}
defer iser.Close()
for atcmd_receiver := range serialReceiver {
_, err := iser.Write(atcmd_receiver.Data)
if err != nil {
fmt.Println(err)
close(atcmd_receiver.Sender)
continue
}
time.Sleep(time.Millisecond * 20)
var buf = make([]byte, SERIAL_BUFF_LEN)
n, err := iser.Read(buf)
// iser.Flush()
if err != nil {
fmt.Println(err)
close(atcmd_receiver.Sender)
continue
}
atcmd_receiver.Sender <- buf[:n]
close(atcmd_receiver.Sender)
}
}
客户端通过信道像服务端发送数据,数据被封装在一个包含接收信道的结构体中,服务端在接收到数据后,处理完成了可以通过这个客户端传过来的信道将数据返回给对应的客户端。使用信道可以避免多线程间的通信同步问题,当有多个客户端请求是,信道会自动阻塞,等待当前处理完成。
开始使用的库是github.com/tarm/serial"
。
针对serial.Port
类型,缺少了Open()
,因此只需要实现了Open()
方法,就可以直接通过这个库来完成我们的串口服务了。代码如下:
serial.go
package serialAT
import (
"time"
"github.com/tarm/serial"
)
type SerialConf struct {
*serial.Config
*serial.Port
}
func New_serialAT(name string, baud int, timeout_ms int) *SerialConf {
return &SerialConf{
Config: &serial.Config{
Name: name,
Baud: baud,
ReadTimeout: time.Millisecond * 300000,
}}
}
func (s *SerialConf) Open() (err error) {
s.Port, err = serial.OpenPort(s.Config)
if err != nil {
return err
}
return nil
}
使用时:
/* 启动串口服务 */
serc := New_serialAT(SERIAL_AT_PORT, SERIAL_AT_BAUD, 300000) // 串口读取阻塞延迟设置为300s,AT+COPS=?首次执行会花费很长时间
go Start_serial_at_service(serc, serial_receiver)
后面,我切换了串口库为 go.bug.st/serial
。其中的serial.Port
类型缺少Open()
和Flush()
方法的实现,因此,新增一个文件,同样的,通过匿名结构体或者匿名接口,实现继承。这样我们新建的类型就只需要完成父类型缺少的SerialOperation
接口中的方法,就可以使用串口服务了。代码如下:
go-serial.go
package serialAT
import (
"go.bug.st/serial"
)
type Go_serial struct {
serial.Port
serial.Mode
PortName string
Timeout_ms int
}
func New_go_serial(name string, baud int, timeout_ms int) *Go_serial {
return &Go_serial{
Mode: serial.Mode{
BaudRate: baud,
},
PortName: name,
}
}
func (s *Go_serial) Open() error {
var err error
s.Port, err = serial.Open(s.PortName, &s.Mode)
if err != nil {
return err
}
return nil
}
func (s *Go_serial) Flush() error {
return nil
}
使用:
serc := New_go_serial(SERIAL_AT_PORT, SERIAL_AT_BAUD, 300000) // 串口读取阻塞延迟设置为300s,AT+COPS=?首次执行会花费很长时间
go Start_serial_at_service(serc, serial_receiver)
可以看出,针对串口服务的代码是不需要任何改动的。面向对象的编程思想,确实降低了耦合,维护修改起来是很方便的。