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

2 回复

我能感觉到可能有更好的方法来做这件事。

从你提供的两段代码片段中,我没有看出任何“(代码)异味”。想象一下,如果我需要在这个代码库上工作,并且需要修复一个问题,例如在天气监控器中,我可能会从 main 函数开始,找到你的 startServices,然后找到天气监控器的配置和启动位置,接着从那里深入 Run 函数,等等。也就是说,这看起来是一个合理的(再次强调,基于这两个片段;我实际上没有看到你的 WeatherMonitor 本身是在哪里创建或启动的,但如果它类似于你的 ErcotRtscWatcherErcotSppWatcher,那么对我来说这似乎是合理的)、可读性好的设计 🤷。

你是否在寻求改进某种,我想称之为,“品质”?也许是不想让 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
    }
}

这些模式提供了更好的服务管理、优雅关闭和错误处理机制,使代码更易于测试和维护。

回到顶部