最近重构了之前的代码,从之前的MVC结构切换到CleanArch结构,但是切换的时候代码风格有些混乱。在下面的代码中,repository就是一个仓库,主要用来封装数据库查询或者第三方微服务调用。它实现了domain.IAzRepository接口,其他层的代码只依赖这个接口,不依赖具体的实现。代码风格Style1在Go中,我们经常“返回实现(struct)并依赖接口”。实际上,当函数返回时,我们返回一个具体的实现。函数的参数或者我们依赖接口的Struct的成员。这种风格依赖好像违背了这个原则//repositoryrepositorystruct{db*gorm.DB}//NewAZRepositoryNewAZRepositoryfuncNewAZRepository(db*gorm.DB)domain.IAzRepository{return&repository{db:db}}风格二这种风格返回实现,而且由于没有export,所以看起来有封装的特点,但是如果你运行golint,你会发现会报错,因为这样写会导致我们将非exported的struct暴露给被暴露的//repositoryrepositorytyperepositorystruct{db*gorm.DB}//NewAZRepositoryNewAZRepositoryfuncNewAZRepository(db*gorm.DB)*repository{return&repository{db:db}}这种写法的主要问题在于,由于Repository是导出的,所以在其他外部包中可以直接通过&Repository{}初始化,这样初始化后使用会造成panic,因为成员函数是nil指针//RepositoryrepositorytypeRepositorystruct{db*gorm.DB}//NewAZRepositoryNewAZRepositoryfuncNewAZRepository(db*gorm.DB)*Repository{return&Repository{db:db}}总是很难选择。带着这个问题,我咨询了同组的同事和Go语言交流组的几位同学。大部分会选择款式三,少部分会选择款式一,款式2.几乎没有人选,我到底应该选什么?最终我选择的是样式1,是面向场景的,因为我们的包其实并不希望其他包直接依赖实现,因为在开发的过程中可能会单独拆解成微服务或者需要更换存储库。如果有直接依赖仓库的外部包,后续重构会很困难。另外,我们在其他地方一般都选择样式3,因为不导出结构名。外部初始化其实没有更好的办法,比如varrRepository。至于上面提到的直接字面量初始化的问题,我们可以通过统一的代码风格来解决。在外部包中,除了用于传递参数的Option结构外,其余不允许直接通过&XXX{}初始化
