设计模式之 - 工厂模式

2020-06-15  本文已影响0人  mtko

工厂模式属于创建型模式

工厂模式

简单工厂模式
工厂方法模式
抽象工厂模式

三种模式的命名迷惑性很强,但功能差别巨大,并且和实际的业务需求有关联性,如果不仔细研磨,不结合实际的业务场景,基本很难理解和合适地应用于实际业务

作者不会举例制造车辆或其他实物的案例,因为这些例子看似形象,实则不贴近实际代码业务,常常看完之后还要脑补实际的代码业务来做类比,本文将以开发 电商App首页 的客户端开发者视角,使用需求推动三种工厂设计模式转变的思路,解释工厂模式

物料

Swift5.2.4

简单工厂模式

  1. 模式结构
  1. 案例

假设你正在编写一个电商App首页,首页有两种类型:带导航栏的首页带底部工具栏的首页。这两种首页随后端下发字段而变动呈现

两种首页相同和不同点如下:
相同点:都有一个ListView,是数据的主要承载UI
不同点:一个有导航栏没底部工具栏,另一个有底部工具栏没导航栏;都有一个按钮,但按钮的布局不同

  1. 对应的代码实现:

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)")
    }
}
  1. 弊端

违背开闭原则

如果需求变化,需要新增侧边栏样式的首页该怎么办? 答案是只能修改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)

那么有没有方式解决这个问题?答案是:有,即工厂方法模式

工厂方法模式

为了解决简单工厂模式的弊端,我们接着讲工厂方法模式

  1. 模式结构
  1. 案例

假如声明一个抽象工厂类,只提供接口或者只给出默认的实现用来创建普通的首页;再分别声明两个具体工厂类来创建导航栏风格首页工具栏风格首页。那么当新增侧边栏风格首页时,再新声明一个具体工厂类来创建侧边栏风格首页,修改代码时是不是就不会动到已声明的抽象工厂类和两个具体工厂类相对应的代码呢?答案是:是的。

  1. 对应的代码实现

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
}
  1. 弊端

被动忽略多维度场景,造成模糊职责、代码耦合、产品层级混乱

当界面复杂,我们一定需要为界面注入一个Model或者ViewModel(我们假设引入的是ViewModel),那ViewModel的构造逻辑应该写在哪里?按照经验主义的编程方式 或者 在工厂方法模式下,可能创建逻辑是会在界面中进行。

从设计模式的角度来看。面对构造过程复杂(过程简单还用得着设计模式?)的ViewModel,这是一种耦合的、混乱产品层级的设计,并且不符合控制反转的设计原则;View和ViewModel应只互为绑定者,但ViewModel的构造逻辑不应在View中进行。

抽象工厂模式

为了解决经验主义的编程方式 或者 工厂方法模式弊端,我们接着讲抽象工厂模式

  1. 模式结构
  1. 案例

案例在上述的工厂方法模式弊端一小节中已经讲述了一部分

简单说下MVVM。1.在应对庞大的业务时,MVC模块架构容易造成胖VC、UI和数据层业务耦合,我们在开发时为了便于扩展维护单元测试,常会采用MVVM模块架构。2.电商App首页一般是业务逻辑的集中区,导航栏风格首页底部工具栏首页侧边栏首页,必定有许多相同的、不同的功能。面对复杂且需单测的业务,我们使用MVVM来设计模块结构。

MVVM抽象工厂模式又有什么关联呢?有,关联大了去了。

抽象工厂模式MVVM结合。
ViewModel和其子类构成一种产品结构
View和其子类构成一种产品结构
某种ViewModel和某种View的组合即为:产品族

这应该是抽象工厂模式最有价值的应用点之一了

  1. 对应的代码实现

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
}
  1. 弊端

模式复杂,学习成本高
增加一种产品结构时,会侵入已有代码

比如各个风格的首页都将绑定与风格对应的弹框视图
弹框视图基类 = PopUpView
各风格首页对应的弹框视图子类 = XXXPopUpView

此时要在各个工厂类里加入构造弹框视图的方法和逻辑,
甚至各个工厂类类名也要修改

举例:

1.加入逻辑

 /// 生产具体产品 - PopUpView
 override class func productPopUpView() -> PopUpView {
     return NavigationBarHomePagePopUpView()
 }

2.修改类名

NavigationBarHomePageAndViewModelFactory -> NavigationBarHomePageAndViewModelAndPopUpViewFactory

总结:

  1. 对于扩展可能性不大的简单业务,简单工厂模式就够了,避免过度设计
  2. 对于单维度(单产品结构)的复杂业务,使用工厂方法模式
  3. 对于多维度(多产品结构)的复杂业务,使用抽象工厂模式;尽量避免维度(产品结构)增加
上一篇下一篇

猜你喜欢

热点阅读