Golang中更好的设计模式探讨
Golang中更好的设计模式探讨 我有一个Go服务,用于收集我感兴趣的一些数据,并通过gRPC暴露这些数据。数据收集部分我是在主函数中作为一个“服务”启动的。但随着我为程序添加更多功能,我意识到很可能有更好的实现方式……例如:
主函数中的代码片段:
func (app *Application) startServcies() {
// pass the config to the rtsc service
rtscWatcher := services.ErcotRtscWatcher{
Details: &app.Config.Details.RTSC,
InfoLog: app.InfoLog,
ErrorLog: app.ErrorLog,
Stream: app.RtscStream,
}
sppWatcher := services.ErcotSppWatcher{
Details: &app.Config.Details.SPP,
InfoLog: app.InfoLog,
ErrorLog: app.ErrorLog,
Stream: app.SppStream,
}
// start the watcher
go rtscWatcher.Run()
go sppWatcher.Run()
我的做法是在每个服务类型上实现 Run 方法,然后在主函数中逐个实例化它们。
一个Run方法的示例:
func (s *WeatherMonitor) Run() {
// ensure user config doesn't do anything cheeky like set runtime to 0
if err := ServiceValidator(s.Details); err != nil {
s.ErrorLog.Println(err)
return
}
// unpack the time and zone from the config
t, z := s.Details.StartAt[0], s.Details.StartAt[1]
// set the zone
tz, _ := time.LoadLocation(z)
if !s.Details.Scheduled {
// this branch starts immediately
s.InfoLog.Printf("%v is starting. running for %vs every %vs", s.Details.Name, s.Details.Runtime, s.Details.Refresh)
for i := 0; i < (s.Details.Runtime / s.Details.Refresh); i++ {
go GetWeather(s.Stream, s.ErrorLog)
msg := <-s.Stream
s.Store = append(s.Store, &msg)
time.Sleep(time.Duration(s.Details.Refresh) * time.Second)
}
if !s.Details.ReRun {
s.InfoLog.Println("terminating", s.Details.Name)
return
}
s.InfoLog.Println(s.Details.Name, "rotating service")
}
}
有什么建议吗?
更多关于Golang中更好的设计模式探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
我能感觉到可能有更好的方法来做这件事。
从你提供的两段代码片段中,我没有看出任何“(代码)异味”。想象一下,如果我需要在这个代码库上工作,并且需要修复一个问题,例如在天气监控器中,我可能会从 main 函数开始,找到你的 startServices,然后找到天气监控器的配置和启动位置,接着从那里深入 Run 函数,等等。也就是说,这看起来是一个合理的(再次强调,基于这两个片段;我实际上没有看到你的 WeatherMonitor 本身是在哪里创建或启动的,但如果它类似于你的 ErcotRtscWatcher 和 ErcotSppWatcher,那么对我来说这似乎是合理的)、可读性好的设计 🤷。
你是否在寻求改进某种,我想称之为,“品质”?也许是不想让 startServices 列出每一个实现?或许是想要一种启用/禁用某些服务的方法?还是其他什么?
更多关于Golang中更好的设计模式探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Go中处理多个后台服务时,可以使用更结构化的模式来管理生命周期和依赖注入。以下是几种改进方案:
1. 使用接口统一服务管理
type Service interface {
Start() error
Stop() error
Name() string
}
type ServiceManager struct {
services []Service
mu sync.RWMutex
}
func (sm *ServiceManager) Register(s Service) {
sm.mu.Lock()
defer sm.mu.Unlock()
sm.services = append(sm.services, s)
}
func (sm *ServiceManager) StartAll() error {
sm.mu.RLock()
defer sm.mu.RUnlock()
for _, s := range sm.services {
if err := s.Start(); err != nil {
return fmt.Errorf("failed to start %s: %w", s.Name(), err)
}
}
return nil
}
2. 实现具体的服务类型
type WatcherService struct {
watcher services.ErcotRtscWatcher
done chan struct{}
wg sync.WaitGroup
}
func (ws *WatcherService) Start() error {
ws.done = make(chan struct{})
ws.wg.Add(1)
go func() {
defer ws.wg.Done()
ws.watcher.Run(ws.done)
}()
return nil
}
func (ws *WatcherService) Stop() error {
close(ws.done)
ws.wg.Wait()
return nil
}
func (ws *WatcherService) Name() string {
return "rtsc-watcher"
}
3. 重构Run方法支持优雅关闭
func (s *WeatherMonitor) Run(stopCh <-chan struct{}) {
if err := ServiceValidator(s.Details); err != nil {
s.ErrorLog.Println(err)
return
}
ticker := time.NewTicker(time.Duration(s.Details.Refresh) * time.Second)
defer ticker.Stop()
s.InfoLog.Printf("%v starting", s.Details.Name)
for {
select {
case <-stopCh:
s.InfoLog.Printf("%v stopping", s.Details.Name)
return
case <-ticker.C:
go GetWeather(s.Stream, s.ErrorLog)
select {
case msg := <-s.Stream:
s.Store = append(s.Store, &msg)
case <-stopCh:
return
}
}
}
}
4. 在主函数中使用服务管理器
func (app *Application) startServices() error {
manager := &ServiceManager{}
rtscWatcher := &WatcherService{
watcher: services.ErcotRtscWatcher{
Details: &app.Config.Details.RTSC,
InfoLog: app.InfoLog,
ErrorLog: app.ErrorLog,
Stream: app.RtscStream,
},
}
sppWatcher := &WatcherService{
watcher: services.ErcotSppWatcher{
Details: &app.Config.Details.SPP,
InfoLog: app.InfoLog,
ErrorLog: app.ErrorLog,
Stream: app.SppStream,
},
}
manager.Register(rtscWatcher)
manager.Register(sppWatcher)
app.ServiceManager = manager
return manager.StartAll()
}
5. 使用context管理服务生命周期
type ContextService interface {
Start(ctx context.Context) error
}
type WeatherMonitor struct {
// ... existing fields
ctx context.Context
cancel context.CancelFunc
}
func (wm *WeatherMonitor) Start(ctx context.Context) error {
wm.ctx, wm.cancel = context.WithCancel(ctx)
go func() {
ticker := time.NewTicker(time.Duration(wm.Details.Refresh) * time.Second)
defer ticker.Stop()
for {
select {
case <-wm.ctx.Done():
return
case <-ticker.C:
wm.collectData()
}
}
}()
return nil
}
func (wm *WeatherMonitor) collectData() {
go GetWeather(wm.Stream, wm.ErrorLog)
select {
case msg := <-wm.Stream:
wm.Store = append(wm.Store, &msg)
case <-wm.ctx.Done():
return
}
}
这些模式提供了更好的服务管理、优雅关闭和错误处理机制,使代码更易于测试和维护。

