Windows Phone 7 for iPhone Developers - 章节摘录: 面向对象编程





0/5 (0投票)
本章将介绍面向对象编程 (OOP) 的一些核心概念,
Kevin Hoffman 由 Addison-Wesley Professional 出版 ISBN-10: 0-672-33434-8 ISBN-13: 978-0-672-33434-4 |
当然,并非所有好的程序都是面向对象的,也并非所有面向对象的程序都是好的。
Bjarne Stroustrup
无论您计划编写哪种类型的应用程序,或者计划在哪种平台上编写,您都需要运用和理解一些基本的面向对象原则和概念。
大多数开发人员已经熟悉了诸如继承、基于契约(接口)的编程、成员、方法、封装等概念。然而,这些概念在不同的平台上有着不同的实现,甚至不同的术语。本章将帮助您理清这些概念,并向您展示面向对象编程的核心原则在 iOS 和 WP7 中的实现方式。
为什么选择面向对象编程?
如果您正在阅读这本书,您可能不需要被说服面向对象编程是一件好事。然而,正如 Bjarne Stroustrup 在引言中所说——并非所有的面向对象编程都是好的。就像任何工具一样,如果使用不当,它可能会把一个初衷良好的项目搞得一团糟。
我们创建类是为了将逻辑上相关的数据和行为组合在一起,这些数据和行为都旨在执行特定的任务。当这些类编写得当,它们就能体现出面向对象编程的优势:提高代码复用性,创建可测试、可靠、可预测且易于构建、易于维护和易于修改的应用程序。
在移动设备上,我们有类来封装从读取 GPS 坐标到显示文本、接受输入等各种事物的相关信息和行为。如果没有一套精心设计的类来抽象设备的各种功能,我们将永远无法快速可靠地为该设备生成软件。
同样的情况也适用于我们构建的应用程序和编写的代码。如果没有我们自己创建可重用类的能力,即使是为移动应用程序编写的相对少量的代码,也将无法维护,也无法按时按预算交付应用程序。
构建一个类
在本章的其余部分,我们将逐步构建一个具有越来越强功能的类。目标是引导您完成为 iPhone 和 Windows Phone 7 设计和构建一个功能齐全的类的过程,以便您可以进行比较和对比,并将您现有的 iPhone 技能(如果有)映射到 C# 中类的创建方式。
为了演示类的创建,我们需要一些要建模的行为和数据。在本章的其余部分,我们将开发一个可能出现在游戏中的类。由于我们是遵循更明智的人的建议的优秀程序员,这个类将仅模拟行为、逻辑和数据封装,而与用户界面无关。简而言之,这个类是一个纯粹的模型,而不是一个视图。
我们将作为起点构建的类是模拟一个可以在假设的移动游戏中参与战斗的对象的逻辑、数据封装和行为的类。我们将这个类命名为 `Combatant`(战斗者)。
首先,我们将创建一个空类。在列表 3.1 和 3.2 中,您分别可以看到 Objective-C 头文件(.h)和实现文件(.m)的代码。列表 3.3 显示了 C# 中这个相同的(完全空的,到目前为止完全无用的)类。
列表 3.1 Combatant.h
@interface Combatant : NSObject {
}
@end
在前面的代码中,您可以看到一个 Objective-C 头文件在刚使用 Xcode 模板创建空类后所具有的典型内容。这里没有什么特别值得注意的。在学习 C# 时,最重要的是要记住 C# 类不会将代码分离成头文件和实现文件。
列表 3.2 Combatant.m
#import "Combatant.h"
@implementation Combatant
@end
前面的代码是 Objective-C 类的实现文件。所有实际的实现代码都放在这里,而头文件用于让引用该类的其他代码知道该类的行为方式以及它公开的数据。
列表 3.3 Combatant.cs
using System;
namespace Chapter3
{
public class Combatant
{
}
}
最后,列表 3.3 显示了用 C# 实现的同一个空类。如果您通过向 WP7 项目添加一个类来创建自己的空类,您可能会注意到很多额外的 `using` 语句。为保持清晰,我在列表 3.3 中删除了它们。这里没有什么特别的,我们还没有进入任何有趣的部分。本节的目的是帮助您理解 iOS 和 C# 中类的存储方式之间的差异,在本章的后续内容中,我们将逐步进行更多面向对象编程的比较。
封装数据
任何类最重要的功能之一就是封装数据。这是面向对象编程最初使用时的主要原因之一。
数据封装涉及几个关键概念,每个编程语言的实现方式都不同:
- 存储成员变量
- 提供访问这些变量的包装器
- 为成员变量添加范围和安全性(例如,只读)
Objective-C 在早期对现代编程语言称之为“属性”的功能支持有限,但现在已具备完整且强大的数据封装功能,可与 C# 相媲美。
要了解数据封装的实际应用,最好的方法是看一些包含成员变量的代码。在接下来的几个代码列表中,我们将为 `Combatant` 类添加成员变量,以模拟战斗者的一些我们知道游戏引擎可能需要的属性,例如生命值、护甲等级、伤害等级以及其他一些细节。
列表 3.4 显示了我们在 iOS 应用程序的头文件中可能如何声明属性。
列表 3.4 带有成员变量的 Combatant.h
@interface Combatant : NSObject {
}
@property(nonatomic, assign) int maxHitPoints;
@property(nonatomic, assign) int currentHitPoints;
@property(nonatomic, assign) int armorClass;
@property(nonatomic, assign) int damageClass;
@property(nonatomic, retain) NSString *combatantName;
这对大多数 iOS 开发者来说应该相当熟悉。我们有两个基于 `int` 的属性来存储生命值和护甲等级等值,还有一个字符串属性用于存储战斗者姓名。使用 `@property` 语法,我们可以指定 `combatantName` 属性的自动生成访问器将自动保留字符串。在 C# 中,我们无需担心像 Objective-C 那样保留字符串以防止意外释放。列表 3.5 显示了自动合成列表 3.4 中声明的属性的 getter 和 setter 访问器的实现。
列表 3.5 带有成员变量的 Combatant.m
#import "Combatant.h"
@implementation Combatant
@synthesize maxHitPoints, currentHitPoints, armorClass, damageClass, combatantName;
@end
在实现(.m)文件中,我使用了 `@synthesize` 关键字来指示 Xcode 自动生成这些属性的访问器。Xcode 如何做到这一点由头文件中 `@property` 声明中的信息决定。如果我愿意,我可以手动覆盖此自动生成过程,并为特定属性提供我自己的访问器,甚至替换 getter 或 setter 访问器。
列表 3.6 显示了 `Combatant` 类的 C# 版本,包括属性 get 和 set 访问器的自动实现。
列表 3.6 带有成员变量的 Combatant.cs
using System;
using System.Net;
using System.Windows;
namespace Chapter3
{
public class Combatant
{
public int MaxHitpoints { get; set; }
public int CurrentHitPoints { get; set; }
public int ArmorClass { get; set; }
public int DamageClass { get; set; }
public string Name { get; set; }|
public Point Location { get; set; }
public int HitPointPercent
{
get
{
double pct =
(double)CurrentHitPoints / (double)MaxHitpoints;
return (int)Math.Floor(pct * 100);
}
}
}
}
列表 3.6 显示了基本的 `Combatant` 类,其中包含几个公共属性,使用了 C# 中可用的快捷语法。这种快捷语法允许开发人员将 get 和 set 实现留空。当这些访问器留空时,编译器将自动生成一个私有成员变量来支持公共属性,并代表开发人员完成所有必需的连接工作。这种“自动属性”在 iOS 中也可用。
每个属性名称前面的 `public` 关键字表示该属性在任何程序集中的任何类都可以可见和访问。如果将关键字更改为 `internal`,则属性仅对该程序集中的其他类可用。最后,`private` 关键字表示只有该类可以访问这些成员。当我们在本章后面讨论继承和对象层次结构时,您将学到 `protected` 关键字。
最后一个属性 `HitPointPercent`(生命值百分比)展示了如何创建一个只读属性,该属性根据其他属性动态计算其值。这展示了数据封装的另一个例子,因为它允许开发人员将计算的复杂性隐藏在一个简单的属性后面。在本例中,这是一个简单的百分比计算,但您可以想象这种技术在模拟具有详细规则和逻辑的复杂业务对象时是多么有用。另请注意,我们需要在除法计算中将每个整数值转换为浮点值;否则,`/` 运算符将假定为整数除法并返回 0 而不是小数。
添加行为
现在您已经有了一个封装数据的类,您想为其添加行为。类中的行为以方法的形式添加,方法是在类的实例范围内执行的函数。
Objective-C 中的方法通常在其签名在头文件(.h)中定义,而实现定义在实现文件(.m)中。在 C# 中,方法直接在类上定义,没有用于公开方法签名的头文件。
我们想为 `Combatant` 类添加的一些行为可能包括攻击另一个战斗者和移动。列表 3.7 至 3.9 说明了我们如何为类添加方法来赋予类一些行为。一个好的通用规则是,将成员(或属性)视为模型上的名词,而行为(或方法)应视为模型上的动词。
列表 3.7 带有行为的 Combatant.h
@interface Combatant : NSObject {
}
- (void)attack:(Combatant *)target;
@property(nonatomic, assign) int maxHitPoints;
@property(nonatomic, assign) int currentHitPoints;
@property(nonatomic, assign) int armorClass;
@property(nonatomic, assign) int damageClass;
@property(nonatomic, retain) NSString *combatantName;
列表 3.7 显示了 `Combatant` 类的头文件,包括属性声明以及 `Attack` 方法的方法签名。
列表 3.8 带有行为的 Combatant.m
#import "Combatant.h"
@implementation Combatant
@synthesize maxHitPoints, currentHitPoints, armorClass, damageClass, combatantName;
- (void)attack:(Combatant *)target
{
// obviously this should be more complicated...
target.currentHitPoints -= rand() % 20;
}
@end
列表 3.8 显示了 `Attack` 方法的 Objective-C 实现。此方法作用于另一个战斗者的给定实例,以允许其对目标造成伤害。在此处重要的是要注意,在列表 3.8 的 Objective-C 代码和列表 3.9 的 C# 代码中,两个类都使用封装的访问器来操作其他对象;它们不直接修改内部变量。
列表 3.9 带有行为的 Combatant.cs
using System;
using System.Net;
using System.Windows;
using System.Diagnostics;
namespace Chapter3
{
public class Combatant
{
public int MaxHitpoints { get; set; }
public int CurrentHitPoints { get; internal set; }
public int ArmorClass { get; set; }
public int DamageClass { get; set; }
public string Name { get; set; }
public Point Location { get; private set; }
public int HitPointPercent
{
get
{
double pct =
(double)CurrentHitPoints / (double)MaxHitpoints;
return (int)Math.Floor(pct * 100);
}
}
public void MoveTo(Point newLocation)
{
this.Location = newLocation;
Debug.WriteLine("Combatant {0} just moved to ({1},{2})",
this.Name,
this.Location.X,
this.Location.Y);
}
public void Attack(Combatant target)
{
Random r = new Random();
// obviously oversimplified algorithm...
int damage =
(this.DamageClass - target.ArmorClass) * r.Next(20);
target.CurrentHitPoints -= damage;
}
}
}
在列表 3.9 中,对某些类成员的访问修饰符有几个小的改动,并且我们实现了 `Attack` 和 `MoveTo` 方法。您可能会注意到,`CurrentHitPoints`(当前生命值)属性的 `set` 访问器的访问修饰符现在是 `internal`。这意味着只有 `Combatant` 相同程序集内的类才能修改该属性。这允许您的“游戏引擎”自由调整战斗者的生命值,但不允许核心引擎外部的代码直接修改该数据。这强制所有对生命值的更改只能通过授权的途径进行。
此外,`Location`(位置)属性现在具有 `private` 访问修饰符。这意味着只有 `Combatant` 类本身可以修改其自己的位置。这强制对 `Combatant` 位置的更改必须通过 `MoveTo` 方法进行,这是移动 `Combatant` 的唯一可接受方式。
我在这里提到这些是因为 C# 对方法和成员的访问修饰符具有比 Objective-C 更精细粒度的控制,允许您更严格地控制哪些代码可以影响某些数据,哪些不能。虽然这可能看起来是一个不重要的细节,但当您编写供其他开发人员使用的代码时,它变得极其重要。通过防止对您的成员变量和属性进行意外更改,可以消除一整类“意外的副作用”错误。
继承
在本章的这一部分,我们将研究如何使用继承来创建 `Combatant` 类的特殊派生类。例如,我们可能想创建一个不能移动的特定类型的战斗者,例如自动炮塔或固定大炮。另一种我们可能想创建的是在它们速度很快的情况下获得额外攻击的战斗者。最后,我们可能想创建一个完全不寻常的东西,比如一个醉汉攻击者,他一次造成的伤害永远不会超过一点。
图 3.1 显示了我们想要从现有的 `Combatant` 类创建的类层次结构图。如您所见,我们想创建三个新类:
ReallyDangerousCombatant (非常危险的战斗者)
StationaryCombatant (静态战斗者)
DrunkenCombatant (醉汉战斗者)
在 C# 或 Objective-C 中构建这些类相当简单。C# 为我们提供了对继承类型可以做什么和可以看到什么更精细的控制,因此我们将比在 Objective-C 中更多地使用访问修饰符。
在列表 3.10 和 3.11 中,我展示了一个名为 `ReallyDangerousCombatant` 的示例派生类,用 Objective-C 编写,它造成双倍伤害。这是最基本的继承形式——创建一个子类,其行为超出了父类的范围。
列表 3.10 ReallyDangerousCombatant.h
#import "Combatant.h"
@interface ReallyDangerousCombatant : Combatant {
}
@end
以及“非常危险”的战斗者类的实现
列表 3.11 ReallyDangerousCombatant.m
#import "ReallyDangerousCombatant.h"
@implementation ReallyDangerousCombatant
- (void)attack:(Combatant *)target
{
[super attack:target];
target.currentHitPoints -= 12;
}
在列表 3.11 中,您可以看到,非常危险的战斗者首先调用其父类(由 `super` 关键字指示)来攻击目标。然后它对自己造成的伤害。由于继承层次结构,这个非常危险的实现造成的伤害将始终比普通战斗者多 12 点。
为了节省篇幅并花更多时间关注这些类的 C# 实现,我将不包括 `DrunkenCombatant.h`、`DrunkenCombatant.m`、`StationaryCombatant.h` 和 `StationaryCombatant.m` 的列表。以下三个列表显示了新派生类的 C# 实现。
列表 3.12 ReallyDangerousCombatant.cs
namespace Chapter3
{
public class ReallyDangerousCombatant : Combatant
{
public override void Attack(Combatant target)
{
base.Attack(target);
// attack again for good measure!
base.Attack(target);
}
}
}
列表 3.13 展示了如何使用继承和子类来创建一个战斗者,这个战斗者非常醉,以至于无法赢得战斗,并且在移动到游戏指定的位置时非常困难。
列表 3.13 DrunkenCombatant.cs
using System;
using System.Windows;
namespace Chapter3
{
public class DrunkenCombatant : Combatant
{
public override void Attack(Combatant target)
{
target.CurrentHitPoints -= 1; // never do any real damage
}
public override void MoveTo(Point newLocation)
{
Random r = new Random();
Point realLocation =
new Point(r.NextDouble() * 30, r.NextDouble() * 30);
this.Location = realLocation;
}
}
}
现在让我们来看看如何使用继承来创建一个完全不动弹的战斗者(例如,一个固定炮塔)。
列表 3.14 StationaryCombatant.cs
using System.Windows;
namespace Chapter3
{
public class StationaryCombatant : Combatant
{
public override void MoveTo(Point newLocation)
{
// do nothing
}
}
}
无论游戏引擎多少次要求该战斗者移动,它都不会做出任何响应。
继承与基于数据的切换 - 这是一个至今仍在争论的问题,无论您的平台或语言选择如何,只要它支持面向对象编程。以 `StationaryCombatant`(静态战斗者)为例。我们构建了一个子类,这样每次要求它移动时,它都会简单地拒绝。另一种选择可能是创建一个名为 `IsStationary` 的布尔属性。然后基类可以在其 `Move` 方法中检查 `IsStationary` 属性的状态。这样就可以避免为静态对象创建一个完整的子类。
起初这看起来是一个更简单的解决方案。但这只是滑坡的开始。很快,您的简单基类就会变成一个装满属性和数据的垃圾箱——一个巨大的存储桶,其中包含可能只有 1% 的对象实例使用的信息。这仅仅是麻烦的开始。
现在,您的简单 `Move` 方法已经变得冗长,并且充满了巨大的 `if` 语句。在许多情况下,逻辑会嵌套起来,几乎无法阅读。当有人去修改您的 `Move` 方法时,它可能会破坏您类的特殊实例的功能(例如,对于 `IsStationary` 为 `true` 的 `Combatant`)。许多设计模式都会被这些巨大的 `if` 语句破坏,但为了在本章中保持简单,我不会在这里列出它们的名字或定义。
总而言之:如果您可以通过继承和接口(下一节讨论)来解决您的特殊化问题,那么这通常比在一个“膨胀大师”类中填充过多的属性和逻辑要干净、可维护和可靠得多。
使用契约编程
契约与类实现不同。契约仅定义特定类的要求;它实际上并不控制类的实现。继续战斗者类比:`Combatant` 基类定义了所有子类都可以继承的行为。`Combatant` 契约定义了任何想要称为“战斗者”的类必须实现的行为和数据。
让我们举一个例子,假设我们仍在开发游戏引擎。如果我们采用直接的继承层次结构,我们可能会限制我们可以模拟的交互类型。假设单继承(Objective-C 和 C# 都是如此),任何可以对玩家造成伤害(通过 `Attack` 方法)的类都必须继承自 `Combatant`。这给我们带来了一个问题:那些具有独特继承层次结构但不能继承自 `Combatant` 的复杂非活动对象呢?
假设玩家走在一条小巷里,被一辆汽车撞到了。车辆在这个游戏中可能需要自己的继承层次结构,可能从 `Vehicle` 或 `MovingObject` 等基类开始。鉴于我们没有多重继承的能力,我们如何才能让非战斗者对象对玩家造成伤害而不弄乱 `Combatant` 对象层次结构?答案是契约。
在 Objective-C 中,契约被称为协议;在 C# 中,它们被称为接口,但它们服务于完全相同的目的。契约定义了某个类必须实现的所需属性或方法的最小集合。它们不强制对继承层次结构施加任何限制。在这里,记住两个类,即使它们拥有完全不同的继承层次结构,也可以实现同一个接口,这一点至关重要。
所以,让我们来看看 `Combatant` 类。`Attack` 方法做了两件事(这可能给我们一个可以开始重构的线索):它计算要对对手造成多少伤害,然后它要求另一个战斗者承受该伤害。如果我们把实际造成伤害的功能剥离出来,并将其作为 `ITakesDamage`(承受伤害)接口的要求,我们就可以在游戏引擎中获得真正的灵活性。这个接口要求任何实现该接口的类都必须实现一个名为 `TakeDamage` 的方法。
列表 3.15 显示了 C# 中的 `ITakesDamage` 接口,列表 3.16 显示了新的 `Combatant` 类,该类已重构,将造成伤害的关注点分离出来,使其成为满足接口要求的任务。
列表 3.15 ITakesDamage.cs
namespace Chapter3
{
public interface ITakesDamage
{
void TakeDamage(int hitPoints);
}
}
列表 3.16 显示了重构后实现接口的 Combatant 类。
列表 3.16 Combatant.cs (重构为实现 IDoesDamage)
using System;
using System.Net;
using System.Windows;
using System.Diagnostics;
namespace Chapter3
{
public class Combatant : ITakesDamage
{
public int MaxHitpoints { get; set; }
public int CurrentHitPoints { get; private set; }
public int ArmorClass { get; set; }
public int DamageClass { get; set; }
public string Name { get; set; }
public Point Location { get; protected set; }
public Combatant()
{
this.CurrentHitPoints = this.MaxHitpoints;
}
public int HitPointPercent
{
get
{
double pct =
(double)CurrentHitPoints / (double)MaxHitpoints;
return (int)Math.Floor(pct * 100);
}
}
public virtual void MoveTo(Point newLocation)
{
this.Location = newLocation;
Debug.WriteLine("Combatant {0} just moved to ({1},{2})",
this.Name,
this.Location.X,
this.Location.Y);
}
public virtual void Attack(Combatant target)
{
Random r = new Random();
int damage =
(this.DamageClass - target.ArmorClass) * r.Next(20);
target.TakeDamage(damage);
}
public void TakeDamage(int hitPoints)
{
this.CurrentHitPoints -= hitPoints;
}
}
}
Combatant 类中的新 `Attack` 方法现在确定了要造成的伤害量,然后调用 `TakeDamage` 方法来影响目标。现在,Combatant 类不再是游戏引擎中唯一可能受到伤害的东西(任何实现 `ITakesDamage` 的东西现在都可以受到伤害),我们可以创建像 PincushionTarget(如列表 3.17 所示)这样的类,它可以被玩家伤害,但本身不是战斗者。
列表 3.17 PincushionTarget.cs
public class PincushionTarget : ITakesDamage
{
void TakeDamage(int hitPoints)
{
// take points out of pincushion target
}
}
供参考,列表 3.18 显示了协议定义在 Objective-C 中可能是什么样子。Objective-C 不使用大写的“I”作为前缀命名约定,而是使用“Protocol”一词作为后缀。要在 Objective-C 中实现类似的目标,我们会创建一个名为 `TakesDamageProtocol` 的协议,如列表 3.19 所示。我向您展示这一点是因为协议在 iOS 和 UIKit 中被广泛使用,因此识别这些模式如何转化为 C# 模式可能非常有用。
列表 3.18 TakesDamageProtocol.h
@protocol TakesDamageProtocol
- (void)takeDamage:(int)hitPoints;
@end
命名空间与命名约定
在您阅读本章中的示例时,您可能已经注意到 C# 类始终存在于命名空间中。C# 中的命名空间旨在避免命名冲突,并有助于组织逻辑连接的类、枚举和结构体的层次结构。
在 Objective-C 以及苹果的所有库中,无论是针对 iPhone 还是传统的 Mac 开发,您都会发现没有命名空间。Objective-C 决定不支持命名空间的概念(C# 和 C++ 等许多 OOP 语言都提供此功能)。相反,苹果选择了一种标准:属于特定家族、目的、产品或公司的类都以一个通用的两字母大写前缀开头。
例如,由 Kevin Hoffman 编写的战斗者类可能被命名为 `KHCombatant`,而不是简单的 `Combatant`。此外,同一类由 Exclaim Computing 公司编写,则可能写为 `ECCombatant` 或 `XCCombatant`。
iOS 应用程序中的命名冲突很少见,因为只有当您的应用程序使用包含与您的类同名的类的库、框架或类时,您才会遇到这些冲突。在这种情况下,命名冲突的罕见几率通常通过使用两字母前缀命名约定来消除。
扩展他人的类
本章我想介绍的最后一个主题是扩展其他开发者或公司编写的类的能力,而无需获得这些类的源代码。请记住,在 iOS 和 C# 中,对第三方类的扩展只能访问您的代码通常有权访问的类成员和方法。换句话说,您不能使用类扩展来规避围绕私有或受保护成员的封装方法。
C# 提供了一个名为“静态扩展”的功能,这大致相当于 Objective-C 世界中的“类别”概念。当特定类的静态扩展在当前代码块的范围内时,该代码块可以像这些方法属于原始类一样调用扩展类上的方法。
假设我们没有编写 `Combatant` 类,并且它是密封的,我们无法继承它。我们想为 `Combatant` 类添加一个方法,使其对象像跳方块舞一样以方形移动。也许这种效果是一种自定义的法术,可以施加在我们的战斗者身上,让他们无法抗拒地想要跳起来。
我们可以使用列表 3.19 中的代码来实现对 `Combatant` 类的自定义扩展。
列表 3.19 CombatantSquareDancingExtension.cs
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace Chapter3
{
public static class CombatantSquareDancingExtension
{
public static void SquareDance(this Combatant dancer)
{
Point origin = dancer.Location;
Point step1 =
new Point(origin.X, origin.Y - 1); // move forward
Point step2 =
new Point(step1.X - 1, step1.Y); // move left
Point step3 =
new Point(step2.X, step2.Y + 1); // move back
Point step4 =
new Point(step3.X + 1, step3.Y); // move right
}
}
}
尽管在构建扩展类时没有硬性规定命名约定,但我喜欢遵循一个简单的命名方案:[OriginalClass][ExtensionDescription]Extension。后缀 `Extension` 的存在立即告诉我以及任何查看该类的其他开发人员,它本身不是一个完整的类;相反,它为其他类提供了额外功能。被扩展的类是扩展类名称的第一部分。因此,在我们这个例子中,一个通过提供跳方块舞功能来扩展 Combatant 类的类将被命名为 CombatantSquareDancingExtension。
如果列表 3.19 中的代码在范围内,那么调用任何类型为 `Combatant`(直接或通过继承)的对象的 `SquareDance` 方法应该是完全合法的(并且可能很有趣)。
摘要
在本章中,我们简要概述了一些面向对象编程(OOP)的核心概念,以及它们如何应用于使用 Objective-C 和 C# 分别构建 iOS 和 Windows Phone 7 应用程序的任务。
那些编程经验丰富的人可能会觉得其中一些概念是基础知识,但关键在于向您介绍使用 C# 等面向对象编程语言进行日常编程所需的语法。当您继续阅读本书的其余部分时,我们将广泛使用封装、继承、属性、方法、接口和扩展等概念,如果您现在没有牢固掌握这些概念,那么本书的其余部分将很难阅读。
如果您已阅读本章,并认为您现在对如何用 C# 编写面向对象代码有了不错的理解,那么您已为深入研究 Windows Phone 7 应用程序开发世界并继续阅读本书做好了充分准备。