Chapter 17: Facade Pattern

大綱

When should you use it?

  • The facade pattern is a structural pattern that provides a simple interface to a complex system.

    • The facade: provides simple methods to interact with the system. This allows consumers to use the facade instead of knowing about and interacting with multiple classes in the system.

    • The dependencies: are objects owned by the facade. Each dependency performs a small part of a complex task.

  • When should you use it?

    • Use this pattern whenever you have a system made up of multiple components and want to provide a simple way for users to perform complex tasks.

Playground example

  • 目標: a product ordering system involves several components: customers and products, inventory in stock, shipping orders and others.

    • provide a facade to expose common tasks such as placing and fulfilling a new order

// MARK: - Example
// 1. set up two products
let rayDoodle = Product(
  identifier: "product-001",
  name: "Ray's doodle",
  cost: 0.25)

let vickiPoodle = Product(
  identifier: "product-002",
  name: "Vicki's prized poodle",
  cost: 1000)

// 2. create inventoryDatabase using the products
let inventoryDatabase = InventoryDatabase(
  inventory: [rayDoodle: 50, vickiPoodle : 1]
)

// 3. create the orderFacade using the inventoryDatabase and a new ShippingDatabase.
let orderFacade = OrderFacade(
  inventoryDatabase: inventoryDatabase,
  shippingDatabase: ShippingDatabase())

// 4. create a customer and call orderFacade.placeOrder(for:by:).
let customer = Customer(
  identifier: "customer-001",
  address: "1600 Pennsylvania Ave, Washington, DC 20006",
  name: "Johnny Appleseed")

// provide a simple way for users to perform complex tasks
orderFacade.placeOrder(for: vickiPoodle, by: customer)
// Place order for 'Vicki's prized poodle' by 'Johnny Appleseed'
// Order placed for 'Vicki's prized poodle' by 'Johnny Appleseed
  • Dependencies

// MARK: - Dependencies

// 1. define two simple models
public struct Customer {
  public let identifier: String
  public var address: String
  public var name: String
}

// 2. make both of these types conform to Hashable to enable you to use them as keys within a dictionary
extension Customer: Hashable {

  public var hashValue: Int {
    return identifier.hashValue
  }

  public static func ==(lhs: Customer,
                        rhs: Customer) -> Bool {
    return lhs.identifier == rhs.identifier
  }
}

public struct Product {
  public let identifier: String
  public var name: String
  public var cost: Double
}

extension Product: Hashable {

  public var hashValue: Int {
    return identifier.hashValue
  }

  public static func ==(lhs: Product,
                        rhs: Product) -> Bool {
    return lhs.identifier == rhs.identifier
  }
}

// This is a simplified version of a database that stores available inventory, which represents the number of items available for a given Product.
public class InventoryDatabase {
  public var inventory: [Product: Int] = [:]

  public init(inventory: [Product: Int]) {
    self.inventory = inventory
  }
}

// This is likewise a simplified version of a database that holds onto pendingShipments, which represents products that have been ordered but not yet shipped for a given Customer.
public class ShippingDatabase {
  public var pendingShipments: [Customer: [Product]] = [:]
}
  • Facade

// MARK: - Facade

public class OrderFacade {
  public let inventoryDatabase: InventoryDatabase
  public let shippingDatabase: ShippingDatabase

  public init(inventoryDatabase: InventoryDatabase,
              shippingDatabase: ShippingDatabase) {
    self.inventoryDatabase = inventoryDatabase
    self.shippingDatabase = shippingDatabase
  }

  // 將placeOrder中所需要的dependency進行邏輯封裝
  public func placeOrder(for product: Product,
                         by customer: Customer) {
    print("Place order for '\(product.name)' by '\(customer.name)'")

    // 檢查庫存
    let count = inventoryDatabase.inventory[product, default: 0]
    guard count > 0 else {
      print("'\(product.name)' is out of stock!")
      return
    }

    // 調整庫存內容
    inventoryDatabase.inventory[product] = count - 1

    // 調整shipment內容
    var shipments =
      shippingDatabase.pendingShipments[customer, default: []]
    shipments.append(product)
    shippingDatabase.pendingShipments[customer] = shipments

    print("Order placed for '\(product.name)' " +
      "by '\(customer.name)'")
  }
}

What should you be careful about?

  • Be careful about creating a “god” facade that knows about every class in your app.

  • It’s okay to create more than one facade for different use cases.

Tutorial project

Key points

  • The facade pattern provides a simple interface to a complex system. It involves two types: the facade and its dependencies.

  • The facade provides simple methods to interact with the system. Behind the scenes, it owns and interacts with its dependencies, each of which performs a small part of a complex task.

Last updated

Was this helpful?