17.
Facade Pattern
Written by Joshua Greene
The facade pattern is a structural pattern that provides a simple interface to a complex system. It involves two types:
-
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.
For example, a product ordering system involves several components: customers and products, inventory in stock, shipping orders and others.
Instead of requiring the consumer to understand each of these components and how they interact, you can provide a facade to expose common tasks such as placing and fulfilling a new order.
Playground example
Open IntermediateDesignPatterns.xcworkspace in the Starter directory, and then open the Facade page.
You’ll implement part of the ordering system mentioned above. Specifically, you’ll create an OrderFacade that allows a user to place an order.
Enter the following after Code example:
import Foundation
// MARK: - Dependencies
public struct Customer {
public let identifier: String
public var address: String
public var name: String
}
extension Customer: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(identifier)
}
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 func hash(into hasher: inout Hasher) {
hasher.combine(identifier)
}
public static func ==(lhs: Product,
rhs: Product) -> Bool {
return lhs.identifier == rhs.identifier
}
}
Here you define two simple models: a Customer represents a user that can place an order, and a Product sold by the system. You make both of these types conform to Hashable to enable you to use them as keys within a dictionary.
Next, add the following to the end of the playground:
public class InventoryDatabase {
public var inventory: [Product: Int] = [:]
public init(inventory: [Product: Int]) {
self.inventory = inventory
}
}
public class ShippingDatabase {
public var pendingShipments: [Customer: [Product]] = [:]
}
First, you declare InventoryDatabase. This is a simplified version of a database that stores available inventory, which represents the number of items available for a given Product.
You also declare ShippingDatabase. 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. In a complex system, you’d also likely define a CustomerDatabase, BillingDatabase and more. To keep this example simple, however, you’ll omit these elements.
Add the following to the end of the playground:
// 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
}
}
Here, you declare OrderFacade and add two properties, inventoryDatabase and shippingDatabase, which you pass into this via its initializer, init(inventoryDatabase:shippingDatabase:).
Next, add the following method to the end of the OrderFacade class you just added:
public func placeOrder(for product: Product,
by customer: Customer) {
// 1
print("Place order for '\(product.name)' by '\(customer.name)'")
// 2
let count = inventoryDatabase.inventory[product, default: 0]
guard count > 0 else {
print("'\(product.name)' is out of stock!")
return
}
// 3
inventoryDatabase.inventory[product] = count - 1
// 4
var shipments =
shippingDatabase.pendingShipments[customer, default: []]
shipments.append(product)
shippingDatabase.pendingShipments[customer] = shipments
// 5
print("Order placed for '\(product.name)' " +
"by '\(customer.name)'")
}
This is a simple method that consumers of the facade will call to place orders for a given Product and Customer.
Here’s what the code does:
-
You first print the
product.nameandcustomer.nameto the console. -
Before fulfilling the order, you guard that there’s at least one of the given
productin theinventoryDatabase.inventory. If there isn’t any, you print that the product is out of stock. -
Since there’s at least one of the
productavailable, you can fulfill the order. You thereby reduce thecountof theproductininventoryDatabase.inventoryby one. -
You then add the
productto theshippingDatabase.pendingShipmentsfor the givencustomer. -
Finally, you print that the order was successfully placed.
Great, you’re ready to try out the facade! Add the following code at the end of the playground:
// MARK: - Example
// 1
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
let inventoryDatabase = InventoryDatabase(
inventory: [rayDoodle: 50, vickiPoodle : 1]
)
// 3
let orderFacade = OrderFacade(
inventoryDatabase: inventoryDatabase,
shippingDatabase: ShippingDatabase())
// 4
let customer = Customer(
identifier: "customer-001",
address: "1600 Pennsylvania Ave, Washington, DC 20006",
name: "Johnny Appleseed")
orderFacade.placeOrder(for: vickiPoodle, by: customer)
Here’s what this does:
-
First, you set up two products.
rayDoodleare drawings from Ray, andvickiPoodleis a prized pet poodle by Vicki. Don’t even get me started about the poodle doodles! -
Next, you create
inventoryDatabaseusing the products. There are a lot ofrayDoodles(he likes to doodle, apparently) and only onevickiPoodle. It’s her prized poodle, after all! -
Then, you create the
orderFacadeusing theinventoryDatabaseand a newShippingDatabase. -
Finally, you create a
customerand callorderFacade.placeOrder(for:by:). Naturally, of course, your order is for Vicki’s prized poodle. It’s expensive, but it’s worth it!
You should see the following printed to the console:
Place order for 'Vicki's prized poodle' by 'Johnny Appleseed'
Order placed for 'Vicki's prized poodle' by 'Johnny Appleseed'
Doodles and poodles aside, you’ve just created a nice start for an ordering system!
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. For example, if you notice a facade has functionality that some classes use and other functionality that other classes use, consider splitting it into two or more facades.
Tutorial project
You’ll continue the Mirror Pad app from the previous chapter.
If you skipped the previous chapter, or you want a fresh start, open Finder and navigate to where you downloaded the resources for this chapter. Then, open starter\MirrorPad\MirrorPad.xcodeproj in Xcode.
You’ll implement a share button in this chapter and make use of a facade. To keep the focus on the design pattern, and to save you a lot of typing, the facade’s dependencies have been provided for you.
Open Finder and navigate to wherever you downloaded the resources for this chapter. Alongside the Starter and Final directories, you’ll see a Resources directory that contains DrawingSelectionViewController.swift, DrawingSelectionViewController.xib and ImageRenderer.swift.
Position the Finder window above Xcode and drag and drop DrawingSelectionViewController.swift and ImageRenderer.swift into the app’s Facades group.
When prompted, check the option for Copy items if needed and press Finish to add the files.
Finally, drag and drop DrawingSelectionViewController.xib into the Views group. Again, select Copy items if needed and press Finish to add the file.
You’ll use DrawingSelectionViewController to display an OutlineView over the existing ViewController. It has a button to toggle the selected drawing between the inputDrawView and entireDrawView and a “Share” button to share the selection.
You’ll pass the selected view to ImageRenderer to convert it into a UIImage. You’ll then use the resulting image to create a UIActivityViewController.
UIActivityViewController is actually an Apple-provided facade! It provides a simple interface to share strings, images and other media with iCloud, iMessage, Twitter and other apps that are available on the device.
Your job is to create a new facade called ShareFacade. You’ll use this to provide a simple interface to allow consumers to select which view to share, turn the view into an image and share this via whichever app the user chooses.
Thereby, ShareFacade will coordinate between DrawingSelectionViewController, ImageRenderer and UIActivityViewController to accomplish this goal.
You’ve got the mission brief down, so it’s time to code!
Create a new Swift File called ShareFacade.swift within the app’s Facades group and replace its contents with the following:
import UIKit
public class ShareFacade {
// MARK: - Instance Properties
// 1
public unowned var entireDrawing: UIView
public unowned var inputDrawing: UIView
public unowned var parentViewController: UIViewController
// 2
private var imageRenderer = ImageRenderer()
// MARK: - Object Lifecycle
// 3
public init(entireDrawing: UIView,
inputDrawing: UIView,
parentViewController: UIViewController) {
self.entireDrawing = entireDrawing
self.inputDrawing = inputDrawing
self.parentViewController = parentViewController
}
// MARK: - Facade Methods
// 4
public func presentShareController() {
}
}
Let’s take a look at what is going on here:
-
First you declare instance variables for
entireDrawing,inputDrawingandparentViewController. In order to prevent a strong reference cycle, you denote each property asunowned. -
Next, you declare a property for
imageRenderer, which you’ll use later. -
Next, you create an initializer to set each of the
unownedproperties. -
Finally, you stub out a method for
presentShareController, which ultimately consumers will call to present the share controller to select which view to share, convert the view to an image and share it.
Before you can implement presentShareController(), you’ll need to make ShareFacade conform to DrawingSelectionViewControllerDelegate, which is required by DrawingSelectionViewController.
Still in ShareFacade.swift, add the following to the bottom of the file after the closing class curly brace:
// MARK: - DrawingSelectionViewControllerDelegate
extension ShareFacade: DrawingSelectionViewControllerDelegate {
// 1
public func drawingSelectionViewControllerDidCancel(
_ viewController: DrawingSelectionViewController) {
parentViewController.dismiss(animated: true)
}
// 2
public func drawingSelectionViewController(
_ viewController: DrawingSelectionViewController,
didSelectView view: UIView) {
parentViewController.dismiss(animated: false)
let image = imageRenderer.convertViewToImage(view)
let activityViewController = UIActivityViewController(
activityItems: [image],
applicationActivities: nil)
parentViewController.present(activityViewController,
animated: true)
}
}
Here’s what this does:
-
drawingSelectionViewControllerDidCancelis called whenever the user presses the Cancel button to abort sharing. In this case, you tellparentViewControllerto dismiss its currently displayed view controller with an animation. -
drawingSelectionViewController(_:didSelectView:)is called whenever the user presses the Share button to select a view to share. In this case, you first tellparentViewControllerto dismiss its current view controller without an animation.Next, you immediately create an
imagefrom the givenviewby passing this toimageRenderer.In turn, you use this
viewto create aUIActivityViewController, whichparentViewControllerpresents using an animation.Ultimately, this will have the nice effect of immediately hiding the
DrawingSelectionViewControllerand animating in the newUIActivityViewController.
Next, add the following code inside presentShareController():
// 1
let selectionViewController =
DrawingSelectionViewController.createInstance(
entireDrawing: entireDrawing,
inputDrawing: inputDrawing,
delegate: self)
// 2
parentViewController.present(selectionViewController,
animated: true)
This code is fairly straightforward:
-
You first create a new
DrawingSelectionViewControllerinstance calledselectionViewControllerusing a convenience class constructor method,createInstance(entireDrawing:inputDrawing:delegate).If you inspect this method within DrawingSelectionViewController.swift, you’ll see it creates a new view controller instance by calling
DrawingSelectionViewController(nibName: nil, bundle: nil), sets themodalPresentationStyleandmodalTransitionStyle, sets the passed-in variables as properties on the new instance, and returns the view controller. -
Finally, you tell
parentViewControllerto present theselectionViewControllerwith an animation.
Your ShareFacade is all set up and ready to be used!
Open ViewController.swift and add the following right after the opening class curly brace:
// MARK: - Properties
public lazy var shareFacade: ShareFacade =
ShareFacade(entireDrawing: drawViewContainer,
inputDrawing: inputDrawView,
parentViewController: self)
Here, you create a new property called shareFacade. Since you pass self as the parentViewController, you make this a lazy property to ensure that the ViewController itself is fully created first.
Lastly, add the following inside sharePressed(_:):
shareFacade.presentShareController()
With this single line, you’ve added sharing capabilities to ViewController. Aren’t facades great?
Build and run the app. Draw several lines in the top-left view, press the Share button, and you’ll be presented with the DrawingSelectionViewController.
Press the red Share button, and you’ll see the UIActivityViewController, where you can pick an app to use to share the image.
If you’re using the simulator, you’ll only see a few apps available. If you use a real device instead, you’ll see several more depending on the apps you have installed.
Key points
You learned about the facade pattern in this chapter. Here are its 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.
Where to go from here?
Congratulations on making it to the end of the Intermediate section! If you’ve worked through all of the chapters so far, you now know the majority of the design patterns used in iOS.
Mirror Pad has come a long way, and the code is much more maintainable! There’s still a lot of functionality you can add:
- Color and brush stroke selection
- Undo and redo functionality
- Saving and loading drawings in the app
Each of these are possible using the existing patterns you’ve learned in the Intermediate and Fundamental Design Patterns sections. Feel free to continue building out Mirror Pad as much as you like.
But you still have more learning to do. Continue onto the next section to learn about advanced design patterns, including mediator, composite, command and more!