Object-Oriented Programming - OBJECT-ORIENTED PROGRAMMING - Understanding Swift Programming: Swift 2 (2015)

Understanding Swift Programming: Swift 2 (2015)

PART 2: OBJECT-ORIENTED PROGRAMMING

11. Object-Oriented Programming

Although many of the readers of this book will be Objective-C programmers looking only to understand Swift, some will have programming experience but know little or nothing about object-oriented programming. This chapter is for those readers. If you are familiar with object-oriented programming, you may want to skip or just skim most of this chapter. However, everyone should probably read the section on polymorphism, which describes the different ways Swift uses this characteristic of object-oriented programming.

Computing began with the "stored program computer" in which instructions for how to manipulate bits were stored in memory. The first programming was in machine language, meaning that the programmer entered the ones and zeroes that defined the instructions directly into the computer by hand. Programs were sequences of steps that performed calculations and stored the result in memory. Assembly languages were then developed that automated the tedium of hand coding. Later, high level languages were developed that used named variables, and allowed statements like this:

var m = 5;

The same basic model was still used, though—some calculation followed by storing the result in memory, now often known as "changing state", or changing the state of the computer.

This led to procedural programming, organizing code in subroutines, functions, or methods that allowed the same code to be executed multiple times in different contexts and thus reused. Efforts were made to organize these procedures as structured modules so as to avoid the dreaded “spaghetti code” that so often resulted. The procedures operated on data structures that were typically large and that often involved the entire program or large parts of it rather than specific modules.

Object-oriented programming is a quite different style of programming. The idea with object-oriented programming is to create objects that combine both data structures and procedures. Those objects are often intended to represent real-life entities, including both their data (in the form of what are known as instance variables or properties) and their behavior (in the form of methods.)

Objects (combining both data structures and code) served as the basic unit, or module, of computing, and considerable effort is made to isolate each object from other objects.

Twenty-five years ago object-oriented programming was only one contender among many styles of programming. But it was adopted seemingly overwhelmingly by industry—to the surprise of many academics. However, despite its seeming dominance, studies comparing object-oriented approaches with more traditional procedural and modular methods haven't demonstrated significant gains. (It is harder and harder to do these kinds of comparison studies because the object oriented approach is now so dominant.)

According to the TIOBE Programming Community Index (tiobe.com) for August, 2015, the C language, which is not object-oriented, is the second most popular programming language. But nearly all of the top ten, namely Java, C, C++, C#, Python, Objective-C, PHP, Visual Basic .NET, JavaScript, and Perl are all at least in theory object-oriented. Many of these were not originally object-oriented languages, but object-oriented capabilities were added because of the popularity of the approach. (Objective-C ranked #6, down from its peak of #3, while Swift was at #17.)

As winners of battles tend to write their history, you will read a lot of nonsense written by proponents of object-oriented programming. You might get the impression that it is impossible to build a large scale software system without the use of object-oriented programming (many successful large scale systems have been built with other kinds of languages.) You might get the impression that before object-oriented programming, nobody ever broke programs down into smaller modules—it was just one big pile of spaghetti. You might get the impression that object-oriented programming is an advantageous approach for every programming problem.

For building apps for devices like the iPhone, iPad, iPod Touch, Apple Watch, and the Macintosh, object-oriented programming is actually a pretty good choice. This is not because it is necessarily a good way to design the business logic in apps (it often helps, and is unlikely to hinder). But more important, it is because iOS (and the Mac) have very large libraries—Foundation and Cocoa Touch in the case of iOS—that are organized as classes. And as the developers of Smalltalk found—when the first GUI (Graphical User Interface) oriented user interfaces were built in the 1970s at Xerox Palo Alto Research Center—object-oriented programming is a very good way to build these kinds of user interfaces.

Despite the value of the existing class-based libraries, there is a growing recognition that classes are often not a good way to solve certain programming problems, and Swift has itself been designed to make substantial use of alternative approaches, including using structures and protocols rather than classes. See Chapter 34 on “Protocol Oriented Programming”.

I went into some detail earlier about the strong emphasis that programming over the decades has placed on storing results to memory, that is ,"changing state". This may be so seemingly necessary to today's programmers as water might be to a fish—-so obvious that programmers, or fish, don't even think about it. But there is a very different style of programming now becoming more popular that deliberately does not change the state, or memory, of the computer. There are some significant advantages to this style, known as functional programming. Swift has been influenced to an extent by functional programming, and you can actually do some functional programming in it. The principal pure functional language, Haskell, is now at #41 in the TIOBE list. Swift may be a good chance for functional programming to gain at least some traction.

However, Swift is primarily an object-oriented (and, arguably, protocol-oriented) language. There are three principles of object-oriented programming that have been considered fundamental to object oriented languages. These are encapsulation, inheritance, and, arguably, polymorphism.

Encapsulation

Encapsulation bundles together information and behavior in one component, or object, and object-oriented programming languages provide mechanisms for this bundling and for isolating the object from the rest of the world.

Typically, methods within an object can operate on the data in the same object. However, there are limits on what another object, outside of the object in question, can access. In general, direct access to data in an object is not allowed, with outside objects required to execute so-called getter and setter methods of the bundle to read or write information, even when this indirect access is allowed.

Often, the ability of the rest of the program to access properties or methods within an object is carefully controlled with the intent of minimizing that access to what is absolutely required. This is typically done as a general practice to prevent errant code from causing damage.

It is also a common practice to provide other objects with a specific interface that does not change, while hiding the details of the implementation within an object from outside objects. This allows code within objects to be rewritten to fix bugs, make the code run faster, or be more general without "breaking" outside code that depends upon it.

Swift includes mechanisms to enforce encapsulation, including the requirement of accessing properties of objects only with dot syntax (which requires getters and setter methods) and having access control keywords (e.g., private, public) that, along with other mechanisms, control access to data and methods for particular objects.

Inheritance

Swift, Objective-C, and most other object-oriented languages have inheritance. As part of the basic dividing up of the world into objects, entities called classes are defined. Objects are created based on these classes, which serve essentially as blueprints that define what data structures the objects contain, and what methods the objects are allowed to use. Each object has its own copy of the data structure so created. An object that has such a copy is known as an instance of the class. Thus, if the class is Apple and it has a property defined named color, multiple objects can be created using the Apple class blueprint. Some might have their color property stored as "red", while others may have their color property stored as "green".

When a new class is created, it can define itself as being a subclass of another existing class. If it does this, it will inherit all of the data structures (properties) of the class it is a subclass of. (The original existing class is known as the subclass's superclass.) It will also inherit all of the methods of its superclass. Often, a superclass will have inherited data structures and methods from its own superclass, and that superclass's superclass, and these will also be inherited by a new subclass. This is part of what makes the iOS and Mac APIs so powerful. They are organized into classes that form a hierarchy. A new subclass can access large amounts of functionality because of inheriting, directly and indirectly, data structures and methods.

This makes it easy to reuse substantial amounts of code. And it also makes maintenance easier and more productive. If a bug is fixed in a method at a level high in the hierarchy, it will be fixed for all of the methods below it in the hierarchy. (It is also true that if a bug is introduced into a method high in the hierarchy, it will appear in all of the methods below it in the hierarchy.)

There are some downsides to inheritance. Although much of the effort in object-oriented programming goes into making objects largely independent from each other and only “loosely coupled”, it has been increasingly recognized that inheritance often couples objects that have a subclass-superclass relationship awfully tightly. Even twenty years ago this was recognized and advice given to “favor composition over inheritance” in the influential book Design Patterns. This meant that composing classes out of other classes without inheritance was often desirable. And other criticisms have been offered, leading up to Swift being designed with as much of an eye to noninheritance approaches as to inheritance. See Chapter 34 on “Protocol Oriented Programming.”

Swift has single inheritance, meaning that it can only direct inherit from one superclass. Only one popular programming language, C++, allows multiple inheritance, or the ability to directly inherit from more than one superclass. The problem is that there are conceptual problems with multiple inheritance. For example, suppose classes B and C both inherit from class A, but B overrides one of the inherited methods with an implementation of its own. And suppose further that class D inherits from both class B and class C. Which version of the method that was overridden should class D inherit? The one that was overridden from B or the one that was not overridden from C?

Some object-oriented languages do not have inheritance, but are based on prototypes. A new class can be defined as a clone of a given object, and is given copies of its properties and methods. However, in prototype-based languages like JavaScript it is possible to organize things so as to create what are effectively classes and inheritance.

Some apps may have substantial business logic that might be usefully organized by the use of classes that inherit from each other. Other kinds of business logic may not fit easily into this structure.

Polymorphism

It is commonly said that to be object-oriented, a programming language must have encapsulation, inheritance, and polymorphism. Requiring encapsulation is reasonable: it is fundamental to the approach. Requiring inheritance is reasonable, especially if languages like JavaScript that are based on prototypes rather than classes are included, given that this is a similar approach.

Polymorphism is more controversial. It's a very fancy word that seems to cause as much confusion as clarity and feeds the suspicion of critics of object-oriented programming that the approach is as much fluff as substance.

Matt Gallagher—hardly a critic of object-oriented programming—on his blog Cocoa With Love, says that "the term is so abstract as to be worthless" and "only serves to obfuscate and confuse". Gallagher doesn't have an issue with the concept itself, but with the term, saying that other words that better describe specific situations should be used instead.

The term polymorphism was apparently first used in biology, where it refers to two different forms of the same organism occurring simultaneously. Thus, most jaguars in South America are tan/orange in color, while about 6% are dark, nearly black, though they have the same genetics. The term is from the Greek language and basically means "many forms or shapes."

If you look up the topic in Wikipedia, you will read:

If a function denotes different and potentially heterogeneous implementations depending on a limited range of individually specified types and combinations, it is called ad hoc polymorphism. [From wikipedia.org, Polymorphism (computer science). Retrieved 2015.]

This is more commonly known as function overloading.

The same source discusses parametric polymorphism, which turns out to mean generic programming.

So I agree with Gallagher that the word isn't very helpful, and I will try to avoid using the term polymorphism and its variations in the rest of this book, relying on the more specific and concrete terms instead. Apple's documentation on Swift in fact doesn't use the word at all; nor do most books on Swift.

Swift does have many aspects that make use of polymorphism, many more than Objective-C, which actually makes rather minimal use of it. I've identified six aspects of Swift that seem to qualify, and I have described them below:

METHOD OVERRIDING

Often, a class will deal with a general concept, and subclasses of it with more specific versions. Thus, a class may exist named Shape, with subclasses Rectangle and Circle. All may include a method named draw that can draw the shape. The draw method in Rectangle is created by inheriting, and then overriding (changing the code) in the draw method inherited from Shape to what is necessary to draw a rectangle.

The system must then choose which version of draw will be executed based on the type of the particular instance.

SUBTYPING

In Swift an array can contain only instances of the same type. In fact, the compiler and runtime will allow either instances of a given type or an instance of a subclass of that type. If you try to initialize an array by providing a list of values that are not instances of subclasses or superclasses of each other but have a common ancestor, the compiler will infer the type of the array to be the type of that common ancestor, and then allow all of the values to be placed into the array because they are instances of subclasses of the common ancestor.

Allowing an object of a given type to be contained in a variable typed as its superclass is known as subtyping, or subtype polymorphism. (The reverse is not allowed. An object of a given type cannot be stored in a variable that has the type of the object’s subclass.)

OPERATOR OVERLOADING

The same operator can be used in different contexts, with each context having a particular set of types of operands. For example, the "+" operator is used for addition with integer types, but, when used with strings, causes different code to be executed that results in concatenation of the strings. For details, see Chapter 25, "Operators Revisited: Custom Operators and Operator Overloading."

FUNCTION OVERLOADING

Functions can be defined that have the same name but with different combinations of types for input parameters and return values. The compiler is smart enough to call the appropriate code in a given situation, based on the types that are used in the call to the function. Overloading is also often used with initializers in Swift (which are similar to but not quite the same as functions or methods).

SUBSCRIPT OVERLOADING

The use of a subscript in a particular context is defined much like a function, with input parameters and a return value that have specific types. If multiple subscript definitions are included in a particular class, structure, or enumeration, the compiler will select the proper code based on the type.

GENERIC PROGRAMMING

In generic programming (see Chapter 28 on "Generic Programming" for details) code is written that uses an abstract symbol (such as T) whereever a particular type would otherwise be used. This allows a programmer to write a single piece of code rather than multiple pieces of code that each handle a different type or pattern of types. For example, a programmer might have to write one function to do a calculation with an integer type, and a second function to do the same calculation with a floating point type. By using generic programming, a single piece of code is written with abstract symbols replacing the names of particular types. The compiler creates the object code for different functions based on the types needed and sets up the function call so that it calls the appropriate version of the function for the particular type involved when it is run.

Hands-On Exercises

Go to the following web address with a Macintosh or Windows PC to do the Hands-On Exercises.

For Chapter 11 exercises, go to

understandingswiftprogramming.com/11