在本教程的第一部分中,你学会了面向对象设计的基本概念:对象,继承以及模型-视图-控制器(MVC)模式。你初步完成了一个叫做 Vehicles 的程序,它帮助你更好的理解所学的这些概念。
在这第二部分中,你将学习多态性以及其它一些面向对象编程的关键概念:类工厂方法和单例。
如果你已经完成了本教程的前半部分,那就太好了!你可以在本教程中继续使用之前您所使用的工程。然而,如果你刚刚开始阅读本篇教程,你也可以从这里下载我们为你准备的第一部分完整工程。
多态性(Polymorphism)
关于多态的普通定义来源于它的希腊词根 – “Poly” 表示很多, “Morph” 表示形式。
在计算机科学中,这个词有特别的定义,依据 F
ree Online Dictionary of Computing网站的解释:
一个变量,它在可能指向一个对象,这个对象的类在编译时还未知,但是会在运行时根据实际指向的类执行相应的反馈。
这些定义最终可以归结为“一个对象同时可以成为不同的事物”。
Objective-C 中的多态性有一些子类型,但是其中最主要的两种类型,也是最常见的两种就是修饰模式和适配器模式。
修饰(Decorator)模式
苹果公司的基础文档 Cocoa 设计模式中有这样的解释:
修饰设计模式将额外的职责动态的附加给一个对象。修饰模式为用于拓展功能性的子类化提供了灵活的选择。就像子类化一样,修饰模式能让你不用修改原来的代码就能合并添加新的功能。 修饰类包含了一个被拓展行为类的对象。
在Objective-C中,一个修饰模式的最典型例子就是类别的使用。
类别是iOS中一种特别的类,它能让你在不继承一个类,也不需要修改这个类的源代码的情况下为这个类添加额外你所需要的方法。它主要被用来拓展iOS自带的UIKit组件。
类别与子类之间的区别非常的简单:类别能让你为一个存在的类添加新的方法,但是你不能修改已经存在的方法。你不能为一个类别添加新的特性或者实 例变量 – 你只能使用那些本来就存在的。如果你想添加新的特性或者实例变量,那你就要考虑使用继承来创建一个子类,并添加你想要添加的特性和方法了。
但是如果你不需要这样做呢?假如你只是需要将你经常使用的一些方法封装进UIKit对象呢?在这种情况下,类别就是你的最佳解决方案。
在你的练习应用中,你将为UIAlertView添加一个简便方法,以避免为一个简单的警告界面重复的书写 分配-初始化-显示 这些步骤。
实现修饰模式
打开 FileNewFileCocoa Touch, 并选择 Objective-C Category:
将 Convenience 作为类别名填入第一栏,在第二栏选择添加一个UIAlertView的类别:
一旦你创建完成这些文件,你就会发现 Xcode 给类别不同的文件名以区分这是一个类别文件,就像下面显示的这样:
这种 [原类名]+[类别名] 的形式同时指明了被修饰的类名和类别本身的名字。你甚至可以在同一个应用中为同一个类添加各种不同的类别;这样还能使这个类别在其它的应用中更容易被使用。
在类别中创建一个方法,就像为一个普通的类创建方法一样。因为你将要创建一个UIAlertView 的新的实例,而不是使用已经存在的实例,打开 UIAlertView+Convenience.h 文件, @interface 行之后添加如下方法声明:
1
2
|
// Shows a UIAlertView with the given title and message, and an OK button to dismiss it.
+ (void)showSimpleAlertWithTitle:(NSString *)title andMessage:(NSString *)message;
|
1
2
3
4
5
6
7
8
9
|
+ (void)showSimpleAlertWithTitle:(NSString *)title andMessage:(NSString *)message
{
UIAlertView *simpleAlert = [[UIAlertView alloc] initWithTitle:title
message:message
delegate:nil
cancelButtonTitle:@
"OK"
otherButtonTitles:nil];
[simpleAlert show];
}
|
这里所做事非常简单 — 你只是集成了一些你要重复使用的代码,它产生一个简单的警告窗口,上面带一个可以让窗口消失的取消按钮。
接着,打开 VehicleDetailViewController.m 文件,并添加如下导入语句:
1
|
#import "UIAlertView+Convenience.h"
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
-(IBAction)goForward
{
[UIAlertView showSimpleAlertWithTitle:@
"Go Forward"
andMessage:[self.detailVehicle goForward]];
}
-(IBAction)goBackward
{
[UIAlertView showSimpleAlertWithTitle:@
"Go Backward"
andMessage:[self.detailVehicle goBackward]];
}
-(IBAction)stopMoving
{
[UIAlertView showSimpleAlertWithTitle:@
"Stop Moving"
andMessage:[self.detailVehicle stopMoving]];
}
-(IBAction)makeNoise
{
[UIAlertView showSimpleAlertWithTitle:@
"Make Some Noise!"
andMessage:[self.detailVehicle makeNoise]];
}
|
编译并运行你的应用;选择一辆车以后,随意点击一个除了 “Turn…” 之外的按钮,你会看到根据不同车所显示的对应的消息。比如,如果你在不同车中都点击“Make Some Noise!” 按钮,你看到的将会是下面这样:
但是如果你要做一些更复杂的事情呢 – 比如你需要从你显示的 UIAlertView 中获取一些信息呢?这个时候,适配器模式和委托就要派上用场了。
适配器(Adapter)模式
再看苹果文档中的解释 Cocoa Fundamentals Guide:
适配器设计模式将一个类的接口转变为另外一种用户期望的接口。适配器让那些因为接口不适配而冲突的类能够一起工作。对目标对象的类实现了解耦。
协议是 Objective-C 中适配器的最主要的例子。它可以指定一些能被任何类所实现的方法。它们通常被用于 DataSource 和 Delegate 方法,但是也可以用于帮助两个完全不相关类之间的通信。
这种模式的优势在于只需要某个类声明它遵从这个协议,无论这个类是个模型或者视图又或是控制器都没关系。它只想知道在另外一个类里所发生的事,并为此实现所有需要的方法。
为了知道用户希望车所应转弯的角度,你就需要利用 UIAlertViewDelegate 协议以获取用户输入UIAlertView的数据。
实现适配器模式
打开 VehicleDetailViewController.h 并将它声明为遵从 UIAlertViewDelegate 协议,像下面这样将协议名加入一对括号中:
1
|
@interface VehicleDetailViewController : UIViewController <UIAlertViewDelegate>
|
如果要将上面这行语句翻译为中文,将会是:“这是一个VehicleDetailViewController 它是UIViewController 的子类,并遵从于 UIAlertViewDelegate 协议”。如果一个类遵从于多个协议,你只需要将它们一起列在括号中并用逗号分开就行了。
注意: 在一个类中实现某个特定的协议通常还被称为“遵从”这个协议。
你将用这些来实现一个获取用户期望转弯度数的机制。
打开 VehicleDetailViewController.m 将 turn 方法替换为如下的实现:
1
2
3
4
5
6
7
8
9
10
|
-(IBAction)turn
{
//Create an alert view with a single text input to capture the number of degrees
//to turn your vehicle. Set this class as the delegate so one of the delegate methods
//can retrieve what the user entered.
UIAlertView *turnEntryAlertView = [[UIAlertView alloc] initWithTitle:@
"Turn"
message:@
"Enter number of degrees to turn:"
delegate:self cancelButtonTitle:@
"Cancel"
otherButtonTitles:@
"Go!"
, nil];
turnEntryAlertView.alertViewStyle = UIAlertViewStylePlainTextInput;
[[turnEntryAlertView textFieldAtIndex:0] setKeyboardType:UIKeyboardTypeNumberPad];
[turnEntryAlertView show];
}
|
该方法创建了一个带输入框的 UIAlertView ,用来提示用户输入一个数字。
接下来,你需要为 UIAlertView 实例添加一个委托方法作为用户输入一个数字以后的回调函数。添加如下的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
#pragma mark - UIAlertViewDelegate method
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
//Note: Only one alert view will actually declare this class its delegate, so we can
// proceed without double-checking the alert view instance. If you have more than
// one alert view using the same class as its delegate, make sure you check which
// UIAlertView object is calling this delegate method.
if
(buttonIndex != alertView.cancelButtonIndex) {
//Get the text the user input in the text field
NSString *degrees = [[alertView textFieldAtIndex:0] text];
//Convert it from a string to an integer
NSInteger degreesInt = [degrees integerValue];
//Use the simple alert view to display the information for turning.
[UIAlertView showSimpleAlertWithTitle:@
"Turn"
andMessage:[self.detailVehicle turn:degreesInt]];
}
//else the user has cancelled, and we don't need to do anything.
}
|
以上的代码实现了一个选定的 UIAlertViewDelegate 中的一个方法,它用于监听UIAlertView中某个按钮被按下的事件。编译并运行你的程序;从列表里选择一辆车,点击 Turn 按钮,并输入一个需要转弯的角度,就像这样:
如果你点击了Cancel,将不会发生任何事,因为你在委托的实现中忽略了它。然而,如果你点击 Go!,前面那个 UIAlertView 将会消失,下面这个 UIAlertView 将会出现:
你的应用现在在功能上已经完整了。然而,你总会希望将你的代码写的更加优雅,更易于管理和扩展。是时候介绍另外两个面向对象的设计模式了,它们将大大简化你的编程工作。
额外的面向对象模式
尽管在面向对象编程中你可以运用大量的模式(事实上, Eli Ganem 已经发表了一篇相关的 教程),其中有两种对你的‘车’应用来说显得特别的有用:类工厂方法 和 单例。
它们都被广泛的运用于 iOS 开发,理解它们将有效的帮助你理解未来你作为一个iOS开发者所要接触到的大部分代码。
类工厂方法
类工厂的主要理念在于产生并返回一个特定类的实例对象,并在产生对象时尽可能的预填充数据。相比调用 alloc/init 再设置特性,使用类工厂的一个显而易见的好处就是,你的代码将会缩短很多。
这样,你就能使用一个方法来创建一个对象并初始化它的所有特性,而不需要用一个方法来创建对象,再用很多行代码来设置各种特性。与此同时,这种技术还有两个不太明显的好处。
其一,它强制了每个使用你的类的人必须提供你所需要的每一项数据,以创建一个功能完整的实例对象。鉴于你在本教程的前面部分所创建的那些对象,你可能也发现了,往往很容易就忘记了一个或两个特性的初始化。有了类工厂方法, 你将被强制关注你创建对象所需要的每一项特性。
其二,公认的减少了使用了
ARC 的代码将产生的问题,在 iOS 5之后类工厂方法将返回自动释放池对象,释放调用者而不必在以后再释放。你可能不需要担心这个问题,除非你需要兼容老的代码,但是这依旧是一个值得注意的问题。
实现车的类工厂方法
打开Vehicle.h 文件并声明如下类工厂方法,该方法的参数代表了一个车的所有基本参数:
1
2
|
//Factory Method
+ (instancetype)vehicleWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear powerSource:(NSString *)powerSource wheels:(NSInteger)numberOfWheels;
|
instancetype 类型是一个稍微安全版本的 id。一个 id 类型的参数或者返回值,将会接受任何 NSObject 的子类,instancetype 作为一个方法签名,它告诉你在这个实例被初始化后,你将收到的一定是这个类或者它的子类的实例。
注意: 更多关于instancetype 的介绍,请阅读 NSHipster
另外一件需要关注的事是有关于类工厂方法和继承:因为类工厂方法返回一个完全初始化的对象,所以在父类中你要小心的使用它们,它返回一个特殊类的对象。
打开 Vehicle.m 文件,添加如下类工厂方法的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#pragma mark - Factory method
+ (instancetype)vehicleWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear powerSource:(NSString *)powerSource wheels:(NSInteger)numberOfWheels;
{
//Use self in the superclass to ensure you're getting the proper return type for each of the subclasses.
Vehicle *newVehicle = [[self alloc] init];
//Set the provided values to the appropriate instance variables.
newVehicle.brandName = brandName;
newVehicle.modelName = modelName;
newVehicle.modelYear = modelYear;
newVehicle.powerSource = powerSource;
newVehicle.numberOfWheels = numberOfWheels;
//Return the newly created instance.
return
newVehicle;
}
|
这里的类工厂方法初始化了对象并设置了特性。因为车将会有子类,所以请确保你使用的是 [[self alloc] init] 而不是 [[Vehicle alloc] init]。这样, 像 Car 这样的子类也可以使用这个继承的类工厂方法来获取一个Car对象,而不是 Vehicle 对象。
注意: Quality Coding 网站有一篇深入分析此话题的文章 How to Botch Your Objective-C Factory Method。
实现汽车的类工厂方法
打开 Car.h 文件并声明如下的类工厂方法:
1
2
|
//Factory Method
+(Car *)carWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear powerSource:(NSString *)powerSource numberOfDoors:(NSInteger)numberOfDoors convertible:(BOOL)isConvertible hatchback:(BOOL)isHatchback sunroof:(BOOL)hasSunroof;
|
因为你需要的是有关于 Vehicle 类的所有信息,而不仅仅是轮子的个数,你将所有有关Vehicle 类的特性和带有Car-特点的特性作为方法的参数。
打开 Car.m 文件,将 init 方法替换为如下的类工厂方法实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#pragma mark - Factory Method
+(Car *)carWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear powerSource:(NSString *)powerSource numberOfDoors:(NSInteger)numberOfDoors convertible:(BOOL)isConvertible hatchback:(BOOL)isHatchback sunroof:(BOOL)hasSunroof
{
//Create the car object using the superclass factory method.
Car *newCar = [Car vehicleWithBrandName:brandName modelName:modelName modelYear:modelYear powerSource:powerSource wheels:4];
//Set the car-specific properties using the passed-in variables.
newCar.numberOfDoors = numberOfDoors;
newCar.isConvertible = isConvertible;
newCar.isHatchback = isHatchback;
newCar.hasSunroof = hasSunroof;
//Return the fully instantiated Car object.
return
newCar;
}
|
注意依据以往的经验法则来说,你并不一定必须从 init 方法和类工厂方法中二选一;然而在本例中你将不会再直接使用 init 方法,取而代之的类工厂方法将负责所有init 方法曾经所做的事。这种管理旧代码的方法是合理的,因为你将不会再需要它们。
接下来,打开 VehicleListTableViewController.m 并更新 setupVehicleArray 方法以对你创建的每个汽车类使用新的类工厂方法,就像下面这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
//Create a car.
Car *mustang = [Car carWithBrandName:@
"Ford"
modelName:@
"Mustang"
modelYear:1968
powerSource:@
"gas engine"
numberOfDoors:2 convertible:YES hatchback:NO sunroof:NO];
//Add it to the array
[self.vehicles addObject:mustang];
//Create another car.
Car *outback = [Car carWithBrandName:@
"Subaru"
modelName:@
"Outback"
modelYear:1999
powerSource:@
"gas engine"
numberOfDoors:5 convertible:NO hatchback:YES sunroof:NO];
//Add it to the array.
[self.vehicles addObject:outback];
//Create another car
Car *prius = [Car carWithBrandName:@
"Toyota"
modelName:@
"Prius"
modelYear:2007
powerSource:@
"hybrid engine"
numberOfDoors:5 convertible:YES hatchback:YES sunroof:YES];
//Add it to the array.
[self.vehicles addObject:prius];
|
编译并运行你的应用;一切看起来就像往常一样,但是你知道实际上在创建你的 Vehicle 数组的时候,你的代码量大大减少了。现在你可以将这种模式应用到 Motorcycle 和 Truck 类上了。
实现摩托车的类工厂方法
在 Motorcycle.h 文件中,添加如下新的类工厂方法声明:
1
2
|
//Factory Method
+(Motorcycle *)motorcycleWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear engineNoise:(NSString *)engineNoise;
|
在此情况下,你将添加一些特定的参数以创建 Motorcycles的实例。
现在打开 Motorcycle.m 并将 init 方法替换为你的类工厂方法的实现,就像下面这样:
1
2
3
4
5
6
7
8
9
10
11
12
|
#pragma mark - Factory Method
+(Motorcycle *)motorcycleWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear engineNoise:(NSString *)engineNoise
{
//Create a new instance of the motorcycle with the basic properties by calling the Factory
//method on the superclass.
Motorcycle *newMotorcycle = [Motorcycle vehicleWithBrandName:brandName modelName:modelName modelYear:modelYear powerSource:@
"gas engine"
wheels:2];
//Set the Motorcycle-specific properties.
newMotorcycle.engineNoise = engineNoise;
return
newMotorcycle;
}
|
实现卡车的类工厂方法
打开 Truck.h 并添加如下类工厂方法声明:
1
2
|
//Factory Method
+(Truck *)truckWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear powerSource:(NSString *)powerSource wheels:(NSInteger)numberOfWheels cargoCapacityCubicFeet:(NSInteger)cargoCapacityCubicFeet;
|
就像之前一样,你将带入一些特定于你新的 Vehicle 实例的参数 — 在此情况下, 卡车。
打开 Truck.m, 添加如下类工厂方法的实现 (本例中没有可以替代的 init 方法):
1
2
3
4
5
6
7
8
9
10
11
|
#pragma mark - Factory Method
+(Truck *)truckWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear powerSource:(NSString *)powerSource wheels:(NSInteger)numberOfWheels cargoCapacityCubicFeet:(NSInteger)cargoCapacityCubicFeet
{
//Create a new instance using the superclass's factory method.
Truck *newTruck = [Truck vehicleWithBrandName:brandName modelName:modelName modelYear:modelYear powerSource:powerSource wheels:numberOfWheels];
newTruck.cargoCapacityCubicFeet = cargoCapacityCubicFeet;
//Return the newly created truck instance.
return
newTruck;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
//Add a motorcycle
Motorcycle *harley = [Motorcycle motorcycleWithBrandName:@
"Harley-Davidson"
modelName:@
"Softail"
modelYear:1979 engineNoise:@
"Vrrrrrrrroooooooooom!"
];
//Add it to the array.
[self.vehicles addObject:harley];
//Add another motorcycle
Motorcycle *kawasaki = [Motorcycle motorcycleWithBrandName:@
"Kawasaki"
modelName:@
"Ninja"
modelYear:2005 engineNoise:@
"Neeeeeeeeeeeeeeeeow!"
];
//Add it to the array
[self.vehicles addObject:kawasaki];
//Create a truck
Truck *silverado = [Truck truckWithBrandName:@
"Chevrolet"
modelName:@
"Silverado"
modelYear:2011 powerSource:@
"gas engine"
wheels:4 cargoCapacityCubicFeet:53];
[self.vehicles addObject:silverado];
//Create another truck
Truck *eighteenWheeler = [Truck truckWithBrandName:@
"Peterbilt"
modelName:@
"579"
modelYear:2013 powerSource:@
"diesel engine"
wheels:18 cargoCapacityCubicFeet:408];
[self.vehicles addObject:eighteenWheeler];
|
编译并运行你的应用;一切还是像往常一样工作。然而,你缩短并简化了你的代码,将你经常需要重复使用的代码转移到可以重用的类工厂方法中去。
类工厂方法不仅使用方便,还大大降低了再不经意间漏掉一个特性的可能性。它们可能很常见,像 NSString 的 stringWithFormat: 或 UIButton 的buttonWithType: – 现在你已经将它们添加到你自己的车类及其它的子类中!
单例模式
类工厂方法中一种很有用也很特别的方法就是 单例。它确保了一个类的一个特定实例永远只被初始化一次。
这对于那些只需要一个实例的东西来说是很棒的 — 比如, UIApplication 单例 sharedApplication — 或者那些初始化开销很大的类,又或者虽然所存的数据很小但是你的应用从始至终都需要获取并更新它。
在 Vehicles 应用中,你会发现有一个数据可能我们从始至终都需要接触或者更新的:车的列表。这个列表也违反了 MVC 原则,因为它让 VehicleListTableViewController 来管理它的创建和生存。通过将车的列表移交到它自己的单例类中去,你的代码在将来会更具可拓展性。
打开 FileNewFileObjective-C Class 并创建一个名为VehicleList 的 NSObject 的子类。打开 VehicleList.h 并添加如下类方法声明和一个用于存储车的数组特性:
1
2
3
4
|
//The list of vehicles.
@property (nonatomic, strong) NSArray *vehicles;
//Singleton Instance
+ (VehicleList *)sharedInstance;
|
接着,打开 VehicleList.m 并添加如下单例类工厂方法的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
+ (VehicleList *)sharedInstance
{
//Declare a static instance variable
static VehicleList *_vehicleList = nil;
//Create a token that facilitates only creating this item once.
static dispatch_once_t onceToken;
//Use Grand Central Dispatch to create a single instance and do any initial setup only once.
dispatch_once(&onceToken, ^{
//These are only invoked the onceToken has never been used before.
_vehicleList = [[VehicleList alloc] init];
_vehicleList.vehicles = [VehicleList initialVehicleList];
});
//Returns the shared instance variable.
return
_vehicleList;
}
|
注意你将 _vehicleList 实例变量和 onceToken GCD 标记声明为 static 变量。这意味着这个变量将存在于这个应用的整个声明周期中。这将从两个方面有助于单例的创建:
1.相比检查 _vehicleList 实例变量是否为空, GCD 能更快的检测 onceToken 是否被执行过了,以相应的决定是否需要创建 _vehicleList 实例。使用 GCD 来执行这个检测操作同时也是线程安全的,因为 dispatch_once 确保了当它被从多线程调用的时候,下一个对象只有在当前线程结束之后才会被允许创建实例。
2._vehicleList 实例不会被意外的覆盖掉,因为静态变量只能被初始化一次。如果任何人在你的_vehicleList变量被初始化后,不经意间另外调用了一次 [[VehicleList alloc] init] ,将不会对你现有的 VehicleList 对象有任何效果。
接下来,你需要将车的创建工作从 VehicleListTableViewController 转移到 VehicleList 类中。
首先,在VehicleList.m文件的头部导入 Car, Motorcycle, 和 Truck 类:
1
2
3
|
#import "Car.h"
#import "Motorcycle.h"
#import "Truck.h"
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
+ (NSArray *)initialVehicleList
{
//Initialize mutable array.
NSMutableArray *vehicles = [NSMutableArray array];
//Create a car.
Car *mustang = [Car carWithBrandName:@
"Ford"
modelName:@
"Mustang"
modelYear:1968
powerSource:@
"gas engine"
numberOfDoors:2 convertible:YES hatchback:NO sunroof:NO];
//Add it to the array
[vehicles addObject:mustang];
//Create another car.
Car *outback = [Car carWithBrandName:@
"Subaru"
modelName:@
"Outback"
modelYear:1999
powerSource:@
"gas engine"
numberOfDoors:5 convertible:NO hatchback:YES sunroof:NO];
//Add it to the array.
[vehicles addObject:outback];
//Create another car
Car *prius = [Car carWithBrandName:@
"Toyota"
modelName:@
"Prius"
modelYear:2007
powerSource:@
"hybrid engine"
numberOfDoors:5 convertible:YES hatchback:YES sunroof:YES];
//Add it to the array.
[vehicles addObject:prius];
//Add a motorcycle
Motorcycle *harley = [Motorcycle motorcycleWithBrandName:@
"Harley-Davidson"
modelName:@
"Softail"
modelYear:1979 engineNoise:@
"Vrrrrrrrroooooooooom!"
];
//Add it to the array.
[vehicles addObject:harley];
//Add another motorcycle
Motorcycle *kawasaki = [Motorcycle motorcycleWithBrandName:@
"Kawasaki"
modelName:@
"Ninja"
modelYear:2005 engineNoise:@
"Neeeeeeeeeeeeeeeeow!"
];
//Add it to the array
[vehicles addObject:kawasaki];
//Create a truck
Truck *silverado = [Truck truckWithBrandName:@
"Chevrolet"
modelName:@
"Silverado"
modelYear:2011
powerSource:@
"gas engine"
wheels:4 cargoCapacityCubicFeet:53];
[vehicles addObject:silverado];
//Create another truck
Truck *eighteenWheeler = [Truck truckWithBrandName:@
"Peterbilt"
modelName:@
"579"
modelYear:2013
powerSource:@
"diesel engine"
wheels:18 cargoCapacityCubicFeet:408];
[vehicles addObject:eighteenWheeler];
//Sort the array by the model year
NSSortDescriptor *modelYear = [NSSortDescriptor sortDescriptorWithKey:@
"modelYear"
ascending:YES];
[vehicles sortUsingDescriptors:@[modelYear]];
return
vehicles;
}
|
以上方法创建或者重置车的列表,并能在任何时候被调用。
你会发现大部分代码都是从VehicleListTableViewController转移过来的,但是现在所有的车都被添加到新创建的一个 局部变量 vehicles 数组,而不是 VehicleListTableViewController的 self.vehicles。
现在你可以回到 VehicleListTableViewController.m 并移除三样我们不再需要的内容:
1.删除整个 setupVehiclesArray 方法,并删除其在awakeFromNib中的调用。
2.删除 vehicles 实例变量并删除其在 awakeFromNib中的初始化调用。
3.删除 Car.h, Motorcycle.h, 和 Truck.h 的#imports语句。
你的VehicleListTableViewController的私有接口以及 awakeFromNib 的实现现在看起来应该是这样的:
1
2
3
4
5
6
7
8
9
10
|
@interface VehicleListTableViewController ()
@end
@implementation VehicleListTableViewController
#pragma mark - View Lifecycle
- (void)awakeFromNib
{
[
super
awakeFromNib];
//Set the title of the View Controller, which will display in the Navigation bar.
self.title = @
"Vehicles"
;
}
|
你将注意到Xcode 提示你有三个错误,因为你有三个地方使用了 vehicles 特性以填充 UITableViewDataSource 和 segue 处理方法。你需要用你新的单例取代它们。
首先,在 VehicleListTableViewController.m 文件头部导入 VehicleList 类,以便你可以使用单例:
1
|
#import "VehicleList.h"
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return
[[VehicleList sharedInstance] vehicles].count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@
"Cell"
forIndexPath:indexPath];
Vehicle *vehicle = [[VehicleList sharedInstance] vehicles][indexPath.row];
cell.textLabel.text = [vehicle vehicleTitleString];
return
cell;
}
#pragma mark - Segue handling
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if
([[segue identifier] isEqualToString:@
"showDetail"
]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
Vehicle *selectedVehicle = [[VehicleList sharedInstance] vehicles][indexPath.row];
[[segue destinationViewController] setDetailVehicle:selectedVehicle];
}
}
|
编译并运行你的应用;你会看到和之前一样的列表,但是你会因此而睡得更安稳,因为你知道应用背后的代码更加干净,简洁也更易于扩展了。
基于以上这些改变,你将能够轻易的为这个列表添加新的Vehicles。比如,你想添加一个新的 UIViewController 来允许用户添加他们自己的 Vehicle, 你就只需要将它添加到单例的 Vehicles 数组。
又或者,你想让用户能够编辑Vehicle对象,你能确保所有的数据都被存储并返回,而不需要为VehicleListViewController实现一个 delegate。
有关于单例有一件事特别需要注意:它们将在你的应用的整个生命周期中生存着,因此你不应该给它们加载太多数据。它们在轻量级的数据存储以及使对象们对整个应用范围内可见是很棒的。
如果你需要存储大量的数据,那么你可能需要寻找一些更加健壮的工具来帮助存储和检索数据,就像 Core Data 。
接下来做什么呢?
在一个单独的应用中,你运用基本对象,继承,MVC模式,多态性还有单例和类工厂方法创建了一个干净的,面向对象的应用程序。你可以通过
这里回顾本工程的完成版代码。
有关于单例的更多介绍,你可以阅读 Mike Ash的一篇极棒的文章
the Care and Feeding of Singletons。
如果你需要了解更多有关于面向对象模式的信息,Eli Ganem 写了一篇很好的教程,叫做
iOS Design Patterns , 它回顾了一些本教程中提到的内容,接着它介绍了一些更加高级的设计模式,你可以用来构建更加优雅的代码。
如果你有任何问题,欢迎在下面的评论中留言!