设计模式之 - 工厂模式
工厂模式属于创建型模式
工厂模式
简单工厂模式
工厂方法模式
抽象工厂模式
三种模式的命名迷惑性很强,但功能差别巨大
,并且和实际的业务需求有关联性,如果不仔细研磨,不结合实际的业务场景,基本很难理解和合适地应用于实际业务
作者不会举例制造车辆或其他实物的案例,因为这些例子看似形象,实则不贴近实际代码业务,常常看完之后还要脑补实际的代码业务来做类比,本文将以开发 电商App首页 的客户端开发者视角,使用需求推动三种工厂设计模式转变
的思路,解释工厂模式
物料
Swift5.2.4
简单工厂模式
- 模式结构
- 工厂
- 抽象产品
- 具体产品
- 案例
假设你正在编写一个电商App首页
,首页有两种类型:带导航栏的首页
,带底部工具栏
的首页。这两种首页随后端下发字段而变动呈现
两种首页相同和不同点如下:
相同点:都有一个ListView,是数据的主要承载UI
不同点:一个有导航栏没底部工具栏,另一个有底部工具栏没导航栏;都有一个按钮,但按钮的布局不同
- 对应的代码实现:
import Foundation
import UIKit
enum HomePageType {
case navigationBar
case tabBar
}
/// 简单工厂
class HomePageFactory {
class func product(withType type: HomePageType) -> HomePage {
let vc: HomePage
switch type {
case .navigationBar:
vc = NavigationBarHomePage()
let navigationBar = UINavigationBar()
vc.view.addSubview(navigationBar)
case .tabBar:
vc = TabBarHomePage()
let tabBar = UITabBar()
vc.view.addSubview(tabBar)
}
return vc
}
}
/// 抽象产品 - 普通风格首页
class HomePage: UIViewController {
var backGroundImage: UIImage? // 基础参数 - 背景图
var listView = UITableView() // 基础参数 - 主UI(承载主要数据)
}
/// 具体产品 - 导航栏风格首页
class NavigationBarHomePage: HomePage {
//可以重写listView
}
/// 具体产品 - 工具栏风格首页
class TabBarHomePage: HomePage {
//可以重写listView
}
使用简单工厂模式
制造产品:
class Client {
func product() {
let haveTabBarHomePage = HomePageFactory.product(withType: . tabBar)
debugPrint("创建了一个底部工具栏风格首页 = \(haveTabBarHomePage)")
}
}
- 弊端
违背开闭原则
如果需求变化,需要新增侧边栏
样式的首页该怎么办? 答案是只能修改HomePage的代码,在HomePageFactory中的product(withType type:) 方法中新增一个switch case(case .sideMenu
)分支,代码如下:
/// 工厂类型枚举
enum HomePageType {
case navigationBar
case tabBar
case sideMenu // 新增
}
/// 简单工厂
class HomePageFactory {
class func product(withType type: HomePageType) -> HomePage {
let vc: HomePage
switch type {
case .navigationBar:
vc = NavigationBarHomePage()
let navigationBar = UINavigationBar()
vc.view.addSubview(navigationBar)
case .tabBar:
vc = TabBarHomePage()
let tabBar = UITabBar()
vc.view.addSubview(tabBar)
case .sideMenu: // 新增
vc = SideMenuHomePage()
let sideMenu = UIView()
vc.view.addSubview(sideMenu)
}
return vc
}
}
这明显违背了面向对象的开放封闭原则(OCP,Open Closed Principle)
那么有没有方式解决这个问题?答案是:有,即工厂方法模式
工厂方法模式
为了解决简单工厂模式
的弊端,我们接着讲工厂方法模式
- 模式结构
- 抽象工厂
- 具体工厂
- 抽象产品
- 具体产品
- 案例
假如声明一个抽象工厂类
,只提供接口或者只给出默认的实现用来创建普通的首页;再分别声明两个具体工厂类
来创建导航栏风格首页
和工具栏风格首页
。那么当新增侧边栏风格首页
时,再新声明一个具体工厂类
来创建侧边栏风格首页
,修改代码时是不是就不会动到已声明的抽象工厂类
和两个具体工厂类
相对应的代码呢?答案是:是的。
- 对应的代码实现
import Foundation
import UIKit
class Client {
func product() {
let haveTabBarHomePage = NavigationBarHomePageFactory.product()
debugPrint("创建了一个底部工具栏风格首页 = \(haveTabBarHomePage)")
}
}
/// 简单工厂
class HomePageFactory {
class func product() -> HomePage {
let vc = HomePage()
return vc
}
}
/// 具体工厂 - 导航栏风格首页
class NavigationBarHomePageFactory: HomePageFactory {
override class func product() -> HomePage {
let vc = NavigationBarHomePage()
let navigationBar = UINavigationBar()
vc.view.addSubview(navigationBar)
return vc
}
}
/// 具体工厂 - 工具栏风格首页
class TabBarHomePageFactory: HomePageFactory {
override class func product() -> HomePage {
let vc = TabBarHomePage()
let tabBar = UITabBar()
vc.view.addSubview(tabBar)
return vc
}
}
/// 具体工厂 - 侧边栏风格首页
class SideMenuHomePageFactory: HomePageFactory {
override class func product() -> HomePage {
let vc = SideMenuHomePage()
let sideMenu = UIView()
vc.view.addSubview(sideMenu)
return vc
}
}
/// 抽象产品 - 普通风格首页
class HomePage: UIViewController {
var backGroundImage: UIImage? // 基础参数 - 背景图
var listView = UITableView() // 基础参数 - 主UI(承载主要数据)
}
/// 具体产品 - 导航栏风格首页
class NavigationBarHomePage: HomePage {
//可以重写listView
}
/// 具体产品 - 工具栏风格首页
class TabBarHomePage: HomePage {
//可以重写listView
}
/// 具体产品 - 侧边栏风格首页
class SideMenuHomePage: HomePage {
//可以重写listView
}
- 弊端
被动忽略多维度场景,造成模糊职责、代码耦合、产品层级混乱
当界面复杂,我们一定需要为界面注入一个Model或者ViewModel(我们假设引入的是ViewModel),那ViewModel的构造逻辑应该写在哪里?按照经验主义的编程方式 或者 在工厂方法模式下,可能创建逻辑是会在界面中进行。
从设计模式的角度来看。面对构造过程复杂(过程简单还用得着设计模式?)的ViewModel,这是一种耦合的、混乱产品层级的设计,并且不符合控制反转的设计原则;View和ViewModel应只互为绑定者,但ViewModel的构造逻辑不应在View中进行。
抽象工厂模式
为了解决经验主义的编程方式
或者 工厂方法模式
弊端,我们接着讲抽象工厂模式
- 模式结构
- 抽象工厂
- 具体工厂
- 抽象产品(至少2个维度)
- 具体产品
- 案例
案例在上述的工厂方法模式
弊端一小节中已经讲述了一部分
简单说下MVVM。1.在应对庞大的业务时,MVC
模块架构容易造成胖VC、UI和数据层业务耦合,我们在开发时为了便于扩展维护
和单元测试
,常会采用MVVM
模块架构。2.电商App首页一般是业务逻辑的集中区,导航栏风格首页
、底部工具栏首页
、侧边栏首页
,必定有许多相同的、不同的功能。面对复杂且需单测的业务,我们使用MVVM
来设计模块结构。
那MVVM
和抽象工厂模式
又有什么关联呢?有,关联大了去了。
抽象工厂模式
和MVVM
结合。
ViewModel
和其子类
构成一种产品结构
View
和其子类
构成一种产品结构
某种ViewModel
和某种View
的组合即为:产品族
这应该是抽象工厂模式
最有价值的应用点之一了
- 对应的代码实现
import Foundation
import UIKit
class Client {
func product() {
let haveTabBarHomePageViewModel = TabBarHomePageAndViewModelFactory.productHomePageViewModel() // 生产一个ViewModel
let haveTabBarHomePage = TabBarHomePageAndViewModelFactory.productHomePage() // 生产一个View
debugPrint("创建了一个底部工具栏风格首页的ViewModel = \(haveTabBarHomePageViewModel)")
debugPrint("创建了一个底部工具栏风格首页 = \(haveTabBarHomePage)")
}
}
// MARK: - 抽象工厂 和 具体工厂
/// 抽象工厂
class HomePageFactory {
/// 生产抽象产品 - ViewModel
class func productHomePageViewModel() -> HomePageViewModel {
let viewModel = HomePageViewModel()
return viewModel
}
/// 生产抽象产品 - View
class func productHomePage() -> HomePage {
let vc = NavigationBarHomePage()
let navigationBar = UINavigationBar()
vc.view.addSubview(navigationBar)
return vc
}
}
/// 具体工厂 - 生产导航栏风格的首页ViewModel & View
class NavigationBarHomePageAndViewModelFactory: HomePageFactory {
/// 生产具体产品 - ViewModel
override class func productHomePageViewModel() -> HomePageViewModel {
return NavigationBarHomePageViewModel()
}
/// 生产具体产品 - View
override class func productHomePage() -> HomePage {
let vc = NavigationBarHomePage()
let navigationBar = UINavigationBar()
vc.view.addSubview(navigationBar)
return vc
}
}
/// 具体工厂 - 生产底部工具栏风格的首页ViewModel & View
class TabBarHomePageAndViewModelFactory: HomePageFactory {
/// 生产具体产品 - ViewModel
override class func productHomePageViewModel() -> HomePageViewModel {
return TabBarHomePageViewModel()
}
/// 生产具体产品 - View
override class func productHomePage() -> HomePage {
let vc = TabBarHomePage()
let tabBar = UITabBar()
vc.view.addSubview(tabBar)
return vc
}
}
/// 具体工厂 - 生产侧边栏风格的首页ViewModel & View
class SideMenuHomePageAndViewModelFactory: HomePageFactory {
/// 生产具体产品 - ViewModel
override class func productHomePageViewModel() -> HomePageViewModel {
return SideMenuHomePageViewModel()
}
/// 生产具体产品 - View
override class func productHomePage() -> HomePage {
let vc = SideMenuHomePage()
let sideMenu = UIView()
vc.view.addSubview(sideMenu)
return vc
}
}
// MARK: - 产品结构 之 HomePageViewModel
/// HomePage界面对应的ViewModel
class HomePageViewModel {
var title: String = ""
var haveBackButton = true
var itemViewModels = [String]()
}
/// 导航栏风格界面对应的ViewModel
class NavigationBarHomePageViewModel: HomePageViewModel {
var navigationLeftIconName = "" // 导航栏右侧IconName
var navigationAlpha = 0.6 // 导航栏不透明度值
}
/// 底部TabBar风格界面对应的ViewModel
class TabBarHomePageViewModel: HomePageViewModel {
var tabBarTitles = [String]() // TabBar各个按钮的Title
}
/// 底部TabBar风格界面对应的ViewModel
class SideMenuHomePageViewModel: HomePageViewModel {
var sideMenuItemViewModels = [AnyObject]() // 侧边栏按钮对应的itemViewModel集合
}
// MARK: - 产品结构 之 HomePage
/// 抽象产品
class HomePage: UIViewController {
var backGroundImage: UIImage? // 基础参数 - 背景图
var listView = UITableView() // 基础参数 - 主UI(承载主要数据)
}
/// 具体产品 - 导航栏风格首页
class NavigationBarHomePage: HomePage {
//可以重写listView
}
/// 具体产品 - 工具栏风格首页
class TabBarHomePage: HomePage {
//可以重写listView
}
/// 具体产品 - 侧边栏风格首页
class SideMenuHomePage: HomePage {
//可以重写listView
}
- 弊端
模式复杂,学习成本高
增加一种产品结构
时,会侵入已有代码
比如各个风格的首页都将绑定与风格对应的弹框视图
,
弹框视图基类 = PopUpView
各风格首页对应的弹框视图子类 = XXXPopUpView
此时要在各个工厂类里加入构造弹框视图
的方法和逻辑,
甚至各个工厂类类名也要修改
举例:
1.加入逻辑
/// 生产具体产品 - PopUpView
override class func productPopUpView() -> PopUpView {
return NavigationBarHomePagePopUpView()
}
2.修改类名
NavigationBarHomePageAndViewModelFactory
-> NavigationBarHomePageAndViewModelAndPopUpViewFactory
总结:
- 对于扩展可能性不大的简单业务,简单工厂模式就够了,避免过度设计
- 对于
单维度(单产品结构)
的复杂业务,使用工厂方法模式 - 对于
多维度(多产品结构)
的复杂业务,使用抽象工厂模式;尽量避免维度(产品结构)
增加