Golang uses object-oriented thinking to program 2

Keywords: Go Back-end

Remember the best code I've ever written for this vegetable chicken.

The purpose is to write a configuration file saving module to save some configurations that need to be saved in the project.
Because the data to be saved is scattered in many modules. I have thought about building a large structure, and then put all the data to be saved into this structure, but when saving and reading, I need to transfer the data of other modules to this large structure (there are many modules, which is very inconvenient to operate). Another idea is to directly define the data to be saved by other modules in this module, which will destroy the integrity of the module.

Then I chose the first method. After writing, the problem of package circular import is found. Package B is my configuration saving module, which contains the data structure of package A, so package B needs to import package A. However, package A needs to use the save method of package B, and package A needs to import package B. Then GG...

At this time, if the second method is considered and the data structure is defined in the configuration saving module, the problem of package circular import will not occur. However, the module I wrote before is broken. I want to move the defined structure to the saved configuration module. Then, the programmer's persistence does not allow me to do so.

Then, using the object-oriented idea, I define a structure in the module of saving configuration, including read configuration and write configuration. A structure is also defined to save all configuration data from different modules. In order not to import circularly, an empty interface is used. Here, I define two empty interfaces, because only the configuration data of two packages need to be saved.

type GetConfiger interface {
	GetConfig(*GlobalConfig) error
	SetConfig(*GlobalConfig, GetConfiger)
}

/* Use the empty interface to solve the problem of package circular import */
type GlobalConfig struct {
	LogConfig interface{} `json:"LogConfig"`
	ApnParam  interface{} `json:"ApnParam"`
}

As a result, once defined in this way, I feel that this structure can store any configuration.
The code of the last toss is as follows:

package file

import (
	"encoding/json"
	"errors"
	"os"

	log "github.com/sirupsen/logrus"
)

type GetConfiger interface {
	GetConfig(*GlobalConfig) error
	SetConfig(*GlobalConfig, GetConfiger)
}

/* Use the empty interface to solve the problem of package circular import */
type GlobalConfig struct {
	LogConfig interface{} `json:"LogConfig"`
	ApnParam  interface{} `json:"ApnParam"`
}

const (
	GLOBALCONFIG_FILE = "global.conf"
)

/* Global configuration */
var gblConf = &GlobalConfig{}

func init() {
	ReadFile()
}

func Write(conf GetConfiger) {
	conf.SetConfig(gblConf, conf)
	data, err := json.MarshalIndent(gblConf, "", "\t")
	if err != nil {
		log.Error(err)
	}

	err = os.WriteFile(GLOBALCONFIG_FILE, data, 0666)
	if err != nil {
		log.Error(err)
	}
}

func Read(g GetConfiger) error {
	return g.GetConfig(gblConf)
}

func ReadFile() {
	data, err := os.ReadFile(GLOBALCONFIG_FILE)
	if err != nil {
		log.Warn(err)
		return
	}

	err = json.Unmarshal(data, gblConf)
	if err != nil {
		log.Warn(err)
		return
	}
}

func (gb *GlobalConfig) GetConfigToStruct(gi GetConfiger, gs interface{}) error {
	if gs == nil {
		err := errors.New("interface{} is nil for parse")
		log.Warn(err)
		return err
	}
	data, err := json.Marshal(gs)
	if err != nil {
		log.Error(err)
		return err
	}

	err = json.Unmarshal(data, gi)
	if err != nil {
		log.Error(err)
	}
	return err
}

Then my log module defines the following data types.

type LogConfig struct {
	/* Log file handle */
	logFile *os.File `json:"-"`
	/* The log format is "text" or "json" */
	Format string `json:"Format"`
	/* Log output to file */
	ToFile bool `json:"ToFile"`
	/* Log output to terminal */
	ToStdout bool `json:"ToStdout"`
	/* Log level */
	Level string `json:"Level"`
	/* Log file name */ 
	OutFile string `json:"OutFile"`
	/* Log file size limit unit: Byte */
	MaxSize uint32 `json:"MaxSize"`
	/* Log format control */
	Formatter *MyFormatter `json:"Formatter"`
}

type MyFormatter struct {
	/* Print call information, file line */
	ReportCaller bool
	/* Display level information */
	ShowLevel bool
	/* Log with color */
	ColorEnable bool
	/* presentation time stamp  */
	Timestamp bool
	/* timestamp format  */
	TimestampFormat  string
	callerPrettyfier func(*runtime.Frame) (file string, function string, line string) `json:"-"`
}

To use the configuration save module at this time, you only need the following lines of code.

  • Two methods of getconfigger interface are implemented. The interface to structure code here has been written in my module. Just call it directly. The SetConfig method needs to be implemented. It is OK to save the structure to which field in the GlobalConfig structure, which determines which field will be used as the key of json in this configuration.
func (lc *LogConfig) GetConfig(g *file.GlobalConfig) error {
	return g.GetConfigToStruct(lc, g.LogConfig)
}

func (lc *LogConfig) SetConfig(g *file.GlobalConfig, conf file.GetConfiger) {
	g.LogConfig = *lc
}
  • use
func main() {
	lc := &LogConfig{}
	file.Read(lc)
	lc.ToStdout = false
	file.Write(lc)
}

You need to add configuration, add fields in the GlobalConfig structure, and implement the two methods of the getconfigger interface (just copy and paste according to the above implementation, just 2 lines of code), so you can happily use the Write and Read methods to save and Read the configuration.

Two other modules use the case of saving the configuration module.

type APN_param struct {
	Apn     string
	User    string
	Passwd  string
	Dialnum string
}

func (a *APN_param) GetConfig(g *file.GlobalConfig) error {
	return g.GetConfigToStruct(a, g.ApnParam)
}

func (a *APN_param) SetConfig(g *file.GlobalConfig, conf file.GetConfiger) {
	g.ApnParam = *a
}

func main() {
	apn := APN_param{}
	file.Read(&apn)
	apn.Apn = "CMCC"
	file.Write(&apn)
}

Solve all problems perfectly. It does not import circularly, and the data and operation are separated from modules. It also supports the saving of all user-defined types of configurations without modifying the code. It is also very convenient to use.

Posted by teebo on Tue, 16 Nov 2021 08:08:20 -0800