前言
今天继续完成我们的第一个Go语言web项目,上期说到模拟数据库的建立,那么今天需要编写dao层和service层的代码
代码编写
编写dao层
这是我dao文件夹下的文件目录
xxx_dao.go文件是接口的定义,xxxx_dao_impl.go是对应的实现
├─dao
│ post_dao.go
│ post_dao_impl.go
│ topic_dao.go
│ topic_dao_impl.go
老师的项目里面是没有定义接口的,关于为什么要定义接口这个事情,相信有一些开发经验的小伙伴还是很清楚的,接口能让我们很轻松的拓展程序。老师的代码没有接口不是老师的代码不好,因为只是一个简单的demo,老师没有必要建立接口。
我们自己实践的过程中可以适当的去拓展一下老师讲的案例,把自己的想法融入到代码中去,才能让自己更快地掌握知识
这是topic_dao_impl.go文件的源码
package dao
import "github.com/jun-chiang/go-web-demo1/entity"
// TopicDao定义了查询Topic的方式
type TopicDao interface {
// 根据Topic的ID查询Topi对象
QueryTopicById(id uint64) (*entity.Topic, error)
}
这是post_dao.go文件的源码
package dao
import "github.com/jun-chiang/go-web-demo1/entity"
type PostDao interface {
QueryPostListByTopicId(id uint64) ([]*entity.Post, error)
}
这是topic_dao_impl.go文件的源码
package dao
import (
"sync"
"github.com/jun-chiang/go-web-demo1/entity"
"github.com/jun-chiang/go-web-demo1/repository"
)
// 定义结构体
type TopicDaoImpl struct {
}
var (
// 声明结构体变量
topicDaoImpl *TopicDaoImpl
initialOnce sync.Once
)
// 获取TopicDaoImpl的全局唯一实例
func NewTopicDaoImplInstance() *TopicDaoImpl {
// 单例模式
initialOnce.Do(func() {
// 为结构体变量赋值
topicDaoImpl = &TopicDaoImpl{}
})
// 返回已经赋值的topicDaoImpl结构体对象指针
return topicDaoImpl
}
// 实现Topic_dao的方法
func (*TopicDaoImpl) QueryTopicById(id uint64) (*entity.Topic, error) {
return repository.TopicIndexMap[id], nil
}
这里面也是采用了sync
库来实现单例模式,然后加上实现接口的方法,就是一套完整的编码了。至于为什么要采用单例模式来编写这个结构体,我也不是很确定。根据自己的经验,单例模式的对象在数据的存储上有一定优势,毕竟这个对象一直存在,并且仅此一家,可以做到数据共享,但是由于这个demo实在过于简单,发挥不出来它的优势。不过这不重要,老师这样写,肯定是由有原因的,说不定在真实的Go语言项目开发中就是这样的,我们在学习阶段学会模仿也是个不错的进步。
这是post_dao_impl.go文件的源码
package dao
import (
"github.com/jun-chiang/go-web-demo1/entity"
"github.com/jun-chiang/go-web-demo1/repository"
)
// 定义结构体
type PostDaoImpl struct {
}
var postDaoImpl *PostDaoImpl
// 获取TopicDaoImpl的全局唯一实例
func NewPostDaoImplInstance() *PostDaoImpl {
// 单例模式
initialOnce.Do(func() {
// 为结构体变量赋值
postDaoImpl = &PostDaoImpl{}
})
// 返回已经赋值的topicDaoImpl结构体对象指针
return postDaoImpl
}
// 实现Topic_dao的方法
func (*PostDaoImpl) QueryPostListByTopicId(id uint64) ([]*entity.Post, error) {
return repository.PostIndexMap[id], nil
}
没有难点存在,非常地简单哈。这里复习一下map的知识点,map是一种存储键值对数据的数据结构,通过key能很快找到对应的value。这里在查询的时候其实只是简单地传入一个id,然后就从map里面拿到值了,后续要继续优化的话,需要判断是否真正拿到值了,这是对于意外情况的一种处理,能提高程序的健壮性,避免因为一些考虑不周到导致的程序异常。我们平时在业务中也可以使用map来优化一些查询,比如是否存在之类的需求就可以用map来实现。
编写service层
service层文件列表
├─service
│ query_page_info.go
service层下面就一个文件,因为这个demo只有一个功能。这一层的功能主要就是控制业务的流程,毕竟主题和主题的回复列表是需要组装的。整个流程可以分为三个部分
1 参数校验
2 准备数据
3 数据组装
这样分工有助于代码函数的职责划分,提高编码质量,利于后续维护
不完整的query_page_info.go
文件源码(只有上半部分)
package service
import (
"errors"
"strconv"
"sync"
"github.com/jun-chiang/go-web-demo1/dao"
"github.com/jun-chiang/go-web-demo1/entity"
)
type QueryPageInfoFlow struct {
topicIdStr string
topicId uint64
PageInfo *entity.PageInfo
topic *entity.Topic
postList []*entity.Post
}
// 通过TopicId查询页面信息的方法
func QueryPageInfo(topicIdStr string) (*entity.PageInfo, error) {
return NewQueryPageInfoFlow(topicIdStr).Do()
}
// 工厂函数,返回查询流程结构体的指针,用于启动流程
func NewQueryPageInfoFlow(topicIdStr string) *QueryPageInfoFlow {
return &QueryPageInfoFlow{
topicIdStr: topicIdStr,
}
}
// 查询页面信息的整个流程
func (f *QueryPageInfoFlow) Do() (*entity.PageInfo, error) {
// 参数校验
if err := f.checkParam(); err != nil {
return nil, err
}
// 准备数据
if err := f.prepareInfo(); err != nil {
return nil, err
}
// 组装实体
if err := f.packageInfo(); err != nil {
return nil, err
}
return f.PageInfo, nil
}
仔细一看就能发现这里缺少了Do
这个函数里面checkParam
,prepareInfo
,packageInfo
三个函数,它们分别对应了这里业务处理中的三个流程。
参数校验主要是涉及到对于topicId
的校验
准备数据主要是分别查询出Topic
的数据和Post
的数据
组装实体主要是把这些数据组装起来,成为能展示在页面上的数据
小伙伴们可以自己尝试编写一下相关代码,老师的视频里面也没有细讲这三个部分,只是给出了项目源码地址,让大家去看,我的实现和老师的实现略用不同,我觉得我编写的代码功能分工更明确。
这三个函数的具体代码下期见哦,将会在第三期完成整个项目的构建。
总结
学习不能着急,要注意代码中的细节,特别是对于我们这种处于新手期的同学(大佬请忽略)
最后,请你记得劳逸结合哦