In order to avoid tearing, I declare in advance: This article is purely translation, just for learning, plus the level is limited, forgive me!
【原文】https://www.objc.io/issues/13-architecture/singletons/
iOS Subclassing - by Chris Eidhof
This article is a little different from what I usually write. It's less of a guide and more of a collection of ideas and patterns. Almost all the patterns I'm about to describe are found the hard way by making mistakes. By no means I'm an authority on subclassing, but I just wanted to share some of the things I've learned. Don't use it as a definitive guide, but a collection of examples.
When asked about object-oriented programming ( OOP
), Alan Kay (creator) writes that he's not about classes, but message passing ^ ?. However, there is still a lot of focus on creating class hierarchies. In this post, we'll look at a few useful cases, but we'll mostly focus on creating complex class hierarchies. In our experience, this makes the code simpler and easier to maintain. There are many articles on this topic, which you can find in books like Clean Code and Code Complete , which I recommend everyone to read.
When to subclass
First, let's discuss when it makes sense to subclass. If you are building one with a custom layout UITableViewCell
then you need to create a subclass. This is true of almost every point; once you start laying out, it makes sense to move it into a subclass so that you not only neatly organize your code, but also make the code reusable throughout the project.
Suppose, your code is for different platforms and versions, and you need to write custom parts for each platform and version. This is where it makes sense to create a OBJDevice
class that can be subclassed out OBJIphoneDevice
and OBJIPadDevice
, even deeper OBJIPhone5Device
, that can override specific methods. For example, your OBJDevice
class can contain applyRoundedCornersToView:withRadius:
methods. It has a default implementation, but this method can be overridden by specific subclasses.
Another very useful case of subclassing is in model objects ( model object
). Most of the time, the implementation methods that model objects inherit from classes are isEqual:
, hash
, copyWithZone:
and description
. These methods are implemented once by iterating over the properties and it is hard to go wrong. (If you're looking for such a base class, consider using Mantle, which does exactly that, and more than that.)
When not to subclass
I've worked on a lot of projects and I've seen deep subclassing. I'm ashamed that I did that too. Unless the layers are very shallow, the limit is quickly reached.
Fortunately, if you find yourself at such a deep level, there are plenty of options. In the following subsections, we will analyze each part in depth. This is a good option if your subclasses just share the same interface and protocol. If you know that an object needs to be modified a lot, you may need to use delegates to dynamically change and configure it. category
Category( ) may be an option when you want to extend some simple functionality to an existing object . When you have a series of subclasses, each of which override( override
) the same method, you might use a configuration object. Finally, when you want to reuse some functionality, it's better to combine multiple objects rather than extend them.
Alternative
Scheme: Protocol
Often, one reason to use subclassing is when you want to ensure that an object responds to a certain message. Imagine that in an application you have a Player( player
) object that can play a video. Now, if you want to add YouTobe
support, you need the same interface, but a different implementation. One is to implement it with subclasses as follows:
@interface Player: NSObject
- (void)play;
- (void)pause;
@end
@interface YouTobePlayer: Player
@end
Maybe, the two classes don't share much code, just the same interface. In this case, using a protocol might be a better solution. Using protocols, you might write code like this:
@protocol VideoPlayer <NSObject>
- (void)play;
- (void)pause;
@end
@interface Player: NSObjet <VideoPlayer>
@end
@interface YouTobePlayer: NSObject <VideoPlayer>
@end
In this case, there is YouTobePlayer
no need to know Player
the internals.
Scheme: Delegate
Suppose again, that you have a Player
class like the example above. Now, in one place, you want to paly
perform a custom action in a method. It's fairly easy to do: you can create a custom subclass, override the paly
method, call [super paly]
it, and perform custom functionality. This is one way of handling it. Another is to mutate the Player
object and give him a delegate. E.g:
@class Player;
@protocol PlayerDelegate
- (void)playerDidStartPlaying:(Player *)player;
@end
@interface Player: NSObject
@property (nonatomic, weak) id<PlayerDelegate> delegate;
- (void)play;
- (void)pause;
@end
Now, in the player's play
method, the delegate gets the playerDidStartPlaying:
message. Any user of this class simply implements the delegation protocol without subclassing, and Player
the object remains generic. This is a very powerful technique that Apple uses heavily in their own frameworks. Sometimes, you want to organize different methods into different protocols, as UITableView does, which not only has delegates but also data sources.
Scheme: Category
Sometimes, you may want to extend the functionality of an object a little. arrayByRemovingFirstObject
Suppose, you want to extend a method to NSArray . You can put it into a class instead of creating a subclass. like this:
@interface NSArray (OBJExtras)
- (void)obj_arrayByRemovingFirstObject;
@end
When extending a class that you didn't create yourself with a class, it's a good practice to prefix the method. If you don't, someone else might implement the same method using the same technique. Then, if the behavior doesn't match, unexpected things can happen.
One of the dangers of using categories is that your project may end up using a lot of categories, and you can lose your overview. In this case, it might be simpler to create a custom class.
Scenario: Configuration Object
One mistake I've been making (now quickly realized) is that a class is created with a lot of abstract methods, and then a lot of subclasses override a specific method. For example, in a presentation application, you might have a Theme
class that has several properties like : backgroundColor
and font
, and some logic for placing things on the slides.
Then, for each theme, you create a Theme
subclass, override a method (eg: setup
), and configure the properties. There is no point in using subclasses directly. In this case, you can use configuration objects to make your code simpler. You can put shared logic in a Theme
class (eg: slide layout), but put the configuration in a simple object with only properties.
For example, a ThemeConfiguration
class has backgroundColor
and font
properties, and Theme
the class gets an instance of this class when it is initialized.
Scheme: Combination
The most powerful alternative to subclassing is composition. If you want to reuse existing code but don't share the same interface, composition is your "weapon" of choice. For example, suppose you are designing a cache class:
@interface OBJCache: NSObject
- (void)cacheValue:(id)value forKey:(NSString *)key;
- (void)removeCacheValueForKey:(NSString *)key;
@end
An easy way to do this is to subclass NSDictionary
and implement both methods by calling the dictionary methods.
@interface OBJCache: NSDictionary
However, this has several disadvantages. The fact that it is implemented with a dictionary should be an implementation detail. Now, anywhere a NSDictionary
type parameter is required, you can provide a OBJCache
value. If you want to switch to something completely different (for example, your own class library), you need to refactor a lot of code.
A good way is to store this dictionary in a private property (or instance variable) and expose only two cache
methods. Now, you can maintain the flexibility to modify the implementation at will as you gain more insights, and consumers of the class don't need to refactor.