第9节 配置模式


❤️💕💕Java和Golang的设计模式,设计模式介绍、创建者模式、结构型模式、行为型模式。Myblog:http://nsddd.topopen in new window


[TOC]

why

当我们开发一个程序时,往往需要在不同的环境中运行它,比如开发环境、测试环境和生产环境。此时,我们需要对应用程序进行不同的配置,以确保在不同的环境中都能正常运行。而Go语言的设计模式中,提供了一种名为配置模式(Configuration Pattern)的解决方案,来满足这种需求。

配置模式概述

配置模式是一种软件设计模式,它允许我们在编写代码时将配置与实现分离。该模式将应用程序的配置与其实现分离开来,以便于在多个环境中使用相同的代码库,从而提高了代码的可重用性和可维护性。

Go语言中的配置模式

在Go语言中,我们可以使用结构体来存储配置信息。结构体中包含了应用程序需要的所有配置信息。例如,我们可以定义一个名为Config的结构体,并在其中添加必要的配置项:

type Config struct {
    Addr     string
    Port     int
    Username string
    Password string
}

当然,我们也可以使用其他的数据结构来表示配置数据,比如说 map、json、yaml …

然后,在应用程序中,我们可以使用这个Config结构体来读取和设置配置项。例如,我们可以编写以下代码来读取配置文件并将其加载到Config结构体中:

config := &Config{}

file, err := os.Open("config.json")
if err != nil {
    log.Fatal(err)
}

decoder := json.NewDecoder(file)
err = decoder.Decode(&config)
if err != nil {
    log.Fatal(err)
}

这里,我们使用了标准库中的json包来解析配置文件,并将其加载到Config结构体中。然后,我们就可以使用Config结构体中的属性来访问配置信息了。

在实际应用中,我们还可以通过环境变量、命令行参数等方式来设置配置项。例如,我们可以编写以下代码来从命令行参数中读取并设置配置项:

var addr string
var port int
var username string
var password string

flag.StringVar(&addr, "addr", "localhost", "server address")
flag.IntVar(&port, "port", 8080, "server port")
flag.StringVar(&username, "username", "", "database username")
flag.StringVar(&password, "password", "", "database password")

flag.Parse()

config := &Config{
    Addr:     addr,
    Port:     port,
    Username: username,
    Password: password,
}

这里,我们使用了标准库中的flag包来解析命令行参数,并将其设置到Config结构体中。

配置模式初级写法

在初级实现中,我们可以使用一个结构体来存储数据库的配置信息:

type DBConfig struct {
    Host     string
    Port     int
    User     string
    Password string
    Database string
}

然后,我们可以编写一个函数来读取这个结构体并建立与数据库的连接:

func NewDB(config *DBConfig) (*sql.DB, error) {
    dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", config.User, config.Password, config.Host, config.Port, config.Database)
    db, err := sql.Open("mysql", dsn)
    if err != nil {
        return nil, err
    }
    return db, nil
}

这里,我们使用了标准库中的 database/sql 包来建立与数据库的连接。该函数将从DBConfig结构体中获取配置信息,并根据这些信息创建DSN字符串(数据源名称),然后使用 sql.Open() 函数建立与数据库的连接。

数据库的配置模式高级写法

在高级实现中,我们可以进一步提高代码的可重用性和灵活性,使其更加符合开闭原则。具体而言,我们可以使用接口来定义一个抽象的数据库连接对象,并让具体的数据库驱动实现该接口。这样,我们就可以通过简单的配置文件、环境变量、命令行参数等方式来切换不同的数据库驱动,而不需要对应用程序进行大规模的修改。

首先,我们需要定义一个抽象的数据库连接对象:

type DBConnector interface {
    Connect(*DBConfig) (*sql.DB, error)
}

然后,我们可以编写具体的数据库驱动实现该接口。例如,我们可以编写一个名为MySQLConnector的结构体来实现MySQL数据库的连接:

type MySQLConnector struct {}

func (c *MySQLConnector) Connect(config *DBConfig) (*sql.DB, error) {
    dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", config.User, config.Password, config.Host, config.Port, config.Database)
    return sql.Open("mysql", dsn)
}

接下来,我们可以编写一个工厂函数来根据配置文件中的信息创建不同的数据库连接对象:

func NewDBConnector(config *DBConfig) (DBConnector, error) {
    switch config.Driver {
    case "mysql":
        return &MySQLConnector{}, nil
    case "postgres":
        return &PostgresConnector{}, nil
    default:
        return nil, errors.New("invalid database driver")
    }
}

这里,我们使用了switch语句来判断当前配置文件中指定的数据库驱动,并返回相应的数据库连接对象。如果该驱动不存在,则返回一个错误。

最后,我们可以再次编写一个函数来建立与数据库的连接,并将其封装在一个结构体中:

type DB struct {
    connector DBConnector
}

func (db *DB) Connect(config *DBConfig) (*sql.DB, error) {
    return db.connector.Connect(config)
}

这里,我们使用了一个DB结构体来封装具体的数据库连接对象,并提供了一个Connect()方法来建立与数据库的连接。在该方法中,我们调用具体的数据库连接对象的Connect()方法来建立连接。

常见写法

package main

import "fmt"

// Option 是配置选项
type Option func(*config)

// config 是需要配置的对象
type config struct{
    host string
    port int 
}

我们定义了一个 Option 函数类型和一个 config 结构体类型。 config 结构体是需要配置的对象,而 Option 则是用于设置 config 的选项。

接下来,我们可以定义一些 Option 函数以设置 config 对象的不同属性,例如:

func WithHost(host string) Option {
    return func(c *config) {
        c.host = host
    }
}

func WithPort(port int) Option {
    return func(c *config) {
        c.port = port
    }
}

这些 Option 函数将返回一个函数,该函数将获取指向 config 对象的指针并设置其相关属性。例如,WithHost 函数将获取 host 参数并将其设置为 config 对象的 host 属性。

现在,我们可以创建一个名为 NewConfig 的函数,该函数接受一个或多个 Option 函数,并使用这些函数来创建一个配置对象,如下所示:

func NewConfig(opts ...Option) *config {
    // 创建默认配置
    cfg := &config{
        host: "localhost",
        port: 8080,
    }

    // 应用所有选项
    for _, opt := range opts {
        opt(cfg)
    }

    return cfg
}

在此函数中,我们首先创建一个默认的 config 对象(此处为 localhost:8080)。然后,我们遍历所有传入的选项函数,并使用它们来修改 config 对象。最后,我们返回配置对象。

最后,我们可以使用如下方式使用此代码:

func main() {
    // 创建带有定制选项的配置对象
    cfg := NewConfig(
        WithHost("127.0.0.1"),
        WithPort(8888),
    )

    fmt.Printf("host: %s, port: %d", cfg.host, cfg.port)
}

运行上述代码将输出:host: 127.0.0.1, port: 8888。这说明我们成功地使用配置者模式创建了一个可以定制的配置对象。

总结

配置模式是一种非常有用的软件设计模式,它允许我们将配置与实现分离开来,以便于在不同的环境中使用相同的代码库。在Go语言中,我们可以使用结构体来存储配置信息,并通过各种方式(如配置文件、环境变量、命令行参数等)来设置配置项。这种方式不仅提高了代码的可重用性和可维护性,还可以让应用程序在不同环境中更加灵活和可配置。

END 链接