In this chapter, we will discuss two new structural patterns: the adapter and facade patterns. We will focus on the adapter pattern that joins together types that were not designed to work with each other. Then, we will discuss the facade pattern that simplifies the interface of a set of complex systems.
This pattern is often used when dealing with frameworks, libraries, and APIs to easily "adapt" the old and existing interfaces to the new requirements of a program.
The adapter pattern converts the interface of another existing class into an interface waited by the existing clients to allow them to work together. Thanks to this pattern, you can integrate components for which you normally cannot modify the source code and things that often appear with the use of a framework.
Note that you should avoid using this pattern if you already have source code access to the component.
In the preceding diagram, you can see the four participants of this pattern:
ITarget
: This introduces the method signature of the object through this interfaceClient
: The client interacts with objects that implement the ITarget
interfaceAdapter
: This class implements the method of the ITarget
interface and invokes the method of the adapted objectAdaptee
: This is the object for which we need to adapt its interface to make it manipulable by the clientThe client invokes the methodA()
adapter that itself invokes the methodB()
adapter of the adaptee
object.
The following following screenshot represent the organization of our project:
The sales director of your company wants to produce a universal battery charger for mobile phones. This charger can deliver up to 10 volts as output. As the CIO of the company, a member of your development team presents you a first prototype of the charger.
In this chapter, I created a new OS X Command Line Tool project that you'll find in the first_prototype
folder, and I have named it ChargerPrototype
.
Our project is organized as follows:
Interfaces
folder contains the definition of the methods that the client will invoke to charge the phonePhonePrototype.swift
file is a class that defines our test phone and implements the IChargeable
protocolThe result is as follows:
The ChargeableProtocol
interface is a simple protocol that defines the signature of the charge
method:
import Foundation protocol ChargeableProtocol { /// This function is called to charge a mobile phone /// /// Usage: /// /// charge(5.5) /// /// - Parameter volts: voltage needed to charge the battery /// /// - returns: Void func charge(volts: Double) }
Consider that Swift protocols have the same concepts as interfaces.
There are some differences between protocols and Java interfaces, which are as follows:
You can combine protocols at any point with the protocol<>
keyword, for example, declaring a function parameter that must adhere to the A
and B
protocols, which is as follows:
func foo ( var1 : protocol<A, B> ){}
Next, we define a PhonePrototype
class that can be charged using the ChargeableProtocol
protocol:
class PhonePrototype: ChargeableProtocol { /// This function is called to charge a mobile phone /// /// Usage: /// /// charge(5.5) /// /// - Parameter volts: voltage needed to charge the battery /// /// - returns: Void func charge(volts: Double) { print("Charging our PhonePrototype") print("current voltage (volts)") } }
Note the comments added to the code here. With Swift, you can add organized comments that allow you to see the information with a popover (by holding Alt + pointing mouse cursor on the swift the method you want usage information). To check the result of how the comment are displayed proceed like this:
Charger.swift
file. This file represents our universal charger.charge
that appears in the self.phone.charge(volts)
statement. An interrogation point ?
will appear.Then, you will see the following popover, as shown in the following screenshot:
You should consider detailed comments of your functions as best practices. Make sure that all your methods are fully commented. You can check how the popover is well-documented, as follows:
///
to start documenting your code– Parameter parametername: description of the parameter
to describe your parameter– returns: type to return
to describe the returned typeFor more information on existing keywords to document your code, you should check out the following website:
http://ericasadun.com/2015/06/14/swift-header-documentation-in-xcode-7/.
Your engineers present you the prototype of the charger and it seems to work fine.
After loading the ChargerPrototype.xcodeproject
file in Xcode, click on run to launch the code.
On the console, you will see the following result:
Let's see how the Charger
class has been implemented:
import Foundation class Charger { var phone: ChargeableProtocol! let volts = 10.0 func plugMobilePhone(phone: ChargeableProtocol){ print("A mobile is plugged") self.phone = phone self.phone.charge(volts) } }
The implementation of the preceding code is quite simple. By reading it line by line we can deduct how it works:
ChargeableProtocol
. Of course, our universal charger will only communicate with this interface.So, your mobile phone charger works fine with all mobile phone chargers that implement ChargeableProtocol
.
However, there is a problem. Your charger works fine with all phones that implement ChargeableProtocol
but not with mobile phones available on the market. Indeed, each mobile phone manufacturer has its own interface to charge its own products.
It is impossible to use our charger with the existing mobile phone! A roof for a company that wants to sell universal mobile phone chargers.
You can plan an urgent meeting with your teams in order to find a solution to avoid the bankruptcy of your company.
Mike, a new developer of your company, proposes a solution: "To make all mobile phones work with our charger, they need to implement ChargeableProtocol
, but we cannot modify the mobile phones because we do not have the source code. So, we can tell each manufacturer to implement ChargeableProtocol
on their iPhones."
Kevin, the IT project manager, replies, "Mike, welcome to the company, but your solution is not a good choice. Manufacturers have their own systems and they don't want to change them. And what would we do with the phones that are already sold that don't implement ChargeableProtocol
?"
Kevin continues, "The problem that we have is not the manufacturers' problem; he doesn't have to adapt their code to make our charger work with their products, it is up to our company to adapt ours. Yes, we must adapt our code."
Mike then asks, "So, how do you want to proceed?"
Kevin replies, "Well, the concept is simple. If you plan a trip to France, the electrical outlet specifications and shapes are not adapted to the ones that we have here in the U.S. I stumbled upon an adapter that I bought from an electrical shop near my home. This adapter accepts the shape and specification of my American cable and the other one that can be plugged into the French electrical outlet because the side plugged in has the same shape and specification as the French one. All transformations between the U.S. and French specifications are done in the adapter itself."
"The problem is the same here. We must adapt our charger, depending on the manufacturer. So, we will have an adapter per mobile phone and continue to have a unique charger."
Julien, the CEO, replies, "Great, Kevin! Let's go now. We must make our universal charger available before Christmas!"
The first thing that we need to do is to prepare our adapter to make it work with our charger. The charger works only with object that implements ChargeableProtocol
. We will need to implement ChargeableProtocol
with each adapter that we will create.
Once the side charger "plugged to" adapter is completed by implementing ChargeableProtocol
, we will add a reference to the mobile phone that the adapter adapts. Our universal charger doesn't manipulate the mobile phone instance; it is the role of the adapter to manipulate it.
Open the ChargerWithAdapter.xcodeproj
project and check the organization of our code:
We have grouped our actors in the following three folders:
Adaptees
: This contains the component that we must adapt. You can consider this one if you do not have the source code. Remember that you should not use the adapter pattern if you own the source code of the Adaptee
folder.Adapters
: This contains our adapters depending on the mobile phones that we must charge.Interface
: This contains the interface implemented by our adapters and manipulated by the client.main.swift
: This represents the client: the universal charger.Charger.swift
: This is the object that needs to be adapted.Let's study two mobiles phones that we need to adapt: the "Pear" mobile phone and "SamSing" mobile phone: Note that in real life, you will not have the source code of the adaptee, the object that needs to be adapted, you'll only work with their known interfaces.
Let's first analyze the SamSing mobile phone:
import Foundation class SamSingMobilePhone { enum VoltageError: ErrorType { case TooHigh case TooLow } ///Accept only 10 volts func chargeBattery(volts: Double) throws { if volts > 10 { throw VoltageError.TooHigh } if volts < 10 { throw VoltageError.TooLow } print("SamSing mobile phone is charging") print("Current voltage (volts)") } }
Now, let's analyze the Pear mobile phone:
import Foundation class PearMobilePhone { enum PearVoltageError: ErrorType { case NoPower case TooLow case TooHigh } ///Accept only 5.5 volts func charge(volts: Double) throws { guard volts > 0 else { throw PearVoltageError.NoPower} if volts > 5.5 { throw PearVoltageError.TooHigh } if volts < 5.5 { throw PearVoltageError.TooLow } print("Pear mobile phone is charging") print ("Current voltage (volts)") } }
These two classes represent our objects that need to be adapted: the first one is the SamSingMobilePhone
class that has a method called chargeBattery
. This is the method used by the original charger to charge the SamSing mobile phone.
The second one is the PearMobilePhone
class that allows the original charger to charge the battery but this one is simply called charge
. Note that the two mobiles phone need different voltages to be charged.
So what you see here is that our universal charger will need to be adapted to call the charge
method when the Pear mobile phone is plugged in and call the chargeBattery
method when the SamSing mobile phone is plugged in.
We will have to make an adapter for each mobile phone type that we want to be able to charge.
As mentioned earlier, you should not have the source code of the adaptee
object when you use the adapter pattern. The source code is provided here only for demonstration purposes.
Have you checked the source code of our two adaptees? I have voluntary introduced two new Swift keywords that I wish to present you.
First, both classes have an enumeration of possible errors that the charger can handle. The SamSing will throw an error if the voltage is too high or too low; the Pear model can throw the same errors and can throw another one if there is absolutely no power when it is plugged to the charger. As mentioned in the documentation, in Swift, errors are represented by values of type conforming to the ErrorType
protocol. This is the reason why you see each enumeration implementing the ErrorType
protocol:
enum PearVoltageError: ErrorType { case NoPower case TooLow case TooHigh }
Having this enumeration is not sufficient; we need to handle errors when the charge
method is called. For this, we need to tell the method to throw an exception.
For this, we simply add the throw
keyword just before we define the return type, as follows:
func chargeBattery(volts:Double) throws {
Remember that this function definition is exactly the same as:
func charge(volts:Double) throws -> Void {
Not providing the return type is equivalent to stating that the return type is of the type Void
.
Well, once our method is informed that it can handle errors, we still have to raise an error when it is necessary to do so. We make some conditional checks at the beginning of the method, and if something goes wrong, it throws an exception. To raise an exception, we simply use the throw
keyword just before the ErrorType
object (implementing the ErrorType protocol) that we want to raise. (Here is a VoltageError
):
if volts > 10 { throw VoltageError.TooHigh } if volts < 10 { throw VoltageError.TooLow }
As mentioned earlier, the PearMobilePhone
class raises an error; this is when the method receives 0
voltage from the charger. In this case, the PearVoltageError.NoPower
value of the PearVoltageError
enumeration type will be raised.
Let's investigate the charge
method of the PearMobilePhone
class:
///Accept only 5.5 volts func charge(volts: Double) throws -> Void { guard volts > 0 else { throw PearVoltageError.NoPower} if volts > 5.5 { throw PearVoltageError.TooHigh } if volts < 5.5 { throw PearVoltageError.TooLow } print("Pear mobile phone is charging") print("Current voltage (volts)") }
We see that the two if
statements check whether the voltage is, superior to 5.5 volts in the first case, and inferior to 5.5 volts in the second case. Each of these if
statements can throw an error: TooHigh
or TooLow
.
Now, let's check how the NoPower
error is raised:
guard volts > 0 else { throw PearVoltageError.NoPower}
This statement introduces the new Swift 2 keyword, guard … else {
.
The guard
statement is like an if
statement. It executes statements depending on the Boolean value of an expression. The condition must be true for the program to continue after the guard
statement. A guard
statement always has an else
clause. The statement in this clause is executed if the expression is not true.
The guard
statement allows you to perform an early exit when checking conditions.
Something that I really like about guard
is to be able to use it as follows:
guard let unwrappedVar = myVar else { return } print("myVar : (unwrappedVar)")
With an earlier version of Swift, you would have written something like this:
If let unwrappedVar = myVar { Print("myVar : (unwrappedVar")) }else { return } //now if you call the print statement below this will not work //because unwrappedVar is no longer available in this scope. print("myVar : (unwrappedVar)")
So, come back to our pattern. We have seen that our two mobile phones don't have the same interface, and we now need to create an adapter for each of them, as shown in the following diagram:
In the preceding diagram, we can now code our new adapter that will be able to work with the SamSing mobile phone as follows:
import Foundation class SamSingAdapter: ChargeableProtocol { var samSingPhone: SamSingMobilePhone! init(phone: SamSingMobilePhone){ samSingPhone = phone } func charge(volts: Double) { do { print("Adapter started") _ = try samSingPhone.chargeBattery(volts) print("Adapter ended") }catch SamSingMobilePhone.VoltageError.TooHigh{ print("Voltage is too high") }catch SamSingMobilePhone.VoltageError.TooLow{ print("Voltage is too low") }catch{ print("an error occured") } } }
We created a new SamSingAdapter
class that implements ChargeableProtocol
. We need to provide a charge
method that takes the voltage as an argument.
We add a constant called samSingPhone
that instantiates a SamSingMobilePhone()
object, which we will use to call its own chargeBattery
method.
We pass SamSingMobilePhone
as an argument to the constructor of the adapter to get a reference to the mobile that we want to charge. Then, we implement the code of the charge method. (Remember that this is the only method that the client knows.)
Once again, I want to show you some new things that appear with Swift 2.
Swift 2 introduces the do
, try
, catch
, and throw
mechanisms. We already discussed the throw
statement when we discovered our two Adaptee
classes.
A method that has the throw
keyword in its definition is as follows:
func chargeBattery(volts:Double) throws {
It must be called using the try
statement, as this is designed to be clear for developers:
_ = try samSingPhone.chargeBattery(volts)
The _
symbol is a wildcard because the chargeBattery
method of the SamSing mobile doesn't return any value (in fact, it returns Void
). It is useless to write such a statement:
let myVar = try samSingPhone.chargeBattery(volts)
Because you want to handle errors that can be thrown by the chargeBattery
method, this statement must be inside a do { } catch
block:
do { print("Adapter started") _ = try samSingPhone.chargeBattery(volts) print("Adapter ended") }catch SamSingMobilePhone.VoltageError.TooHigh{ print("Voltage is too high") } //…..
So, in the do
block, you'll add the called method that might throw an error, and if you want to handle it, you can catch it in the catch
blocks:
catch SamSingMobilePhone.VoltageError.TooHigh{ print("Voltage is too high") }catch SamSingMobilePhone.VoltageError.TooLow{ print("Voltage is too low") }catch{ print("an error occured") }
The catch
statement will silence all errors and the code will continue to run. If you want to know more about error handling with Swift 2, I recommend that you check out the following website:
https://www.hackingwithswift.com/new-syntax-swift-2-error-handling-try-catch.
Our SamSingAdapter
class is now ready. We will now do the same with the PearAdapter
class.
We will proceed in the same way as the SamSingAdapter
class but with the Pear mobile phone:
import Foundation class PearAdapter: ChargeableProtocol { var pearMobilePhone:PearMobilePhone! init(phone: PearMobilePhone){ pearMobilePhone = phone } func charge(volts: Double) { do { print("Adapter started") _ = try pearMobilePhone.charge(5.5) print("Adapter ended") }catch PearMobilePhone.PearVoltageError.TooHigh{ print("Voltage is too high") }catch PearMobilePhone.PearVoltageError.TooLow{ print("Voltage is too low") }catch{ print("an error occured") } } }
Here, the main differences are that we now have a reference to a PearMobilePhone
object and our charge
method (that implements ChargeableProtocol
) calls the pearMobilePhone.charge
method.
We also need to manage the voltage sent to the phone. An adapter must transform any value to conform to the interface of the adaptee. If we send a very high voltage, our mobile phone will burn and our customers will stop buying our products. So, in our adapter, we set the voltage value sent to the Pear mobile phone to 5.5 volts.
We also catch all the errors that can be thrown by the charge
method of the pearMobilePhone
object.
As we have set the value to 5.5 volts, which is the only voltage accepted by the Pear mobile phone, we will never raise an error, so it is quiet unreadable to have so many catch blocks.
Well, Apple proposes an alternative to you. You can write our adapter like this:
func charge(volts: Double) { print("Adapter started") _ = try! pearMobilePhone.charge(5.5) print("Adapter ended") }
The try!
method allows you to avoid using do
/catch
because you are promising that the call will never fail.
We have our universal charger, already ready, two adapters, and two mobile phones to test our charger. Remember that our charger provides 10 volts by default.
Let's write our simple test program in the main.swift
file:
import Foundation print("*** start test program") // Create our Charger let charger = Charger() print("*** charger ready test program") //Test 1 //Charge a Pear Mobile Phone print("Will charge a Pear Mobile Phone") //1 mobile and adapter creation let pearPhone = PearMobilePhone() let pearAdapter = PearAdapter(phone: pearPhone) //we plug the portable to our charger through the adapter charger.plugMobilePhone(pearAdapter) print("*** -") //Test 2 //Charge a SamSing Mobile Phone print("Will charge a SamSing Mobile Phone") //1 mobile and adapter creation let samSingPhone = SamSingMobilePhone() let samSingAdapter = SamSingAdapter(phone: samSingPhone) //we plug the portable to our charger through the adapter charger.plugMobilePhone(samSingAdapter) print("*** end test program")
I don't think that I need to provide you with more details as the complete code is mentioned here. We prepare our charger, take the first phone, use an appropriate adapter, and then plug the adapter (which is plugged in to our phone too) to the charger. We do the same with the second phone.
Let's run the code and check our result:
Well, it doesn't matter what the mobile is; using our appropriate adapter, by default, the charger will send 10 volts, to the adapter, then the adapter will transform this voltage (or not) and call the appropriate charge method on the phone itself.
This concludes the discovery of the adapter pattern.