购买
新用户福利,0.01充值币购书一次
本书不参加新用户福利活动
起始集:第2章:高效能人士也有拖延习惯
共24集 已更新至12集
北大教授讲《百年孤独》
共24集 已更新至12集
124.66币240币
余额:50.66充值币 50.66赠送币
还差0.01元(0.01币)
50.66币
计算中...
余额:50.66充值币 50.66赠送币
免费或已购章节不扣费
还差0.01元(0.01币)
职工图书馆已开通机构支付功能!选择心仪好书,机构代你购买。
知道了上一章
面向对象设计是基于面向过程设计发展而来的,对象是数据和行为的结合体。在面向对象设计的微观世界中,我们研究的就是构成设计的各种元素和粒子,即函数和对象。在讨论对象之前,我们先讨论一下行为的载体:函数。
函数还用讨论吗?我们从学习编程的第一天就开始用了,多简单啊。
在开始讨论之前,你能先回答一个问题么:“为什么编程的行为要用函数来表达?”也就是:“为什么需要函数?”
其实“数据结构”第一节课就告诉我们: 程序=数据+算法。 算法就是行为,算法是用函数来表示的,所以我们离不开函数。这也是编程最基本的一个认知了。函数是表示行为、描述算法的代码,函数是面向对象设计最基础的一环。这么说固然正确,但是可以展开来说。
函数存在的目的有以下几个。
自顶向下的设计理念告诉我们设计要从上向下,步步细化。细化的是什么?细化的是算法,每个函数的实现就是一个子算法。函数的第一个作用就是分解算法,简化流程。
还记得如何把大象装进冰箱吗:
static void Main() { OpenDoor(); PutElephant(); CloseDoor(); }
这里的每个子函数都可能又划分成多个更小的函数。
函数的第二个作用就是隐藏实现细节,也称为“信息隐藏”,更专业一点的称呼叫“封装”。
比如,上面例子中OpenDoor函数的实现可能是这样的:
void OpenDoor() { // 1.走到冰箱前// 2.握住门把手// 3.使劲拉开}
这里的每一个细节都被隐藏起来了,别人根本不用关注,只要知道这样门可以打开就行了。
其实,实现细节也包含两个部分:数据和算法。数据就是局部变量,算法就是实现逻辑。信息隐藏的同时也是信息的保护。
函数的另一个重要功能就是复用,很多人甚至认为这是函数的唯一用途,或者说是最主要的用途,其实并不是。
看下面这个打印数值的例子:
class Program { static void Main(string[] args) { int year = 2015; Print(year, ConsoleColor.Red); year = 2016; Print(year, ConsoleColor.Blue); Console.ReadLine(); } static void Print(int value, ConsoleColor color) { Console.ForegroundColor = color; Console.WriteLine(value); } }
打印整数值的逻辑很多时候都用得上,所以可以复用。
函数调用只提供一个名字,而实现细节被隐藏到了函数体中。所以只要函数的签名不变,函数体内的修改并不会波及其他代码。因而函数提供了应对变化的手段,把可能变化的逻辑放到函数中封装起来,这是函数的重要用途。
比如下面这个Print函数:
static void Print(int value, ConsoleColor color) { Console.ForegroundColor = color; Console.WriteLine(value); }
如果哪一天我们觉得不输出到控制台,而是输出到文件中,那么,我们只要修改Print本身就可以了,不会波及其他地方。
函数的名字本身就是最好的说明,当我们考虑给一段代码写注释的时候,就是可以使用函数的信号。
void OpenDoor();
这个签名比注释是不是好很多?而且有编译保证正确性,不像注释,写错了也没人知道,而且相关逻辑更改了以后一般人也不会更新注释。
综上所述,其实函数是编程设计中最常用的抽象方式之一,属于粒度较小的元素,这是面向对象设计的基础。比函数稍大一点的抽象是对象,它在函数的基础上加入了数据,下面我们就讨论一下对象。
对象是具有状态和操作的编程实体。对象具有状态,一个对象用数值来描述它的状态。对象具有操作,用于改变对象的状态,操作就是对象的行为。对象实现了数据和操作的结合,使数据和操作集合于统一体中。
面向对象的基本哲学是认为世界是由各种各样具有自己的运动规律和内部状态的对象所组成的,不同对象之间的相互作用和通信构成了完整的现实世界。因此,人们应当按照现实世界这个本来面貌来理解世界,直接通过对象及其相互关系来反映世界。这样建立起来的系统才能符合现实世界的本来面目。在这个哲学体系中,最为基础也最为重要的概念就是面向对象的三大特征:封装、继承和多态。
没错,“封装”,就是前面讨论函数功能的那个“封装”。手法是一样的,只不过目标换成了对象。
封装是信息隐藏的手法。
封装确定了对象与外界交互的稳定方式,这些稳定的成员叫做契约或者接口。封装提供了一个良好的合作基础,只要接口不变,则双方互不干扰,同上面函数的封装意义一样,把可能变化的逻辑使用对象封装起来,是应对变化最直接的手段之一。
在面向对象语言中,对象基本都是由类来实例化的,类就是对象的模子,它定义了对象的一切。
看大象这个类:
class Elephant
{ private string m_name; public Elephant(string name) { m_name = name; } public virtual void Run() { Console.WriteLine("我跑得比较慢..."); }
}
很简单,典型的对象的影子,成员有数据(name),有行为(Run),它们被封装到了一起。
还记得上面函数的复用吗?继承就是对象级别的代码复用的重要手段,但不是唯一手段,还有一种手段叫组合。
继承和组合是代码复用的两种最基本的手法。
看猛犸象的类:
class Mammoth : Elephant { protected int m_age; public Mammoth(string name, int age) : base(name) { m_age = age; } public virtual void Howling() { Console.WriteLine("我还会吓人..."); } public override void Run() { Console.WriteLine("我可是飞象哦..."); } }
它也是大象,有名字,还有了年龄数据,也能跑,还会吓人。它在大象数据和行为的基础上,扩展了新的数据和行为。
继承提供了复用,但是个体是有差异性的。多态就是用于在统一的接口这个框中描述个体的差异性,多态是继承这种手法的延续。
假设猛犸象跑得很快,那么它就不能用原来大象的行为了,需要表现差异:
public override void Run()
{ Console.WriteLine("我可是飞象哦...");
}
好吧,还是看更实际点的例子吧。
在.NET框架中,我们需要一个根对象Object,来提供每个对象都需要的接口功能,如ToString、GetHashCode、GetType、Equals等。同样的,在实战中,在一个软件系统中,我们也需要使用一个对象去描述软件内所用到的实体类都需要的接口功能,我们通常也需要设计这样一些基类,比如可以存储的对象的基类、显示数据的对象的基类、接收和处理用户输入的对象的基类等,例如下面的代码:
public class ModelBase { public virtual void Save() { } public virtual void Load() { } } public class ViewBase { public virtual void Show() { } public virtual void Hide() { } } public class ControllerBase { public virtual void Render() { } }
当需要用到实际的类的时候,我们就可以从这些类来继承,获得这些接口方法的默认实现。如果实现的细节不同于这些默认的实现,那么就可以重写这些方法,也就是多态的体现。
综上所述,如果把重载当作函数的多态的话,我们对比一下函数的功能和对象的特征,你会觉得除了抽象粒度大小以外,还真没什么不同。
来看一下对比关系,如表1-3所示。
表1-3 函数和对象比较
这就是面向对象设计微观世界中最主要的粒子的特性。如果说现实世界中的粒子是围绕万有引力来转的话,那么设计世界中的粒子就是围绕信息隐藏、复用、变化来转的,这是本书将要讨论的重点。
下一章
评论内容
评论时间
登录
批注
保存
这个世界上的大多数人,有的都只是拼尽一生的力气去活着的人生,也许在生命的尽头,每个人都发现变成了自己不曾想这个世界上的大多数人,有的都只是拼尽一生的力气去活着的人生,也许在生命的尽头,每个人都发现变成了自己不曾想…
掌阅AI阅读助手
掌阅AI阅读助手将人工智能技术与海量优质出版物融合,构建出卓越的语义理解、内容总结和信息抽取能力,意在帮助用户更高效的发现知识、理解知识和使用知识。
知道了
翻页方式
行间距
字体选择
细字体
系统默认
细字体
加粗字体