[101]
0 EOF []
```
另外还可以使用 `io.ReadFull` 或者 `ioutil.ReadAll` 取读取字节流,`io.ReadFull`用法和`Read`差不多,`ioutil.ReadAll`不需要设置buf可直接返回buf。更多可参考:[How to use the io.Reader interface
](https://yourbasic.org/golang/io-reader-interface-explained/)
- **利用接口特性**
下面的代码是一个实时统计标准输入字符个数的代码。用户每次按下回车都可以看到当前输入的字符以及历史上已经输入的字符的个数。
``` golang
func CountNumber(input chan []byte) {
count := 0
for data := range input {
count += len(data)
fmt.Printf("Received %d Characters: === %s \n",count, data)
}
}
func main() {
bytes := make(chan []byte)
fmt.Println("Please enter the string to be calculated:")
go CountNumber(bytes)
for {
var input string
_, _ = fmt.Scanln(&input)
bytes <- []byte(input)
}
}
```
代码很简单,但是似乎和我们要讲到的接口式编程没什么关系。下面我们用接口封装一下。
``` golang
func NewCountReader() *CountReader {
return &CountReader{
bytes: make(chan []byte),
data: nil,
}
}
type CountReader struct { // 声明CountReader对象
bytes chan []byte
data []byte
}
func (h *CountReader) Read(p []byte) (int, error) { // 实现Read方法
ok := true
for ok && len(h.data) == 0 {
h.data, ok = <-h.bytes // 将bytes里的数据全部传给 data
}
if !ok || len(h.data) == 0 {
return 0, io.EOF // 可能读到了结尾
}
l := copy(p, h.data)
h.data = h.data[l:]
return l, nil
}
func (h *CountReader) run() {
b := bufio.NewReader(h)
count := 0
for true {
buf := make([]byte, 4)
n,err := b.Read(buf)
if err == io.EOF {
continue
}
count += len(buf[:n])
fmt.Printf("Received %d Characters: === %s \n",count, buf)
}
}
```
首先声明了一个结构体 `CountReader`, 再实现了一个 `Read()` 方法调用,我们知道实现了`Read()`即可以成为 `io.Reader` 接口的实现。也就是说 `CountReader` 就是一个 `io.Reader` ,那么 `io.Reader` 可以使用的方法,也可以给 `CountReader` 使用。这时就可以使用 `bufio` 这个库了。使用 `bufio.NewReader` 的函数对输入数据进行读取和计算。
bufio.NewReader()
方法提供一个缓存buf, 默认缓存4k buffer 缓冲器的实现原理就是,将文件读取进缓冲(内存)之中,再次读取的时候就可以避免文件系统的 I/O 从而提高速度。同理在进行写操作时,先把文件写入缓冲(内存),然后由缓冲写入文件系统。
``` go
func main() {
fmt.Println("Please enter the string to be calculated:")
Counter := NewCountReader()
go Counter.run()
for {
var input string
_, _ = fmt.Scanln(&input)
Counter.bytes <- []byte(input)
}
}
```
接下来通过上述代码即可完成相同的操作,这只是一个简单的例子,如果换成文件io、网络io就会有非常可观的收益。带来业务性能的提升。
# Functional Option
Functional Options 这个编程模式是一个函数式编程的应用案例,编程技巧也很好,是目前 Go 语言中最流行的一种编程模式。
假设实际编程中需要针对业务对象设置很多属性。
``` go
type Server struct {
Addr string
Port int
Protocol string
Timeout time.Duration
MaxConns int
TLS *tls.Config
}
```
在这个 Server 对象中,我们可以看到:
- 要设置侦听的 IP 地址 Addr 和端口号 Port。(必填)
- 协议、超时时间、最大链接数、TLS选项等属性需要配置。(非必填)
那么如何让调用方实现这个必填参数和非必填参数呢?一个方法是将非必填参数设成 `...interface{}` 但这样肯定不好,因为不同的参数类型都不一样。另一种方式就是将 必填参数和非必填参数分开了。
如非必填参数搞成一个结构体
``` golang
type Config struct {
Protocol string
Timeout time.Duration
Maxconns int
TLS *tls.Config
}
```
必填参数和这个 `Config` 直接传给初始化函数,如果没有要填的参数可以将 `Config` 设为 `nil` 。
这样一来 `Server` 结构体便成了这样, 初始化
``` golang
type Server struct {
Addr string
Port int
Conf *Config
}
func NewServer(addr string, port int, conf *Config) (*Server, error) {
//...
}
//Using the default configuratrion
srv1, _ := NewServer("localhost", 9000, nil)
conf := ServerConfig{Protocol:"tcp", Timeout: 60*time.Duration}
srv2, _ := NewServer("locahost", 9000, &conf)
```
这样便已经是大多数人的作法了。但是不是没有修改空间,下面介绍一下 Functional Option 方式。
## **初始化 Server 示例**
首先我们定义一个 `Option` 类型:
``` golang
type Option func(*Server)
```
用函数式方式定义一组函数。
``` golang
func Protocol(p string) Option {
return func(s *Server) {
s.Protocol = p
}
}
func Timeout(timeout time.Duration) Option {
return func(s *Server) {
s.Timeout = timeout
}
}
func MaxConns(maxconns int) Option {
return func(s *Server) {
s.MaxConns = maxconns
}
}
func TLS(tls *tls.Config) Option {
return func(s *Server) {
s.TLS = tls
}
}
```
这组代码的含义是传入一个参数,返回一个函数,函数会将 `Server` 结构的对应参数值进行设置。例如,当我们调用其中的一个函数 MaxConns(30) 时,其返回值是一个 func(s* Server) { s.MaxConns = 30 } 的函数。
这下,我们可以定义一个 `NewServer` 函数,其中有一个可变参数 `option` ,用一个循环来设置 Server 的属性。不仅提供了默认值,还提供将默认值改成可修改选项进行修改。
```golang
func NewServer(addr string, port int, options ...func(*Server)) (*Server, error) {
srv := Server{
Addr: addr,
Port: port,
Protocol: "tcp",
Timeout: 30 * time.Second,
MaxConns: 1000,
TLS: nil,
}
for _, option := range options {
option(&srv)
}
//...
return &srv, nil
}
```
于是,我们在创建 Server 对象的时候,就可以像下面这样:
``` golang
s1, _ := NewServer("localhost", 1024)
s2, _ := NewServer("localhost", 2048, Protocol("udp"))
s3, _ := NewServer("0.0.0.0", 8080, Timeout(300*time.Second), MaxConns(1000))
```
这下对 Server 的封装就像搭积木一样简单容易并且可视化很好。