Chapter 4. Structural Patterns – Adapter and Facade

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.

The adapter pattern

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.

Roles

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.

Design

The generic class diagram of the pattern is as follows:

Design

Participants

In the preceding diagram, you can see the four participants of this pattern:

  • ITarget: This introduces the method signature of the object through this interface
  • Client: The client interacts with objects that implement the ITarget interface
  • Adapter: This class implements the method of the ITarget interface and invokes the method of the adapted object
  • Adaptee: This is the object for which we need to adapt its interface to make it manipulable by the client

Collaboration

The 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:

Collaboration

Illustration

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.

Implementing our first prototype

Our project is organized as follows:

  • The Interfaces folder contains the definition of the methods that the client will invoke to charge the phone
  • The PhonePrototype.swift file is a class that defines our test phone and implements the IChargeable protocol

The result is as follows:

Implementing our first prototype

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)
}

Tip

Consider that Swift protocols have the same concepts as interfaces.

There are some differences between protocols and Java interfaces, which are as follows:

  • Swift protocols can also specify properties that must be implemented (for example, fields)
  • Swift protocols need to deal with values/references through the use of the mutating keyword (because protocols can be implemented by structs and classes)

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:

  1. Open the Charger.swift file. This file represents our universal charger.
  2. Tap and hold the Alt key.
  3. Position the mouse charge that appears in the self.phone.charge(volts) statement. An interrogation point ? will appear.
  4. Click on charge.

Then, you will see the following popover, as shown in the following screenshot:

Implementing our first prototype

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:

  • Use /// to start documenting your code
  • Use – Parameter parametername: description of the parameter to describe your parameter
  • Use – returns: type to return to describe the returned type

Note

For 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:

Implementing our first prototype

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:

  • Our charger contains a reference to a phone. The phone must implement ChargeableProtocol. Of course, our universal charger will only communicate with this interface.
  • Then, we have a method where we can plug our phone.
  • We assign the phone to our reference and call the charge method of the referenced phone.

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.

Tip

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!"

Implementation

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:

Implementation

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.

Implementation of our adaptees

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:

Implementation of our adaptees

Implementation of the SamSingAdapter class

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.

Implementation of 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.

Note

Swift 2 requires an exhaustive try/catch error handling. The last catch statement is our default catch-all block.

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:

Implementation of the PearAdapter class

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset