设计模式 - 深入浅出工厂模式

factory.jpg

工厂模式概述

工厂模式是设计模式的一种,从功能上来说,它的主要作用是创建对象。细分一下,有三种不同类型的工厂模式,即,

  • 简单工厂模式
  • 工厂模式
  • 抽象工厂模式

本文使用swift编码,试图通过简单的代码和配图简要说明工厂模式的使用。既然是关于工厂模式,那么直接映射到现实世界,就用汽车工厂生产汽车这样一个模型来描述工厂模式的使用和逐步改进的过程。

创建对象存在的问题

这里假设,工厂可以生产汽车(Car),这个数据模型可以用swift像下面这样定义,

protocol VehicleFeature {
    func speedUp() -> Void
    func numberOfWheelNumber() -> Int
}

class Vehicle: VehicleFeature {
    private var name: String? = "Vehicle"
    private var wheelNumber: Int?
        func speedUp() {
        print("car speed up")
    }
    
    func numberOfWheelNumber() -> Int {
        if let wheelNumber = self.wheelNumber {
            return wheelNumber
        }
        return 0
    }
}

class Car: Vehicle {
    override func speedUp() {
        print("car speed up")
    }
    
    override func numberOfWheelNumber() -> Int {
        if let wheelNumber = self.wheelNumber {
            return wheelNumber
        }
        return 0
    }
}

上面代码定义了VehicleFeature协议,该协议中定义了-speedUp和-numberOfWheelNumber两个方法,父类Vehicle定义了name和wheelNumber两个可选值属性,并且Vehicle实现了VehicleFeature协议,子类Car继承父类Vehicle。

这时客户想要一辆汽车Car,直接调用Car的初始化方法let car = Car(),即可创建一个Car对象。

想一想,随着工厂业务的扩张,这时候还需要生产摩托车(MotorBicycle)和大巴车(Bus),我们得新增加MotorBicycle和Bus两个类,如下代码所示,

class MotorBicycle: Vehicle {
    
    override func speedUp() {
        print("motorBicycle speed up")
    }
    
    override func numberOfWheelNumber() -> Int {
        if let wheelNumber = self.wheelNumber {
            return wheelNumber
        }
        return 0
    }
}


class Bus: Vehicle {
    
    override func speedUp() {
        print("bus speed up")
    }
    
    override func numberOfWheelNumber() -> Int {
        if let wheelNumber = self.wheelNumber {
            return wheelNumber
        }
        return 0
    }
}

通过let motorBicycle = MotorBicycle(); let bus = Bus()即可为客户创建MotorBicycle和Bus对象。

在开发过程中,通过上述方式创建对象是一种很常见的方法,不过我们应该意识到,随着业务的调整和扩展,类似let car = Car()这样的对象初始化方法会散落在项目的各个角落,代码维护的成本越来越高,比如现在给父类Vehicle添加一个-required init方法,用以在初始化时候指定车辆(Vehicle)车轮数量,代码如下,

class Vehicle: VehicleFeature {
    private var name: String? = "Vehicle"
    private var wheelNumber: Int?
    
    required init(wheelNumber: Int) {
        self.wheelNumber = wheelNumber
    }
    
    func speedUp() {
        print("car speed up")
    }
    
    func numberOfWheelNumber() -> Int {
        if let wheelNumber = self.wheelNumber {
            return wheelNumber
        }
        return 0
    }
}

在swift中,如果父类定义了-required init方法,那么子类定义-init方法时必须要定义同样的-required init方法,所以上述的Car、MotorBicycle和Bus三个子类,也要添加跟父类Vehicle同样的-required init方法,代码如下,

class Car: Vehicle {
    
    required init(wheelNumber: Int) {
        super.init(wheelNumber: wheelNumber)
    }
    
    override func speedUp() {
        print("car speed up")
    }
    
    override func numberOfWheelNumber() -> Int {
        if let wheelNumber = self.wheelNumber {
            return wheelNumber
        }
        return 0
    }
}

class MotorBicycle: Vehicle {
    
    required init(wheelNumber: Int) {
        super.init(wheelNumber: wheelNumber)
    }
    
    override func speedUp() {
        print("motorBicycle speed up")
    }
    
    override func numberOfWheelNumber() -> Int {
        if let wheelNumber = self.wheelNumber {
            return wheelNumber
        }
        return 0
    }
}

class Bus: Vehicle {
    
    required init(wheelNumber: Int) {
        super.init(wheelNumber: wheelNumber)
    }
    
    override func speedUp() {
        print("bus speed up")
    }
    
    override func numberOfWheelNumber() -> Int {
        if let wheelNumber = self.wheelNumber {
            return wheelNumber
        }
        return 0
    }
}

经过上面的更改,我们需要通过带参数的初始话方法let car = Car(wheelNumber: 4)let motorBicycle = MotorBicycle(wheelNumber: 2)来创建对象,项目中大量的创建对象的代码得重新编码和调整。

简单工厂模式

上面的demo,简单说明了创建对象时候可能存在的问题,遇到这样的问题,我们该怎么优化呢?

这时候简单工厂模式(Simple Factory Pattern)出现了,它可以短期内缓解我们的压力。现在客户需要一辆车Car,我们不再通过let car = ...来创建对象,而是将创建对象的任务交给简单工厂,这时需要在该模型中增加一个新的角色简单工厂(SimpleFactory),它的主要功能是根据客户输入的机车类型,为用户创建对应的机车,下面的代码展示了简单工厂模式的使用,

enum FactoryVehicleType {
    case CarType
    case MotorBicycle
    case BusType
}

class SimpleFactory {
    func createVehicle(vehicleType: FactoryVehicleType) -> Vehicle {
        switch vehicleType {
        case .CarType:
            return Car(wheelNumber: 4)
        case .MotorBicycle:
            return MotorBicycle(wheelNumber: 2)
        case .BusType:
            return Bus(wheelNumber: 4)
        }
    }
}

上述代码,定义了枚举类型FactoryVehicleType,它描述了简单工厂可以创建的几种车型,新增加的SimpleFactory(简单工厂类),它包含-createVehicle方法,该方法根据输入的FactoryVehicle枚举值类型,创建对应的车型。

上述代码存在了这样几个角色:抽象产品(Abstract Product)、具体产品(Concrete Product)、简单工厂(SimpleFactory)以及客户类(Consumer),其中SimpleFactory是简单工厂模式的核心类,它们之间的关系如下UML图所示,

SimpleFactory.jpg

通过使用简单工厂方法,为我们创建对象提供了方便,也节省了开发者的精力。假设现在改动了父类Vehicle的required init方法,各个子类也要做相应的改动,此时我们只需在SimpleFactory的-createVehicle方法中改动创建子类的初始化方法即可,而项目各个角落中通过SimpleFactory创建子类对象的零散的代码都不需要做任何的改动,这无疑是一个大大的改进。

但是简单工厂模式也存在很大的不足,那就是随着工厂规模的增加和客户的需求,现在需要生产更多类型的车辆,比如现在要增加生产坦克(Tank)和SUV,这时候我们得重新调整SimpleFactory,代码如下,

enum FactoryVehicleType {
    case CarType
    case MotorBicycle
    case BusType
    case TankType
    case SUVType
}

class SimpleFactory {
    func createVehicle(vehicleType: FactoryVehicleType) -> Vehicle {
        switch vehicleType {
        case .CarType:
            return Car(wheelNumber: 4)
        case .MotorBicycle:
            return MotorBicycle(wheelNumber: 2)
        case .BusType:
            return Bus(wheelNumber: 4)
        case .TankType:
              return Tank(wheelNumber: 16)
        case .SUVType:
              return SUV(wheelNumber: 4)
        }
    }
}

可以想象,随着工厂的发展,SimpleFactory的代码会越来越臃肿,每增加一条新型车辆的生产线,就得新增加FactoryVehicleType枚举类型的种类,同时更改SimpleFactory的-createVehicle方法。所以说,SimpleFactory只能短期内缓解工厂的压力,这种方案并不是一个长久之计。

这么说来,SimpleFactory并不是像它的名字一样看起来简单和简洁,这里的Simple也许仅仅代表了简单工厂模式适合处理相对简单的业务场景。粗略地看一下臃肿不堪的SimpleFactory代码,我们可以说SimpleFactory之所以臃肿不堪,是因为它所承担的职能太多了,它要负责创建Car、Bus和MotorBicycle,还要负责创建Tank和SUV,在将来它可能还要承担更多更繁杂的职能,这显然违背了软件开发中的“单一职责”和“开闭原则”。

开闭原则:Open-Closed Principle,OCP) "Software entities should be open for extension,but closed for modification"。翻译一下:“软件实体应当对扩展开放,对修改关闭”。通俗点来说就是:软件系统中包含的各种组件,例如模块(Modules)、类(Classes)以及功能(Functions)等等,应该在不修改现有代码的基础上,引入新功能。开闭原则中“开”,是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的;开闭原则中“闭”,是指对于原有代码的修改是封闭的,即不应该修改原有的代码。

怎样优化SimpleFactroy,其中的关键点和问题所在其实就是SimpleFactory职责太多、代码臃肿,这时我们需要对SimpleFactory的职能进行拆分,将它创建车辆的各条业务线拆分给不同的工厂,这催生了FactoryPattern(工厂模式)。

工厂模式

FactoryPattern的出现就是为了简化SimpleFactory,它也符合软件开发和设计中的单一职责原则。就像现实世界中一样,现在已经不可能存在这样一个巨型的工厂,它需要负责生产各种类型的汽车,我们需要的是具有单一职能并且高度专业化的工厂,这种单一职责的工厂也是从SimpleFactory拆分而来,那么对应于上面的代码demo,我们拆分SimpleFactory,对于已经定义的Car、Bus、MotorBicycle等数据模型则不需要改变,我们只需要增加对应的三个工厂类和一个抽象工厂协议(AbstractFactory),如下代码所示,

protocol AbstractFactory {
    func createVehicle() -> Vehicle
}

class CarFactory: AbstractFactory {
    func createVehicle() -> Vehicle {
        return Car(wheelNumber: 4)
    }
}

class BusFactory: AbstractFactory {
    func createVehicle() -> Vehicle {
        return Bus(wheelNumber: 4)
    }
}

class MotorBicycleFactory: AbstractFactory {
    func createVehicle() -> Vehicle {
        return MotorBicycle(wheelNumber: 2)
    }
}

AbstractFactory是一个协议,它定义了一个创建Vehicle的-createVehicle方法,实现了该协议的三个具体工厂类(Concrete Factory)需要实现该协议中的-createVehicle方法,分别创建对应的车辆。

现在分别创建一个Car、Bus和MotorBicycle对象,我们可以用如下代码实现,

let car = CarFactory().createVehicle()
let bus = BusFactory().createVehicle()
let motorBicycle = MotorBicycleFactory().createVehicle()

这样,每一种类型的车辆都有其对应的工厂类,当我们需要创建一种类型的车辆时,只需调用其对应的工厂类的-createVehicle方法。

备注:工厂类,例如CarFactory是负责创建Car对象的,上面的代码存在一点点的瑕疵,那就是创建多个Car对象时,也通过CarFactory()多次创建CarFactory对象,但是它并不涉及到数据存储和业务逻辑,这样无疑消耗了额外的内存空间,为了性能考虑,可以将CarFactory、BusFactory和MotorBicycleFactory定义为单例模式。

可以看出,在工厂模式中,有这样几个角色:抽象产品(Abstract Product)、具体产品(Concrete Product)、抽象工厂(Abstract Factory)、具体工厂(Concrete Factory)以及客户(Consumer),AbstractFactory是工厂模式的核心,它们之间的关系如下图UML所示,

FactoryPattern.jpg

与简单工厂模式相比,工厂模式再也不必烦恼于臃肿的代码,如果需要制造新的机车类型,比如现在需要创建坦克和SUV,为了满足这样的需求,我们首先创建两个继承于Vehicle的两个子类Tank和SUV,接着分别创建Tank和SUV对应的工厂类TankFactory和SUVFactory,使用了这样的方法,不会改动原有的代码结构,这样也遵循了“开闭原则”。

工厂模式可以满足大部分需求,但是,但是,重要的但是说三遍,但是,当我们将产品细分为不同的等级结构,例如Car这样一个产品可以细分为发动机(Engine)、车门(Door)和电池(Battery)等几个重要的等级结构;而Car本身也有很多类型,例如奔驰和奥迪,这种情况下,Car有两种类型,每一种Car都分别有其对应使用的Engine, Door和Battery,如下表所列,

产品族 / 产品等级 奔驰 奥迪
发动机Engine BenzEngine AudiEngine
车门Door BenzDoor AudiDoor
电池Battery BenzBattery AudiBattery

这样细分下来,为了使一辆车可以有效使用,必须保证所用的配件能够匹配,想象一下生产的AudiEngine给Benz用,BenzDoor给Audi用,真是画面太美不敢看。这时候给我们生产产品也带来了问题,因为现在有两种车类型 - Benz和Audi,每种车类型有三个重要配件 - Engine、Door和Battery,如果使用工厂模式生产配件,我们得创建6个对应的工厂,即BenzEngineFactory, BenzDoorFactory, BenzBatteryFactory和AudiEngineFactory, AudiDoorFactory, AudiBatteryFactory,这无疑是一个让人无语的方案。所以说这种情况下,工厂模式已经满足不了当前的需求,我们需要进行更高一层次的抽象,这时候“抽象工厂模式”应运而生。

这里,笔者在讨论生产时候偷换了概念,文章前段讨论简单工厂模式(SimpleFactory)和工厂模式(FactoryPattern)时,工厂生产的产品是Vehicle,现在为了引申出抽象工厂模式(AbstractFactory)将Vehicle的子类Car进行了细分,工厂生产产品关注点不在是Vehicle,而是着重讨论生产Car相关配件 - Engine, Door和Battery。

抽象工厂模式

在进一步探讨抽象工厂模式之前,我们有必要了解“产品族”和“产品等级”这两个概念,所谓产品族,是指位于不同产品等级结构中,功能相关联的产品组成的家族。比如奔驰的Engine, Door, Battery组成一个产品族,Audi的Engine, Door, Battery组成一个产品族。而这两个产品族都来自于三个产品等级:Engine, Door和Battery。

生产奔驰的Engine, Door和Battery必须匹配才能组合成有效使用的BenzCar;同样生产Audi的Engine, Door和Battery也必须匹配才能组合成可用的AudiCar。也许让同一个工厂生产这些配件才能保证合理匹配,例如让BenzFactory生产奔驰的Engine, Door和Battery,让AudiFactory生产Audi的Engine, Door和Battery,采用这样的做法,我们也避免了工厂模式中为每一种类型的配件创建对应的工厂。既然BenzFactory和AudiFactory都是为了创建一个产品族,这样我们可以将BenzFactory和AudiFactory抽象出一个AbstractFactory,该抽象工厂有三个方法,-createEngine, -createDoor, -createBattery,

如下代码所示,使用了抽象工厂模式实现了生产Car的配件,

class Component { // 配件 - 翻译不好见谅哈
    var price: Float = 0.0
    
}

class Engine: Component {
    var speed: Int = 0
    var weight: Float = 0.0
}

class BenzEngine: Engine {
    
}

class AudiEngine: Engine {
    
}

class Door: Component {
    var color: UIColor = UIColor.clearColor()
    var hasWindow: Bool = true
}

class BenzDoor: Door {

}

class AudiDoor: Door {

}


class Battery: Component {
    var workTime: Int = 0
    var lekeage: Bool = false //是否漏电
}

class BenzBattery: Battery {
    
}

class AudiBattery: Battery {
    
}

protocol AbstractCarFactory {
    func createEngine() -> Engine
    func createDoor() -> Door
    func createBattery() -> Battery
}

class BenzFactory: AbstractCarFactory {
    func createEngine() -> Engine {
        return BenzEngine()
    }
    
    func createDoor() -> Door {
        return BenzDoor()
    }
    
    func createBattery() -> Battery {
        return BenzBattery()
    }
}

class AudiFactory: AbstractCarFactory {
    func createEngine() -> Engine {
        return AudiEngine()
    }
    
    func createDoor() -> Door {
        return AudiDoor()
    }
    
    func createBattery() -> Battery {
        return AudiBattery()
    }
}

简单描述上面的代码,

  1. 定义了一个基类Component,它代表一个汽车配件的模型,它有一个Float类型的price属性;
  2. 定义三个子类Engine, Door, Battery,它们都继承自Component,并且各自定自己的属性,例如Battery子类定义了workTime(电池工作时长)和lekeage(是否漏电),它们属于抽象产品(abstract product)的范畴;
  3. 定义了Benz和Audi两种车型Engine, Door, Battery的子类,分别是BenzEngine, BenzDoor, BenzBattery和AudiEngine, AudiDoor, AudiBattery,它属于具体产品(concrete product)的范畴;
  4. 定义核心角色AbstractCarFactory,它是一个Protocol,它内部定义了-crateEngine, -createDoor, -createBattery三个方法,分别用于创建Engine, Door, Battery抽象产品,抽象工厂并不创建具体的产品;
  5. 定义BenzFactory和AudiFactory两个具体工厂,它们实现AbstractCarFactory协议,负责创建具体的产品对象,也就是说抽象工厂定义接口,具体工厂实现接口逻辑。

分析总结,抽象工厂模式与工厂模式一样,都包含如下4个角色,即,

  • 抽象工厂
  • 具体工厂
  • 抽象产品
  • 具体产品

区别在于,工厂模式只生产一种单一的产品,而抽象工厂模式生产多种类型的产品,准确来说,抽象工厂模式可以生产一个产品族。

它们之间的关系如下图UML所示,

AbstractFactory.jpg

此时我们可以这样创建一个Benz汽车对象,如下代码所示,

class Car {
    var engine: Engine
    var door: Door
    var battery: Battery
    
    init(engine: Engine, door: Door, battery: Battery) {
        self.engine = engine
        self.door = door
        self.battery = battery
    }
}


func makeCar(carFactory: AbstractCarFactory) -> Car {
    let engine = carFactory.createEngine()
    let door = carFactory.createDoor()
    let battery = carFactory.createBattery()
    let car = Car(engine: engine, door: door, battery: battery)
    return car
}

let benzCar = makeCar(BenzFactory())

简单解释上述代码,首先新定义了一个类Car,它包含Engine, Door, Battery三个属性,并定义了-init初始化方法;接着定义了-makeCar方法,它接受的参数是AbstractCarFactory,返回一个Car对象;我们需要创建benzCar,直接为-makeCar方法参数设置为BenzFactory对象即可。

笔者冒泡:本文在两台不同电脑写作,因为系统原因,Mac Book Pro不能使用ArgoUML,所以只能换个方式画UML类图,所以导致抽象工厂模式的UML图和之前的画风不一样。因为时间有限,所以也重新画图,导致风格不统一,忘读者见谅。

Objective-C SDK中的抽象工厂模式

在Objecitve-C SDK中,NSNumber是我们经常使用的类,我们可以使用NSNumber创建不同类型的数据,如下代码,

NSNumber *integerValue = [NSNumber numberWithInteger:1000];
NSNumber *boolValue = [NSNumber numberWithBool: YES];
...

实际上NSnumber是一个类族,大概就是文章前面提到的产品族这样的概念吧,它包含Char, NSInteger, Bool, Short...等,如果没有NSNumber提供的抽象工厂模式来创建对象,那么对于我们编码来说简直就是噩梦。

NSNumber本身就是一个抽象工厂,它提供了不同的接口用于创建不同类型数据对应的对象,至于创建对象的细节,则交付给实现NSNumber抽象接口的具体工厂。

微信公众号

欢迎关注本人微信公众号,请扫描下方二维码,

foolishlion.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容