iOS设计模式之抽象工厂

2,551 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

  • 本文主要介绍iOS设计模式中的抽象工厂方法。它和工厂方法有些类似,通常来说两个模式都用于相同的目的创建对象而不让客户端知晓返回了什么确切具体的对象。不同点话在于工厂方法是创建一种产品,通过子类创建并重载工厂方法以创建一种产品。而抽象工厂是通过组合对象的方法创建多系列产品,必须修改父类的接口才能支持新的产品。

1. 什么是抽象工厂

结合实际情况,比如我们在餐厅吃披萨,通常我们会有很多类型的披萨,比如有榴莲披萨,烤鸭披萨,火腿披萨,水果披萨等。我们可以结合我们不同的口味选择不同的披萨,我们消费者不在乎披萨是怎么做出来的,只要披萨好吃就行。
在软件设计中,如果客户端想手工创建一个类的对象,那么客户端需要首先知道这个类的细节。但是,一组相关的对象可以在运行时按不同的标准创造的不一样,此时客户端就要知道全部细节才能创建他们,可以通过抽象工厂解决这个问题。

抽象工厂:是一种创建型设计模式,提供一个创建一系列相关或相互依赖对象的接口, 而无需指定其具体类。

2. 什么时候使用抽象工厂方法

软件设计的黄金法则:变动需要抽象
如果有多个类共有相同的行为,但实际实现不同,则可能需要某种抽象类型作为其父类被继承。抽象类型定义所有相关具体类将有的共同行为。比如,我们知道普通的披萨时什么样子,在点餐的时候知道会端上来什么。我们说”出去吃披萨吧“这里,”披萨“就是一个抽象的类型,定义了披萨饼应该具有的共同特征,比如都是可以吃的,下面是圆形的饼上面是浇头。但是,我们从不同的店可以得到同一种披萨(芝士火腿披萨)略有不同的风味。有不同类型的披萨,我们简单的将其叫做“披萨”。来称呼这种特定类型的食物。
因此我们定义生产一种披萨的步骤,只是对于不同的店生产的步骤略微不同,同时根据不同的类型,生产不同口味的披萨。这一过程我们可以称为抽象工厂方法。这一过程通常会结合工厂方法,无需设计在运行时决定使用什么工厂的复杂机制。 说明:当现有的抽象工厂需要支持新产品时,需要向父类添加相对应的新工厂方法,这意味着也要修改其子类以支持新产品的新工厂方法

3. Cocoa Touch框架中使用抽象工厂

之前我们在工厂方法中说了NSNumber类关于工厂方法的使用说明,实际上对于它的初始化不同类型的方法中 image.png

除了boolNumber的实际类型时NSCFBoolean以外,绝大部分都是NSCFNumber类型,虽然返回的都是NSNumber具体子类的实例,但是都是支持NSNumber公有接口

    NSNumber *boolNumber = [NSNumber numberWithBool:YES];

    NSNumber *charNumber = [NSNumber numberWithChar:'a'];

    

    NSLog(@"%d",[boolNumber intValue]);

    NSLog(@"%@",[charNumber boolValue] ? @"true" : @"fales");

打印结果为:

1
true

boolNumber内部保持的布尔值为YES,但是实现了公有的intValue方法,返回反映呢不布尔值的适当int值,charNumber内部也是,实现了公有的boolValue方法,返回反应其内部字符值“a”的布尔值。
接受不同类型参数返回NSNumber类工厂方法生产各种”数工厂“NSNumber中类工厂方法定义了决定实例化何种私有具体子类的默认行为,这种特点称为类簇。类簇是抽象工厂的一种形式NSNumber本身是一个高度抽象的工厂,而NSCFBoolean和NSCFNumber是具体工厂子类。子类是具体工厂

4.代码展示

import XCTest

/// The Abstract Factory protocol declares a set of methods that return
/// different abstract products. These products are called a family and are
/// related by a high-level theme or concept. Products of one family are usually
/// able to collaborate among themselves. A family of products may have several
/// variants, but the products of one variant are incompatible with products of
/// another.
protocol AbstractFactory {

    func createProductA() -> AbstractProductA
    func createProductB() -> AbstractProductB
}

/// Concrete Factories produce a family of products that belong to a single
/// variant. The factory guarantees that resulting products are compatible. Note
/// that signatures of the Concrete Factory's methods return an abstract
/// product, while inside the method a concrete product is instantiated.
class ConcreteFactory1: AbstractFactory {

    func createProductA() -> AbstractProductA {
        return ConcreteProductA1()
    }

    func createProductB() -> AbstractProductB {
        return ConcreteProductB1()
    }
}

/// Each Concrete Factory has a corresponding product variant.
class ConcreteFactory2: AbstractFactory {

    func createProductA() -> AbstractProductA {
        return ConcreteProductA2()
    }

    func createProductB() -> AbstractProductB {
        return ConcreteProductB2()
    }
}

/// Each distinct product of a product family should have a base protocol. All
/// variants of the product must implement this protocol.
protocol AbstractProductA {

    func usefulFunctionA() -> String
}

/// Concrete Products are created by corresponding Concrete Factories.
class ConcreteProductA1: AbstractProductA {

    func usefulFunctionA() -> String {
        return "The result of the product A1."
    }
}

class ConcreteProductA2: AbstractProductA {

    func usefulFunctionA() -> String {
        return "The result of the product A2."
    }
}

/// The base protocol of another product. All products can interact with each
/// other, but proper interaction is possible only between products of the same
/// concrete variant.
protocol AbstractProductB {

    /// Product B is able to do its own thing...
    func usefulFunctionB() -> String

    /// ...but it also can collaborate with the ProductA.
    ///
    /// The Abstract Factory makes sure that all products it creates are of the
    /// same variant and thus, compatible.
    func anotherUsefulFunctionB(collaborator: AbstractProductA) -> String
}

/// Concrete Products are created by corresponding Concrete Factories.
class ConcreteProductB1: AbstractProductB {

    func usefulFunctionB() -> String {
        return "The result of the product B1."
    }

    /// This variant, Product B1, is only able to work correctly with the
    /// variant, Product A1. Nevertheless, it accepts any instance of
    /// AbstractProductA as an argument.
    func anotherUsefulFunctionB(collaborator: AbstractProductA) -> String {
        let result = collaborator.usefulFunctionA()
        return "The result of the B1 collaborating with the ((result))"
    }
}

class ConcreteProductB2: AbstractProductB {

    func usefulFunctionB() -> String {
        return "The result of the product B2."
    }

    /// This variant, Product B2, is only able to work correctly with the
    /// variant, Product A2. Nevertheless, it accepts any instance of
    /// AbstractProductA as an argument.
    func anotherUsefulFunctionB(collaborator: AbstractProductA) -> String {
        let result = collaborator.usefulFunctionA()
        return "The result of the B2 collaborating with the ((result))"
    }
}

/// The client code works with factories and products only through abstract
/// types: AbstractFactory and AbstractProduct. This lets you pass any factory
/// or product subclass to the client code without breaking it.
class Client {
    // ...
    static func someClientCode(factory: AbstractFactory) {
        let productA = factory.createProductA()
        let productB = factory.createProductB()

        print(productB.usefulFunctionB())
        print(productB.anotherUsefulFunctionB(collaborator: productA))
    }
    // ...
}

/// Let's see how it all works together.
class AbstractFactoryConceptual: XCTestCase {

    func testAbstractFactoryConceptual() {

        /// The client code can work with any concrete factory class.

        print("Client: Testing client code with the first factory type:")
        Client.someClientCode(factory: ConcreteFactory1())

        print("Client: Testing the same client code with the second factory type:")
        Client.someClientCode(factory: ConcreteFactory2())
    }
}

执行结果

Client: Testing client code with the first factory type:
The result of the product B1.
The result of the B1 collaborating with the (The result of the product A1.)
Client: Testing the same client code with the second factory type:
The result of the product B2.
The result of the B2 collaborating with the (The result of the product A2.)

5. 总结

抽象工厂和工厂区别?
一个被其多个具体工厂类型共有的抽象工厂类型。抛开抽象,工厂通常指的是具体工厂,而且,它也没有工厂方法的意思。有时,一开始使用具体工厂,后面重构为使用多个具体工厂的抽象工厂。
抽象工厂我们通常会使用协议去定义实现具体的产品,具体的类也就是具体工厂去实现这个产品。它可以涉及很多类型的对象创建,抽象工厂可以隐藏具体的创建过程,不为客户端所见。提供这种抽象,而不暴露创建过程中任何不必要的细节,或确切的类型