Structural patterns

Structural patterns are patterns that describe how objects should relate to each other so that they can work together to achieve a common goal. They help us lower our coupling by suggesting an easy and clear way to break down a problem into related parts and they help raise our cohesion by giving us a predefined way that those components will fit together.

This is like a sports team defining specific roles for each person on the field so that they can play together better as a whole.

Composite

The first structural pattern we are going to look at is called the composite pattern. The concept of this pattern is that you have a single object that can be broken down into a collection of objects just like itself. This is like the organization of many large companies. They will have teams that are made up of smaller teams, which are then made up of even smaller teams. Each sub-team is responsible for a small part and they come together to be responsible for a larger part of the company.

Hierarchies

A computer ultimately represents what is on the screen with a grid of pixel data. However, it does not make sense for every program to be concerned with each individual pixel. Instead, most programmers use frameworks, often provided by the operating system, to manipulate what is on the screen at a much higher level. A graphical program is usually given one or more windows to draw within and instead of drawing pixels within a window; a program will usually set up a series of "views". A view will have lots of different properties but they will most importantly have a position, size, and background color.

We can potentially build up an entire window with just a big list of views but programmers have devised a way of using the composite pattern to make the whole process much more intuitive. A view can actually contain other views, which are generally referred to as subviews. In this sense, you can look at any view like a tree of subviews. If you look at the very root of the tree, you will see a complete image of what will be displayed on the window. However, you can look at any of the tree branches or leaves and see a smaller part of that view. This is the same as looking at a large team as a whole versus looking at a small team within that larger team. In all of this, there is no difference between a view at the root of the tree and a view at the leaf of the tree, except the root has more sub-views.

Let's look at our own implementation of a View class:

class View {
    var color: (red: Float, green: Float, blue: Float)
        = (1, 1, 1) // white
    var position: (x: Float, y: Float) = (0, 0)
    var size: (width: Float, height: Float)
    var subviews = [View]()
    
    init(size: (width: Float, height: Float)) {
        self.size = size
    }
}

This is a pretty simple class, but by adding the subviews property, which is an array of additional views, we are using the composite pattern to make this a very powerful class. You can imagine a virtually infinite hierarchy of views that are all contained within a single parent view. That single view could be passed to some other class that could draw the entire hierarchy of views.

As an example, let's set up a view that has red in the left-half, green in the upper-right half, and blue in the lower-right half:

Hierarchies

To produce this with our class, we could write a code similar to:

let rootView = View(size: (width: 100, height: 100))

let leftView = View(size: (width: rootView.size.width / 2, height: rootView.size.height))
leftView.color = (red: 1, green: 0, blue: 0)
rootView.subviews.append(leftView)

let rightView = View(size: (width: rootView.size.width / 2, height: rootView.size.height))
rightView.color = (red: 0, green: 0, blue: 1)
rightView.position = (x: rootView.size.width / 2, y: 0)
rootView.subviews.append(rightView)

let upperRightView = View(size: (width: rightView.size.width, height: rootView.size.height / 2))
upperRightView.color = (red: 0, green: 1, blue: 0)
rightView.subviews.append(upperRightView)

In this implementation, we actually have a red left half as defined by leftView and a blue right half as defined by rightView. The reason the upper-right half is green instead of blue is that we added upperRightView as a subview to rightView and only made it half the height. This means that our view hierarchy looks similar to the following image:

Hierarchies

It is important to note that the position of upperRightView is left at 0, 0. That is because the positioning of all sub-views will always be relative to their immediate parent view. This allows us to pull any view out of the hierarchy without affecting any of its sub-views; drawing rightView within rootView will look exactly the same as if it were drawn on its own.

You could also set up separate objects to manage the contents of different subsections of the main view. For example, to create a program like Xcode, we might have one object that manages the contents of the file list on the left and a different object that manages the display of the selected file. Clearly, Xcode is much more complex than that, but it gives us an idea of how we can build incredibly powerful and complex software with relatively simple concepts.

You may, however, have noticed a potential problem with our view class. What would happen if we added a view to its own subview hierarchy somewhere. That is most likely going to cause an infinite loop when another part of our code goes to draw the view. As another challenge to you, try to update our View class to prevent this from happening. I suggest you start by making subviews private and providing methods for adding and removing subviews. You will probably also want to add an optional superview property that will reference the parent view.

Alternative to subclassing

As you can see, the composite pattern is ideal for any situation where an object can be broken down into pieces that are just like it. This is great for something seemingly infinite like a hierarchy of views, but it is also a great alternative to subclassing. Subclassing is actually the tightest form of coupling. A subclass is extremely dependent on its superclass. Any change to a superclass is almost certainly going to affect all of its subclasses. We can often use the composite pattern as a less coupled alternative to subclassing.

As an example, let's explore the concept of representing a sentence. One way to look at the problem is to consider the sentence a special kind of string. Any kind of specialization like this will usually lead us to create a subclass; after all, a subclass is a specialization of its superclass. So we could create a Sentence subclass of String. This will be great because we can build strings using our sentence class and then pass them to methods that are expecting a normal string.

However, there is an important obstacle to this method: we don't have control of the String code and even worse we can't even look at the code so we don't even know how the characters are stored. This means that the code can be changed underneath us with an update from Apple without our knowledge. Even with our knowledge, this could cause a maintenance headache.

A better solution would be to use the composite pattern and implement a Sentence type that contains strings:

struct Sentence {
    var words: [String]
    
    enum Type: String {
        case Statement = "."
        case Question = "?"
        case Exclamation = "!"
    }
    
    var type: Type
}

Here, we were able to give more meaningful names to the parts of the sentence with various words and we set up a Type enumeration that allows us to use different end punctuations. As a convenience, we can even add a string calculated property so that we can use the sentence as a normal string:

struct Sentence {
    // ..

    var string: String {
        return self.words.joinWithSeparator(" ")
            + self.type.rawValue
    }
}

let sentence = Sentence(words: [
    "This", "is",
    "a", "sentence"
], type: .Statement)
print(sentence.string) // "This is a sentence."

This is a much better alternative to subclassing in this scenario.

Delegate

One of the most commonly used design patterns in Apple's frameworks is called the delegate pattern. The idea behind it is that you set up an object to let another object handle some of its responsibilities. In other words, one object will delegate some of its responsibilities to another object. This is like a manager hiring employees to do a job that the manager cannot or does not want to do themselves.

As a more technical example, on iOS, Apple provides a user interface class called UITableView. As the name suggest, this class provides us with an easy way to draw a list of elements. On its own, a UITableView isn't enough to make an interface. It needs data to display and it needs to be able to handle all kinds of user interactions, such as tapping, reordering, deleting, and so on.

One instinct is to create your own subclass of UITableView, maybe something like PeopleTableView. This is an OK option until you remember how we discussed that subclassing is actually the strongest type of coupling between two objects. In order to properly subclass a UITableView, you would have to be pretty intimately aware of how the superclass works. This is especially difficult when you are not even allowed to see the code of the superclass.

Another option is to set data on the table view and use the observer pattern to handle user interactions. This is better than the subclassing option, but most data you will want to display is not static and therefore it would be cumbersome to make updates to the table view. It will also still be hard to implement an object that can be reused easily for other ways of displaying a list of information.

So instead, what Apple did is, they created two different properties on UITableView: delegate and dataSource. These properties are there so that we can assign our own objects to handle various responsibilities for the table. The data source is primarily responsible for providing the information to be shown in the table and the delegate's responsibility is to handle user interaction. Of course, if these objects could be of any type, the table view would not really be able to interact with them. Also, if these objects were of a specific type, we would still run into the same subclassing problem. Instead, they are defined to implement the UITableViewDelegate and UITableViewDataSource protocols respectively.

These protocols define only the methods necessary to allow the table view to properly function. This means that the delegate and dataSource properties can be any type as long as they implement the necessary methods. For example, one of the critical methods the data source must implement is tableView:numberOfRowsInSection:. This method provides the table view and an integer referring to the section that it wants to know about. It requires that an integer be returned for the number of rows in the referenced section. This is only one of multiple methods that data source must implement, but it gives you an idea of how the table view no longer has to figure out what data it contains. It simply asks the data source to figure it out.

This provides a very loosely coupled way to implement a specific table view and this same pattern is reused all over the programming world. You would be amazed at what Apple has been able to do with its table view, with very little to no pain inflicted on third party developers. The table view is incredibly optimized to handle thousands upon thousands of rows if you really wanted it to. The table has also changed a lot since the first developer kit for iOS, but these protocols have very rarely been changed except to add additional features.

Model view controller

Model view controller is one of the highest levels and most abstract design patterns. Variations of it are pervasive across a huge percentage of software, especially Apple's frameworks. It really can be considered the foundational pattern for how all of Apple's code is designed and therefore how most third party developers design their own code. The core concept of model view controller is that you split all of your types into three categories, often referred to as layers: model, view, and controller.

The model layer is for all of the types that represent and manipulate data. This layer is the real foundation of what your software can do for its user, so it is also often referred to as the business logic. For example, the model layer from an address book app would have types representing contacts, groups, and so on. It would also contain logic to create, delete, modify, and store those types.

The view layer is for all types involved in the display and interaction of your software. It consists of types like tables, text view, and buttons. Essentially, this layer is responsible for displaying information to the user and providing the affordances for how a user can interact with your application. The view in an address book app would consist of the displayed list of contacts, groups, and contact information.

The final layer, controller, is mostly just the glue code between the model and view layers. It will instruct the view of what to display based on the data in the model layer and it will trigger the right business logic depending on the interactions coming from the view layer. In our address book example, the controller layer would connect something such as a contact add button in the view, to the logic defined in the model for creating a new contact. It will also connect things like the on screen table view to the list of contacts in the model.

In the ideal implementation of model view controller, no model type should ever have any knowledge of the existence of a view type and no view type should know about a model type. Often, a model view controller is visualized sort of like a cake:

Model view controller

The user sees and interacts with the top of the cake and each layer only communicates with its adjacent layers. This means that all communication between the view and the model layers should go through the controller layer. At the same time, the controller layer should be pretty lightweight, because the model layer is doing the heavy lifting on the logic side of the application and the view layer is doing the heavy lifting on drawing to the screen and accepting user input.

One of the main benefits of this design pattern is that it provides a logical and consistent way to break down many pieces of software. This greatly increases your ability to share your code with other developers and understand their code. It gives everyone a frame of reference for when they try to understand another large code base that they haven't seen before. The naming of classes also gives strong clues to developers about what role a type will play in the overall system. Virtually every view class in iOS has the word "view" in it: UITableView, UIView, UICollectionViewCell, etc. Also, most of the controller layer classes that Apple provides have the word controller in them: UIViewController, UITableViewController, MFMailComposeViewController, etc. The model layer is mostly left to third party developers, other than the basic data types, since Apple isn't going to be able to help much with the business logic of your software. However, even among third party developers, these classes are often nouns named after the data they are representing or manipulating: Person, AddressBook, Publisher, and so on.

Another huge benefit of model view controller is that most components will be very reusable. You should be able to easily reuse views with different types of data like you can use a table view to display virtually any kind of data without changing the table view type and you should be able to display something like an address book in lots of different ways without changing the address book type.

As useful as this pattern is, it is also extremely hard to stick to. You will probably spend your entire development career evolving your sense for how to effectively breakdown your problems into these layers. It is often helpful to create explicit folders for each layer, forcing yourself to put every type into only one of the categories. You will also probably find yourself creating a bloated controller layer, especially in iOS, because it is often convenient to stick business logic there. More than any other design pattern, model view controller is probably the one that can be most described as something you strive for but rarely ever perfectly achieve.

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

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