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 objectives of these two new structural patterns are described in the following table:
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).
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:
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.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.
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:
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:
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.
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") } }
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.
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:
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 }
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.
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:
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:
Click on the Eye icon on the right-hand side of the screen. You'll see the result of our vodManager.display()
invocation:
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.