Go设计模式(8)-抽象工厂

上一篇文章讲解了简单工厂和工厂方法,文章链接为Go设计模式(7)-工厂模式,这篇文章讲一下抽象工厂。抽象工厂不是很常用,主要是因为抽象工厂解决的场景比较特殊,实际开发中很难遇到,但抽象工厂提供了减少类个数、增加系统可维护性的思路,还是很值得借鉴的。

本文UML类图链接为:https://www.processon.com/view/link/6080def6079129456d4beecf

本文代码链接为:https://github.com/shidawuhen/asap/blob/master/controller/design/8abstractFactory.go

1.定义

1.1抽象工厂

抽象工厂:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

UML类图

image-20210426233227690.png

1.2分析

UML类图显示抽象工厂比较复杂,其实拆分一下能发现和工厂方法Go设计模式(7)-工厂模式区别不大。Client是调用方,可以不考虑。对于存在的AbstractProductA和AbstractProductB,如果我们去掉AbstractProductB和其子类,是不是就和工厂模式一样了?

在增加了AbstractProductB后,复杂度就上升了。抽象工厂的定义变为了”提供一个创建一系列相关对象的接口“,不再是工厂方法中的”定义一个用于创建对象的接口“,一个工厂有了创建多个对象的能力。

2.使用场景

在下面的讲解之前,我先给大家举个实例,方便讲解和理解。适合抽象工厂的实例不算多,不过好歹从《大话设计模式》上找到一个,虽然这种情况比较少,但也不是没有。

假设我们做了一个学生管理系统,系统有两张表,分别是学生表和成绩表,学生表有插入、更新操作,成绩表有插入、列表操作。系统是20年前建的,存储使用的是Access,现在打算改成MySQL,今后学校有钱了,打算换成Oracle。

这种情况使用工厂模式就不合适了,原因有两个

  1. 工厂模式返回的Product都有同一个父类,Product因为多态可以被随便替换。但是学生类和成绩类操作不一样,强制设置出一个父类,并不合理。
  2. 即使强制出一个父类,会导致生成4个工厂,如果表的个数增多(N)、存储类型增多(M),工厂类的个数为N*M,导致工厂类个数剧增。

仔细分析这个实例,其实有两个维度:

低维度的是学生类和成绩类,分别对应AbstractProductA和AbstractProductB,无论是哪种存储,操作是相同的,所以可以使用继承。

高维度的是存储方式,因为存储方式包含了对应低维度的学生类和成绩类,即ProductA1、ProductB1或者ProductA2、ProductB2。

抽象工厂ConcreteFactory是一个高维度的工厂,它代表的是存储方式,用于生成该存储方式下的所有低维度的类。所以ConcreteFactory需要生成对应的学生类和成绩类。

其实在理解了两个维度之后,抽象工厂的思想也很容易向工厂方法上靠近。上一篇文章Go设计模式(7)-工厂模式中写过,工厂方法的实现是:工厂方法先返回指定工厂,然后通过指定工厂创建ConcreteProduct。那么抽象工厂的实现是:抽象工厂先返回高维度的指定工厂,然后通过该工厂创建所有低维度的ConcreteProduct

这种设计是合理的,首先在后期替换存储方式的时候,只需要统一修改创建的工厂即可,其它无需关注;其次无论增加低维度(科目类等)或者增加存储方式,工厂类变动很小,只需要增加函数(如对应增加低维度的科目类)或者增加工厂类(如对应增加高纬度的存储方式),Product类无法摆脱线性的增加(毕竟要编写新功能)。

3.代码实现

package main

import "fmt"

/// Product部分
/**
 * @Description: 学生接口,定义了插入和更新功能
 */
type Student interface {
   insert() bool
   update() bool
}

/**
 * @Description: Access操作Student类
 */
type AccessStudent struct {
}

/**
 * @Description: 使用Access向Student表中插入数据
 * @receiver a
 * @return bool
 */
func (a *AccessStudent) insert() bool {
   fmt.Println("AccessStudent insert")
   return true
}

/**
 * @Description: 使用Access向Student表中更新数据
 * @receiver a
 * @return bool
 */
func (a *AccessStudent) update() bool {
   fmt.Println("AccessStudent update")
   return true
}

/**
 * @Description: MySQL操作Student类
 */
type MySQLStudent struct {
}

/**
 * @Description: 使用MySQL向Student表中插入数据
 * @receiver a
 * @return bool
 */
func (m *MySQLStudent) insert() bool {
   fmt.Println("MySQLStudent insert")
   return true
}

/**
 * @Description: 使用MySQL向Student表中更新数据
 * @receiver a
 * @return bool
 */
func (m *MySQLStudent) update() bool {
   fmt.Println("MySQLStudent update")
   return true
}

/**
 * @Description: 成绩接口,定义了插入和列表功能
 */
type Score interface {
   insert() bool
   list() []int64
}

/**
 * @Description: 使用Access操作Score类
 */
type AccessScore struct {
}

/**
 * @Description: 使用Access向Score表中插入数据
 * @receiver a
 * @return bool
 */
func (a *AccessScore) insert() bool {
   fmt.Println("AccessScore insert")
   return true
}

/**
 * @Description: 使用Access从Score表中获取成绩列表
 * @receiver a
 * @return []int64
 */
func (a *AccessScore) list() []int64 {
   fmt.Println("AccessScore list")
   return []int64{1, 2}
}

/**
 * @Description: 使用MySQL操作Score类
 */
type MySQLScore struct {
}

/**
 * @Description: 使用MySQL向Score表中插入数据
 * @receiver a
 * @return bool
 */
func (m *MySQLScore) insert() bool {
   fmt.Println("MySQLScore insert")
   return true
}

/**
 * @Description: 使用MySQL从Score表中获取成绩列表
 * @receiver a
 * @return []int64
 */
func (m *MySQLScore) list() []int64 {
   fmt.Println("MySQLScore list")
   return []int64{1, 2}
}

/// Factory部分
/**
 * @Description: 抽象工厂接口,代表高维度工厂,高维度工厂能够生成低维度对象
 */
type Factory interface {
   createStudent() Student
   createScore() Score
}

/**
 * @Description: 高维度Access工厂
 */
type AccessFactory struct {
}

/**
 * @Description: 高维度Access工厂,创建Access的Student对象
 * @receiver a
 * @return Student
 */
func (a *AccessFactory) createStudent() Student {
   return &AccessStudent{}
}

/**
 * @Description: 高维度Access工厂,创建Access的Score对象
 * @receiver a
 * @return Score
 */
func (a *AccessFactory) createScore() Score {
   return &AccessScore{}
}

/**
 * @Description: 高维度MySQL工厂
 */
type MySQLFactory struct {
}

/**
 * @Description: 高维度MySQL工厂,创建MySQL的Student对象
 * @receiver a
 * @return Student
 */
func (m *MySQLFactory) createStudent() Student {
   return &MySQLStudent{}
}

/**
 * @Description: 高维度MySQL工厂,创建MySQL的Score对象
 * @receiver a
 * @return Score
 */
func (m *MySQLFactory) createScore() Score {
   return &MySQLScore{}
}

/// 获得高维度工厂
func getFactory(storeType string) Factory {
   switch storeType {
   case "MySQL":
      return &MySQLFactory{}
   case "Access":
      return &AccessFactory{}
   }
   return nil
}

func main() {
   //抽象工厂使用代码
   fmt.Println("------------抽象工厂")
   factory := getFactory("MySQL")
   if factory == nil {
      fmt.Println("不支持该存储方式")
      return
   }

   student := factory.createStudent()
   score := factory.createScore()

   student.insert()
   student.update()
   score.insert()
   score.list()
}

通过修改storeType,会自动更换存储方式。如果将factory、student、score设置为全局的,则只需要在创建的时候进行更改就可以方便的更改存储方式了。

返回为:

➜ myproject go run main.go

------------抽象工厂

AccessStudent insert

AccessStudent update

AccessScore insert

AccessScore list

➜ myproject go run main.go

------------抽象工厂

MySQLStudent insert

MySQLStudent update

MySQLScore insert

MySQLScore list

总结

抽象工厂模式能够减少工厂类创建的数量,它更加体现了如何管理、分类信息的思想。

抽象工厂符合单一职责原则(每个表一个类)、里氏替换原则(使用继承使得无论是工厂还是Product,子类对象能够替换父类对象出现的任何地方)、依赖倒转原则(工厂类和AbstractProduct类关联,而不是和细节关联)。

至于开闭原则,增加新类和新存储方式,扩展是开放的,违背封闭性的位置在getFactory函数和增加Factory中的函数,前者可以使用配置文件解决,后者因为是Go语言,其实只是在Factory接口中增加了函数命名,其它都没有修改,变动不大。

我们构建类似功能的时候需要使用抽象工厂模式吗?我的理解是可以把模式的架子搭起来,尽量让模式对开发人员影响最小。

因为在项目生命周期中,更改存储可能性极低,如果确实支持多套存储,意味着对一个表增加操作,所有存储都需要同步增加该函数,维护成本太高了。但很难说没这个可能,所以可以搭架子,只管理一个存储即可。

至于对开发人员影响最小,是指使用的时候,开发人员能够方便获取表操作对象,而不必去理解抽象工厂模式,否则会得不偿失。毕竟合理的设计不包含过度设计和使用繁琐。

最后

大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)

我的个人博客为:https://shidawuhen.github.io/

往期文章回顾:

招聘

  1. 字节跳动|抖音电商武汉服务端(高级)开发工程师
  2. 字节跳动|飞书大客户产品经理内推咯
  3. 字节跳动|抖音电商服务端技术岗位虚位以待
  4. 字节跳动招聘专题

设计模式

  1. Go设计模式(8)-抽象工厂
  2. Go设计模式(7)-工厂模式
  3. Go设计模式(6)-单例模式
  4. Go设计模式(5)-类图符号表示法
  5. Go设计模式(4)-代码编写优化
  6. Go设计模式(4)-代码编写
  7. Go设计模式(3)-设计原则
  8. Go设计模式(2)-面向对象分析与设计
  9. Go设计模式(1)-语法

语言

  1. Go工具之generate
  2. Go单例实现方案
  3. Go通道实现原理
  4. Go定时器实现原理
  5. Beego框架使用
  6. Golang源码BUG追查
  7. Gin框架简洁版
  8. Gin源码剖析

架构

  1. 支付接入常规问题
  2. 限流实现2
  3. 秒杀系统
  4. 分布式系统与一致性协议
  5. 微服务之服务框架和注册中心
  6. 浅谈微服务
  7. 限流实现1
  8. CDN请求过程详解
  9. 常用缓存技巧
  10. 如何高效对接第三方支付
  11. 算法总结

存储

  1. MySQL开发规范
  2. Redis实现分布式锁
  3. 事务原子性、一致性、持久性的实现原理
  4. InnoDB锁与事务简析

网络

  1. HTTP2.0基础教程
  2. HTTPS配置实战
  3. HTTPS连接过程
  4. TCP性能优化

工具

  1. GoLand实用技巧
  2. 根据mysql表自动生成go struct
  3. Markdown编辑器推荐-typora

读书笔记

  1. 原则
  2. 资治通鉴
  3. 敏捷革命
  4. 如何锻炼自己的记忆力
  5. 简单的逻辑学-读后感
  6. 热风-读后感
  7. 论语-读后感
  8. 孙子兵法-读后感

思考

  1. 为动员一切力量争取胜利而斗争
  2. 反对自由主义
  3. 实践论
  4. 评价自己的标准
  5. 服务端团队假期值班方案
  6. 项目流程管理
  7. 对项目管理的一些看法
  8. 对产品经理的一些思考
  9. 关于程序员职业发展的思考
  10. 关于代码review的思考
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值