Figure 6-1. Classes and objects
In Objective-C, a class is defined using an @interface directive:
@interface MyClass
// Class definition goes here
@end
A class doesn’t do much by itself. A class is simply the “shape” used to create new objects. When you create an object, you do so by specifying the class of object you want to create and then telling the class to create one. InObjective-C that code looks like this:
MyClass *object = [[MyClass alloc] init];
The result of that expression is a new instance of a class, which is synonymous with an object. The object includes its own storage (data structure) where all of its individual property values are kept. Each object has its ownstorage; changing the value of one object won’t change the value of any other object in the system.
Each object is also associated with a number of methods (functions) that act only on that class of objects. The class defines those methods and every object of that class is endowed with those actions. In Objective-C the codefor methods appears in the @implementation section of that class:
@implementation MyClass
// Methods go here
@end
Methods that do their thing on a particular object (instance) of a class are called instance methods. Instance methods always execute in the context of a single object. When the code in an instance method refers to a propertyvalue, or the special self variable, it’s referring to the property of the specific object it was invoked for. In Objective-C, instance methods start with a hyphen (dash):
- (void)doSomething;
There are also special class methods. A class method is defined by a class but can’t be invoked on any specific object. The context of a class method is the class itself. Class methods are very similar to old-style functions in the sense that they don’t do something to a particular object. Instead,
they usually perform utilitarian functions for the class, like creating new objects or changing global settings. In Objective-C, class methods start with a plus sign:
+ (id)makeSomething;
The +alloc method is a class method. It’s sent to the class to create (allocate) a new object. The -init
method is an instance method. Its job is to prepare (initialize) that single object so it can be used.
Inheritance
Earlier I mentioned that programmers found many situations where a class or structure that they needed was very similar, possibly with only minor additions, to another object or structure that already existed. Furthermore, themethods they’d written for the existing object/structure were all applicable
to the new one. This idea is called inheritance, and is a cornerstone of object-oriented languages.
The idea is that classes can be organized into a tree, with the more general classes at the top, working down to more specific classes at the bottom. This arrangement might look like something in Figure 6-2.
Figure 6-2. A class hierarchy
In Figure 6-2, the generic Object is the base class of all other objects. In Objective-C the base class is NSObject. A subclass of Object is Toy. Toy defines a set of properties and methods common to all Toy objects. The subclassesof Toy are Ball and Vehicle. Subclasses of Vehicle are Train and RaceCar.
The Toy class defines a minimumAge property that describes the youngest age the toy is appropriate for. All subclasses of Toy inherit this property. Therefore, a Ball, Vehicle, Train, and RaceCar all have a minimumAge property.
Similarly, classes inherit methods too. The Vehicle class defines two methods: -start and -stop. All subclasses of Vehicle inherit these two methods, so you can send a -start message to a Train and a -stop message to a RaceCar.The -bounce message can only be sent to a Ball.
This is what computer scientists call subtype polymorphism. It means that if you have an object, parameter, or variable of a specific type (say, Vehicle), you can use or substitute any object that's a subclass of Vehicle. Youcan pass a method that has a Vehicle parameter a Train or a RaceCar
object, and the method will act on the more complex object just as effectively. A variable that refers
to a Toy can store a Toy, a Ball, or a Train. A variable that refers to a Vehicle, however, cannot be set to a Ball, because a Ball is not a subclass of Vehicle.
You’ve already seen this in the apps you’ve written. NSResponder is the base class for all objects that respond to events. UIView is a subclass of NSResponder, so all view objects respond to events. The UIButton is a subclassof UIView, so it can appear in a view and it responds to events.
A UIButton object can be used in any situation that expects a UIButton object, a UIView object, or an NSResponder object.
Abstract and Concrete Classes
Programmers refer to the Toy and Vehicle classes as abstract classes. These classes don’t define usable objects; they define the properties and methods common to all subclasses. You’ll never find an instance of a Toy orVehicle object in your program. The objects you’ll find in your program are Ball and Train objects, which inherit common properties and methods from the Toy and Vehicle classes. The classes of usable objects are calledconcrete classes.
Overriding Methods
Starting a train is a lot different than starting a car. A class can supply its own code for a specific method, replacing the implementation it inherited. This is called overriding a method.
As an example, all subclasses of NSObject inherit a -description method. This returns an NSString describing the object. Of course, the version of -description supplied by NSObject is generic and can’t know about thespecifics of any subclass. As a programmer, you can override
-description in Ball to describe what kind of ball it is, and override -description in Train to describe what kind of train it is.
Sometimes a class—particularly abstract classes—will define a method that doesn’t do anything at all; it’s just a placeholder for subclasses to override. The Vehicle class methods -start and -stop don’t do anything. It’s up tothe specific subclass to decide what it means to start and stop.
For example, the UIViewController class defines the method -viewWillAppear:. This method doesn’t do anything. It’s just a placeholder method that gets called just before the controller’s view appears on the screen. If your view controller subclass needs to do something before your view appears, your class would override -viewWillAppear: and perform whatever it is you need it to do.
If your class’s method also needs to invoke the method defined by its superclass, Objective-C has a special syntax for that. The super keyword means the same thing as self, but messages sent to super go to the methodsdefined by the superclass (ignoring the method defined in this class), as if that method had not been overridden:
[super viewWillAppear:animated];
This is a common pattern for extending (rather than replacing) the behavior of a method. The overriding method calls the original method and then performs any additional tasks.
Design Patterns and Principles
With the newfound power of objects and inheritance, programmers discovered that they could build computer programs that were orders of magnitude more complex than what they had achieved in the past. They alsodiscovered that if they designed their classes poorly, the result was a tangled mess, worse than the old way of writing programs. They began to ponder the question “what makes a good class?”
A huge amount of thought, theory, and experimentation has gone into trying to define what makes a good class and the best way to use objects in a program. This has resulted in a variety of concepts and philosophies,collectively known as design patterns and design principles. Design patterns are reusable solutions to common problems—a kind of programming best practices. Design principles are guidelines and insights into what makes agood design. There are dozens of these patterns and principles, and you could spend years studying them. I’ll touch on a few of the more important ones.
Encapsulation
An object should hide, or encapsulate, its superfluous details from clients—the other classes that use and interact with that class. A well-designed class is kind of like a food truck. The outside of the truck is its interface; itconsists of a menu and a window. Using the food truck is simple: you choose what you want, place your order, and receive your food through the window. What happens inside the truck is considerably more complicated. Thereare stoves, electricity, refrigerators, storage, inventory, recipes, cleaning procedures, and so on. But all of those details are encapsulated inside the truck.
Similarly, a good class hides the details of what it does behind the simple interface defined in its
@interface section. Properties and methods that the clients of that object needs should be declared there. Everything else should be “hidden” in the @implementation or in private @interface sections.
This isn’t just for simplicity, although that’s a big benefit. The more details a class exposes to its clients, the more entangled it becomes with the code that uses it. Computer engineers call this
a dependency. The fewer dependencies, the easier it is to change the inner workings of a class without disrupting how that class is used. For example, the food truck can switch from using frozen French fries to slicing freshpotatoes and cooking them. That change would improve the quality of its French fries, but it wouldn’t require it to modify its menu or alter how customers place their order.
Singularity of Purpose
The best classes are those that have a single purpose. A well-designed class should represent exactly one thing or concept, encapsulate all of the information about that one thing, and nothing else. A method of a class should perform one task. Software engineers call this the single responsibility principle.
A button object that starts a timer has limited functionality. Sure, if you need a button that starts a timer, it would work great. But if you need a button that resets a score, or a button that turns a page, it would be useless. Onthe other hand, a UIButton object is infinitely useful because it does only
one thing: It presents a button the user can tap. When a user taps it, it sends a message to another object. That other object could start a timer, reset a score, or turn a page.
Great objects are like Lego™ blocks. Create objects that do simple, self-contained, tasks and connect them to other objects to solve problems. Don’t create objects that solve whole problems. I’ll discuss this more in Chapter8.
Stability
A ball should be useable all of the time. If you picked a ball you would expect it to bounce. It would be strange to find a ball that wouldn’t bounce until you first turned it over twice, or had to paint it a color.
Strive to make your objects functional regardless of how they were created or what properties have been set. In the Ball example, the -bounce method should work whether the minimumAge property has been set or not. Softwareengineers call these pre-conditions, and you should keep them to a minimum.
Open Closed
There are two corollaries to the single responsibility principle. The first is the so-called “open closed” principle: classes should be open to extension and closed to change. This is a strange one to grasp, but it basically meansthat a class is well designed if it can be reused to solve other problems by extending the existing class or methods, rather than changing them.
Programmers abhor change, but it's the one constant in software development. The more things you have to change in an app, the more chance that it’s going to affect some other part of your project adversely. Softwareengineers call this coupling. It’s a polite way of saying that by changing one thing, you’ll create a bug somewhere else. The open closed principle tries to avoid changing things by designing your classes and methods so youdon’t have to change them in the future.
This takes practice.
Using the toy classes again, both the Train and RaceCar might be electric. You might be tempted to add properties and methods that relate to electric propulsion (voltage, -switchOn, and so on) to the Vehicle class. The problemis what happens when you want to define a wind-up RaceCar. You’ll have to change Vehicle, and that’s going to affect every Train and RaceCar object in your app.
Thinking ahead, you could have added another layer of classes between Vehicle and its subclasses, such as ElectricVehicle and WindUpVehicle. This would let you create a subclass of WindUpVehicle without changing thesubclasses of ElectricVehicle. Now you’re extending your design, not changing it. You’re also thinking beyond the code you write today, to the code you might want to write tomorrow.
Delegation
Another lesson of the single responsibility principle is to avoid mixing in knowledge or logic that’s beyond the scope of your object. A ball has a -bounce method. To know how high the ball will bounce, the method must knowwhat kind of surface the ball is bouncing against. Since this calculation has to be made in the -bounce method, it’s tempting to include that logic in the Ball class. You might do this by adding a -howHigh method thatcalculates the height of a bounce.
That design decision, unfortunately, leads you down a crazy path. Since the bounce calculation varies depending on the environment, the only way to modify the calculation is to override the -howHigh method in subclasses. Thisforces you to create subclasses like BallOnWood, BallOnConcrete, BallOnCarpet, and so on. If you then want to create different kinds of balls, like a basketball and a beach ball, you end up subclassing all of those subclasses(BeachBallOnWood, BasketBallOnWood, BeachBallOnCarpet,and on and on) Your classes are spiraling out of control, as shown in Figure 6-3.
Figure 6-3. Subclassing “solution”
A design pattern that avoids this mess is the delegate pattern. As you’ve seen, the delegate pattern is used extensively in the Cocoa Touch framework. The delegate pattern defers—delegates—key decisions to another object,so that logic doesn’t distract from the single purpose of the class.
Using the delegate pattern, you would create a surface property for the ball. The surface property would connect to an object that implements a -bounceHeightForBall: method. When the ball wants to know how high it shouldbounce, it sends its surface delegate a -bounceHeightForBall: message, passing itself as the ball in question. The Surface object would perform the calculation and return
the answer. Subclasses of Surface (ConcreteSurface, WoodSurface, CarpetSurface, GrassSurface) would override -bounceHeightForBall: to adjust its behavior, as shown in Figure 6-4.
Figure 6-4. Delegate solution
Now you have a simple, and flexible, class hierarchy. The abstract Ball class has BasketBall and BeachBall subclasses. Any of which can be connected to any of the Surface subclasses (ConcreteSurface, WoodSurface,CarpetSurface, GrassSurface) to provide the correct physics. This arrangement also preserves the open closed principle: you can extend Ball or Surface to create new balls or new surfaces, without changing any of the existingclasses.
Other Patterns
There are many, many other design patterns and principles. I don’t expect you to memorize them—just be aware of them. With an awareness of design patterns, you’ll begin to notice them as you see how classes in the Cocoa Touchframework and elsewhere are designed; iOS is a very well-designed system.
Here are other common patterns you’ll encounter:
n Singleton pattern: a class that maintains a single instance of an object for use by the entire program. The [UIApplication sharedApplication] is a singleton.
n Factory pattern and class clusters: a method that creates objects for you (instead of you creating and configuring them yourself). Often, your code won’t know what object, or even what class of objects,needs to be created. A factory method handles (encapsulates) those details for you. The +[NSURL
URLWithString:] method is a factory method. The class of NSURL object returned
will be different, depending on what kind of URL the string describes.
n Decorator pattern: dress up an object using another object. A UIBarButtonItem is not, ironically, a button object. It’s a decorator that may present a button,
a special control item, or even change the positioning of controls in a toolbar.
n Lazy initialization pattern: waiting until you need an object (or its properties) before creating it. Lazy initialization makes some things more efficient and reduces pre-conditions. UITableView lazily creates tablecell objects; it waits until the moment it needs to draw a row before asking the data source delegate to provide a cell for that row.
There are, of course, many others.
The first major book of design patterns (Design Patterns: Elements of Reusable Object-Oriented Software) was published in 1994 by the so-called "Gang of Four": Erich Gamma, Richard Helm, Ralph Johnson, and JohnVlissides. Those patterns are still applicable today, and design patterns have become a “must know” topic for any serious programmer. The original book is not specific to any particular computer language; you couldapply these principles to any language, even non-object-oriented ones. Many authors have since reapplied, and refined, these patterns to specific languages. So if you’re interested in learning these skills primarily forObjective-C, for example, look for a book on design patterns for Objective-C.
Summary
That was a lot of theory, but it’s important to learn these basic concepts. Understanding design pattern and principles will help you become a better software designer, and you’ll also appreciate the design of the iOS classes.Observe how iOS and other experienced developers solve problems, identify the principles they used, and then try to emulate that in your own development.
Theory is fun, but do you know what’s even more fun? Cameras!




No comments:
Post a Comment