Chapter 3. Structural Patterns – Composite and Flyweight

We have already seen three structural patterns: the decorator, proxy, and bridge patterns that provide us with ways of adding state and behavior dynamically, controlling the creation and access of objects, and keeping specifications and implementations separate. This chapter will now focus on the composite and flyweight patterns that are designed to facilitate the manipulation of a group of objects or large number of small objects. The composite is often used and we can also make use of the flyweight pattern.

The flyweight pattern efficiently shares the common information present in small objects by helping you reduce the memory consumption or storage requirements when many values are duplicated.

In this chapter, we will discuss the following topics:

  • The composite pattern
  • The flyweight pattern

The objectives of these two new structural patterns are described in the following table:

Pattern

Objective

The composite pattern

This pattern allows you to compose objects into tree structures and treat the group of objects as an instance of an object.

The flyweight pattern

This pattern allows you to manage huge number of objects by instantiating them on the fly to improve the performance efficiently.

The composite pattern

This pattern is very often used to manipulate a group of objects. Swift, like many other languages already makes use of the composite pattern in its internal structure. For example, in the case of the UIView class available in the cocoa framework, which defines a common behavior of an app layout. Then, individuals view objects in the view hierarchy can be leaf nodes (such as labels) or composites that have collections of other views (such as table view controllers).

Roles

This pattern permits you to treat single components and a group of components in the same way by providing a structured hierarchy of objects. It allows you to build structures of objects in the form of trees that contain both compositions of objects and individual objects as nodes.

Using this pattern, we can create complex trees and treat them as a whole or as parts. Operations can be applied to the whole or the parts too.

We generally find the add, remove, display, find, and group operations in the Composite class.

This pattern can be used when:

  • It is necessary to have a composition hierarchy in a system
  • Clients need to be ignored if they are working with composites objects

Design

The generic UML class diagram is represented in the following figure:

Design

Participants

The participants of this pattern are as follows:

  • Component: This is an abstract class that introduces an object's interface of the composition, implements common methods, and defines the method signature that manages the addition or deletion of components.
  • Leaf: This is a concrete class that defines the behavior of the elements in the composition. It implements the operations that the Composite class supports. A Leaf class does not have its own components.
  • Composite: This is a concrete class that defines the behavior of the components that have children and store the child components. It implements the Leaf class-related operations. This class has an aggregation of the Component class.
  • Client: This class uses the component's interface to manipulate the objects in the composition.

Note

Composite contains components. Components can be Leaf or Composite. It is indeed recursive. A composite holds a set of children; these children may be other composites or leaf elements.

Collaboration

A client sends a request to the leaf throughout the Component interface.

When a component receives a request, it reacts depending on its class. If the component is a leaf, then it will treat the request itself.

If the component is a composite, it will first treat on itself, then it will send a message to each of its child, which in turn will execute a treatment too. Then, when every child completes their treatment, the composite will execute the last treatment.

Illustration

Our company has an online catalog of Video on demand (VOD). All our movies are categorized by genre. As this is a pay-per-view system, each of our videos will have a price, name, and small description.

Now, we want to easily manipulate the display of our full catalog using this new pattern. The following schema represents the organization of our catalog:

Illustration

Implementation

So, it's time for us to apply the generic design of the composite pattern to our case. First, we will redesign our pattern according to our scenario in order to understand what we need to do, as shown in the following diagram:

Implementation

The VODManager class will use the VODComponent interface to access the VOD categories and VOD items. The VODComponent class is our abstract class that will provide the default implementation of the defined methods. The VODItem class will only override the methods that make sense. The VODCategory class will also override methods that make sense, including a way to add the new VODItem and VODCategory objects. After reorganizing our pattern, we are now ready to implement our solution.

Implementation of the VODComponent

First, we create our abstract class, which both VODItem and VODCategory will inherit form. This class will provide the interface for the leaf nodes and composite nodes. Swift doesn't support an abstract class; nevertheless, nothing should prevent us from implementing the default behavior onto our methods, such as informing that a method is not supported using an assert. An assert will just inform us that if it is used in an inappropriate class, then the method will not be supported. We could write our "fake" abstract class like this:

// Abstract Class

class VODComponent {
  
  func add(vodComponent: VODComponent) {
    assert(false, "This method is not supported")
  }
  
  func remove(vodComponent: VODComponent) {
    assert(false, "This method is not supported")
  }
  
  func getName() -> String {
    assert(false, "This method is not supported")
  }
  
  func getDescription() -> String {
    assert(false, "This method is not supported")
  }
  
  func getPrice() -> Double {
    assert(false, "This method is not supported")
  }
  
  func getChild(i:Int) -> VODComponent {
    assert(false, "This method is not supported")
  }
  
  func display() {
    assert(false, "This method is not supported")
  }
}

Implementation of the VODItem leaf

Our component class is ready; a default behavior is available for each method. We can now implement our VODItem class. It is a leaf class in the composite diagram and implements the behavior of the elements of the composite:

class VODItem: VODComponent {
  private var name: String!
  Private var description: String!
  private var price: Double!
  
  init(name:String!, description:String!, price:Double!){
    self.name = name
    self.description = description
    self.price = price
  }
  
  override func getName() -> String {
    return name!
  }
  
  override func getDescription() -> String {
    return description!
  }
  
  override func getPrice() -> Double {
    return price!
  }
  
  override func display() {
    print(" (name!), (price!),  ----  (description!)")
  }
}

See how I have defined the private variables with !. This means that the values of these variables cannot be nil after initialization. This is true because we added a constructor (the init method) where all our arguments must be passed to initialize our private fields.

Then, we override only methods that interest us. The Add/Remove and GetChild methods don't need to be overridden here; this will be done in the composite class.

To ensure that each name, description, and price has a value, we added an exclamation mark to unwrap it.

It is now time to implement our composite category class. We will call this composite class: VODCategory. It will hold VODItems or VODCategory. You'll see that again we will override only methods that interest us in this class. The getPrice() method will not interest us as it doesn't make sense.

Implementation of the VODCategory composite

The first shot of the VodCategory class could be written as follow:

class VODCategory: VODComponent{
  var vodComponents = [VODComponent]()
  private var name: String!
  private var description: String!
  
  init(name:String!, description:String!) {
    self.name = name
    self.description = description
  }
  
  override func add(vodComponent: VODComponent) {
    vodComponents.append(vodComponent)
  }
  
  override func remove(vodComponent: VODComponent) {
    vodComponents.remove(vodComponent)
  }
  
  override func getChild(i:Int) -> VODComponent {
    return vodComponents[i]
  }

  override func getName() -> String {
    return name!
  }
  
  override func getDescription() -> String {
    return description!
  }

  override func display() {
    print(" (name!),  (description!) 
 ----------------")
  }
}

Well, as with VODComponent, we override the methods that interest us. As we can have any number of VODComponent, we add an array of type VODComponent to hold them.

We added the Add, Remove, and GetChild methods. The Add method will allow us to add an item, category, or subcategory. We can also remove it and return a VODComponent class based on its index.

Again, we can add a name and description to our composite that will be displayed when the display() method will be invoked.

Let's take a look at the following code:

  override func remove(vodComponent: VODComponent) {
    vodComponents.remove(vodComponent)
  }

If you write it like the preceding code, you'll get an error because Swift doesn't provide a remove method for the Array type.

It's time to make some modifications in our implementation and introduce you to the use of extensions.

Our problem is that we want to be able to remove an object of the type VODComponent to our vodComponents array using a method called remove (or anything else), where we pass an object that represents the object we want to remove from the list.

If we check the available method when the auto-completion is displayed, we do not see any method that can help us in this purpose, as shown in the following screenshot:

Implementation of the VODCategory composite

In Chapter 2, Structural Patterns – Decorator, Proxy, and Bridge, you could have used the decorator pattern to add such methods to the Array type; you can also propose to simply add a method to the class that will test all the elements in the array by comparing them one by one, and if they are identical, remove them from the list.

What we want is something that can be reused and generic. For this, Swift has extension. This permits you to add a behavior to a class very easily. Let's do it by adding a remove method to the type Array.

The extension must not be added to a class. Indeed, extensions are global. If you add extensions to an OS X or iOS project, you'll generally add them to a dedicated Swift file.

Here is our extension:

extension Array {
  mutating func remove <T: Equatable> (object: T) {
    for i in (self.count-1).stride(through: 0, by: -1) {
      if let element = self[i] as? T {
        if element == object {
          self.removeAtIndex(i)
        }
      }
    }
  }
}

As this function modifies the instance of the Array type and its properties, we mark this function as mutating. Then, we start from the end of the list and compare the elements that we want to find in the current elements of the list:

      if let element = self[i] as? T {
        if element == object {
          self.removeAtIndex(i)
        }
      }

Again, here there are some tricky things to do to make this code without any error. This function tells that we want that Array of type T must implement Equatable (is said with remove <T: Equatable> ) to be able to make the comparison:

        if element == object {

Therefore, we need to modify our abstract class, telling that our class implements the Equatable protocol:

class VODComponent : Equatable {
  
  func add(vodComponent:VODComponent){
    assert(false, "This method is not supported")
  }

Of course, adding this protocol modifies our class diagram a little, but no matter. By implementing this protocol we are telling that elements of type VODComponent can be compared using == and !=.

If this is not completed, then we need to implement this protocol, so the best way to do this is by making a global function outside any class:

// GLOBAL Func
func ==(left: VODComponent, right: VODComponent) -> Bool {
  return left === right
}

Note

The === operator tells us whether the instances of the two components are identical or not.

So, now, all the code required for this class is written; the remove method is available and works perfectly.

Some improvements need to be added to our class to fully complete the implementation of the composite.

Have you seen how we implemented the display method in our composite (VODCategory) ? Indeed, the display() method of the composite only displays the information about itself, but it must invoke the display() method of each element that is contained in the composite. To do this, we will simply add a small part of the code that will iterate all elements that the composite contains in its array by adding a call to their respective display() methods.

Let's change the display() method of the VODCategory class to add an iteration to all our elements:

//VODCategory
class VODCategory:VODComponent{
  
…
  override func display() {
    print(" (name!),  (description!) 
 ----------------")
    for e in vodComponents{
        e.display()
    }
  }
}

So, we iterate over each element of the array using for … in and call the display() method of each element.

Usage

All our classes are now ready and it's time to see how we can use this pattern in our client to test all of this.

We first prepare our VODManager class:

class VODManager{
  var catalog:VODComponent
  
  init(vod: VODComponent) {
      catalog = vod
  }
  
  func displayCatalog() {
      catalog.display()
  }
}

Then, we write our test code:

//USAGE
let horrorCategory = VODCategory(name: "Horror", description: "Horror movies category")
let tvSeriesCategory = VODCategory(name: "TV Series", description: "TV Series category")
let comedyCategory = VODCategory(name: "Comedy", description: "Comedy category")
let voSTTvSeries = VODCategory(name: "VOSTSeries", description: "VOST TV Series sub category")

let allVODComponents = VODCategory(name: "All VOD", description: "All vod components")
let vodManager = VODManager(vod: allVODComponents)

allVODComponents.add(horrorCategory)
allVODComponents.add(tvSeriesCategory)
allVODComponents.add(comedyCategory)

tvSeriesCategory.add(voSTTvSeries)

horrorCategory.add(VODItem(name: "Scream", description: "Scream movie", price: 9.99))
horrorCategory.add(VODItem(name: "Paranormal Activity", description: "Paranormal Activity movie", price: 9.99))
horrorCategory.add(VODItem(name: "Blair Witch Project", description: "Blair Witch movie", price: 9.99))

tvSeriesCategory.add(VODItem(name: "Game of thrones S1E1", description: "Game of thrones Saison 1 episode 1", price: 1.99))
tvSeriesCategory.add(VODItem(name: "Deadwood", description: "Deadwood Saison 1 episode 1", price: 1.99))
tvSeriesCategory.add(VODItem(name: "Breaking Bad", description: "Breaking Bad Saison 1 Episode 1 " , price: 1.99))

voSTTvSeries.add(VODItem(name: "Doc Martin", description: "Doc Martin French serie Saison 1 Episode 1", price: 1.99))
voSTTvSeries.add(VODItem(name: "Camping Paradis", description: "Camping Paradis French serie Saison 1 Episode 1", price: 1.99))

comedyCategory.add(VODItem(name: "Very Bad Trip", description: "Very Bad Trip Movie", price: 9.99))
comedyCategory.add(VODItem(name: "Hot Chick", description: "Hot Chick Movie", price: 9.99))
comedyCategory.add(VODItem(name: "Step Brothers", description: "Step Brothers Movie", price: 9.99))
comedyCategory.add(VODItem(name: "Bad teacher", description: "Bad Teacher Movie", price: 9.99))

vodManager.displayCatalog()

We need to prepare all our components. So, first, we prepare our tree category and then we add items to the good category.

At the end of the script, we call the vodManager.displayCatalog() method that will invoke the display method of all the components.

So, why don't we see something interesting in Playground? In fact, we have some clues that tell us that the code has been properly executed.

On the right-hand side of the screen, we can see the number of times a method has been called, as shown in the following screenshot:

Usage
Usage

However, we will modify this a little bit to get something more accurate for our test. We will modify each display() method by adding the string return type and replace the print statement with a return statement that contains the string to be returned:

  override func display() -> String{
    return " (name!), (price!),  ----  (description!)"
  }

You need to change the display() method of the VODComponent, VODItem, and VODCategory classes.

For VODCategory, you need to modify it like the following code so that it is easily readable:

override func display() -> String{
    var text = " (name!),  (description!) 
 ----------------"
    for e in vodComponents {
        text += "
(e.display()) 
"
    }
    return text
  }

For VODManager, you need to simply add the return type and replace print with return:

  func displayCatalog() -> String{
      return catalog.display()
  }

Finally, move the tvSeriesCategory.add(voSTTvSeries) line in the Usage section (marked with a comment : // USAGE) of the Playground file ( just before the vodManager.display()line; this will make our result easier to read.

Now, you will see something on the right-hand side just after the vodManager.display() line:

Usage

Click on the Eye icon on the right-hand side of the screen. You'll see the result of our vodManager.display() invocation:

Usage

Notice that the display() method of the composite or leaf is called recursively. The items are organized depending on the categories that we have added them to. In the preceding screenshot, we can see that after Horror VODCategory is called, all horror movies (VODItem) that we defined are displayed, then it continues with the TV series that also contain a VOST TV Series subcategory, and so on.

This concludes our discovery of the composite pattern.

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

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