本文主要介绍面向对象(OO)程序设计,以维基百科的解释:
面向对象程序设计(英语:Object-oriented programming,缩写:OOP),指一种程序设计范型,同时也是一种程序开发的方法。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。
简略来说,面向对象程序设计,指采用了面向对象的方法来进行程序设计。设计指一种把计划、规划、设想通过视觉传达出来的活动过程,它是一种创造性,积累 性,实践性的工作。提笔写设计的文章是很有压力的,它不像深入一个知识点一样让人容易有的放矢,一千个读者心中有一千个哈姆雷特,同样的项目两个人来做架 构肯定不一样。包括我,每几年对设计都会有一些不同的看法,回头来看自己的代码也总会挑出很多不足,世界是不完美的,设计却希望尽求完美,闲话不说,来进 入本文的正题,看看从何谈起。
面向过程程序设计不是面向对象程序设计的前提,从面向过程谈起主要是因为自面向对象(OO)程序设计一提出,就有太多的两者对比。在这样的对比中,面向 过程被形容成老化,腐朽,僵硬的设计模式,它自上而下,按照功能逐渐细化,实现快速但面对变化时束手无策。相对而言面向对象具有封装性,重用性,扩展性等 一系列优点,言而总之:“还面向过程??你out了,来面向对象吧。。。”
C语言是面向过程的代表,它在1972年由贝尔实验室的D.M.Ritchie提出,在Unix系统中大放异彩,直至今天在系统软件,图形动画,嵌入开发等众多领域中还保持着旺盛的生命力。程序设计这个概念,伴随着程序开发被提出,最简略的被描述为 程序设计=数据结构+算法,通俗一点的说程序设计指的是设计、编制、调试程序的方法和过程。人是善于思考总结的,在漫长的面向过程的程序开发中,一些设计原则被提出,用以更好的指导设计:
以上原则参考自《Unix编程艺术》,书中总结了17种原则来指导程序设计,精简为一句话就是Unix哲学“KISS—Keep It Simple, Stupid!”,保持简单。
KISS,Keep Simple,Keep是它的核心词,底层的API设计,可以尽量保持简洁清晰,外部的需求变化,Keep?你Hold得住么?
你Hold不住!
变化来源于对事物认识的发展和外部业务的变更,当然实际中变化可能来自于更多方面。你不能拒绝变化,你只能拥抱变化,在长期的程序设计中,两条设计原则被提出:
按照正交性的设想,我们应该打造 一个纯正交的系统,在这个设计中,任何操作均无副作用,每一个动作只改变一件事不会影响其它,改变一件事情一个具体方面的方法只有一个。想法是完美的,世 界是复杂的,软件要能做到拥抱变化,设计希望达到高内聚(模块内元素紧密结合),低耦合(模块间依赖尽可能低),那么如何来拥抱变化呢?
程序最有魅力的事情在于创造,程序员使用代码来完成程序的创造。一花一世界,一木一浮生,作为粘土世界的上帝,我们采取了很多的努力来完成我们的设计:
程序像流水线一样工作起来了,数据从上到下开始运作,但是变化仍然无处不在,大修小补依然无法避免。如果模块设计的足够独立,程序的正交性足够好,变动还在可控范围之内。一旦变化跨越多个模块,程序经过多次大修,就会有种想把它捏回泥巴的冲动。
外部的变化还在继续,里面依旧是勤恳,冰冷的泥块,怎么来拥抱变化?对,伸出你的金手指,让它“活”起来。
英雄应运而生,对象应责而生。我们点“活”了对象,就是为了让它解决事情承担责任。从:
1: struct Data
2: {
3: int d;
4: };
5: void increase_data(Data* data)
6: {
7: printf("过程调用,数据为: %d", ++data->d);
8: }
9: increase_data(&Data());
到
1: public class DataWorker
2: {
3: private int data;
4: public void Increase()
5: {
6: Console.WriteLine("对象调用,数据为: {0}", ++data);
7: }
8: }
9: new DataWorker().Increase();
把传统的数据和处理数据的函数封装起来,用DataWorker对象来表示,数据变成了对象的状态,函数变成了对象的方法(行为)。一个对象被 我们点“活”了,它负责处理一件事情,把责任下放是点活对象的出发点--“变化太快了,面面俱到的管理让我疲于奔命,你就负责处理这块事情吧,由你来应对 这块事情的变化”。
对象被我们点活了,它们各尽其责来处理自己的事情,程序处理被变成了一个个对象间的相互协作,如果有变化产生我们找到负责的对象,由它来处理变化。想法是完美的,可是具体到对象,它怎么来应对变化?修改自己的方法(行为)?
对象是我们点活的,责任是我们分配的,方法(行为)是我们指定的,发生变化了还要我们来修改它的方法(行为),那绕一圈点活它干嘛?和面向过程 中直接修改对应的函数有啥区别?是的,可能方便在于比较容易定位到它,但是你修改了一个对象后,如何保证和它协作的别的对象没有意见,不会造反?
从现实来讲,作为一个老总,指派了一个区域经理来负责一块业务,负责的业务出现了问题,你会对它的业务指手画脚来重新教育他该怎么做么?不会的,不要让自己陷入泥潭,首先信任,不行就炒了他,换一个。
应对变化的关键点在于替换,这样才不会使自己陷入细节。替,顶替,表示新对象可以承担旧对象的职责,对协作的别的对象没有影响。换,表示要能应对变化,更改处理责任的具体方法(行为)。这是一个共性和变性的描述,那么怎么用程序语言来表示?
类是面向对象程序语言中的一个概念,表示具有相同行为对象的模板,类声明了对象的行为,它描述了该类对象能够做什么以及如何做的方法。一个类的不同对象具有相同的成员(属性、方法等),用类来表示对象的共性,那么怎么来表示变性呢?
类之间支持继承,可以用父类和子类来表示这层关系,用自然语言来形容,父类是子类一种更高程度的抽象,比如动物和哺乳动物。子类可以添加新的行 为或者重新定义父类的行为来完成变化。允许子类来重定义父类的行为,在对象间的相互协作中尤为重要,可以把不同的子类对象都当做父类对象来看,这样可以屏 蔽不同子类对象间的差异。在需求变化时,通过子类对象的替换来在不改变对象协作关系的情况下完成变化,这种特性也被称为多态。
封装,继承,多态,被称为面向对象技术中的三大机制,那么回到本文的主题,什么叫面向对象呢?
所谓面向对象,面向两个字很重要--“我的眼里只有你”,面向对象的哲学在于把软件(世界)看成是由各种各样具有特定职责的对象所组成,不同对象之间的相互作用和通讯构成了整个软件(世界)。以面向对象的角度去进行程序设计,需要至少以下三步:
按前面所提,类是具有相同行为对象的模板,通过同一个类创建的不同对象具有相同的行为,对象是类的一个具体例子(实例),我们面向对象设计程序时,一般从类的设计开始。
类通常情况下是自然世界中一个概念的描述,比方说Person类通常对应人,这种软件和自然世界中的对应关系使我们可以尽可能运用人类的自然思 维方式去解决问题。那么如何发现类呢?一个最简单的办法就是把我们熟知的自然概念直接抽象为类,比方说一个图书馆借书的程序,管理员,书,借书者,我们可 以很容易想出一系列的概念,这些名词概念来自于我们对生活对该领域的了解。把我们熟悉的名词(概念)直接抽象为类,把动词抽象为该类的行为,这是一种最粗 糙的类设计方法。这种设计比较容易下手,但是也会出现一些问题:
对第一点来说,这是一个理想状态,大多数面向对象语言都是静态语言,如C#/Java/C++等,类作为对象的模板,既确定了该类对象的功能, 在编译后又决定了对象的内存模型。静态语言使用继承,接口来完成类的扩展,相比动态语言,静态语言在运行速度,类型安全上有着很大优势,但由于类的扩展性 是在编译期决定的,要应对变化就需要在类的设计上多下功夫。
针对第二点,这个前面已经提到了,引入对象的唯一原因是具有责任,应对变化,让它“活”起来,是为了让我们更轻松的生活,面向对象是一种方法观,程序执行后仍然被编译成一条条的过程语句执行。不要舍本逐末,来总结一下面向对象的设计经验。
设计模式,这里的全称应该是面向对象设计模式,我们熟知的设计模式,通常指GOF定义的23种设计模式。每种模式都有一个对应的名字,按种类可分为创建型,结构型和行为型三类。
介绍面向对象设计模式的文章也很多,模式,按Alexander的经典定义,指在某一背景下某个问题的一种解决方案。这里的解决方案指细节,某 一背景下某个问题才是难点,深刻理解“什么时候,什么场合用”比“如何用”更重要,既然是面向对象设计模式,先从对象创建说起。
面向对象是为了适应变化,变化无处不在:
设计模式是一种经验的积累,面向对象设计模式的根本是为了应对对象变化,每种设计模式都对应了一类变化点。这就需要在实际运用中识别变化点,因地制宜的分析可否引入对应的设计模式来最佳化设计。
文章存在草稿里很久了,再接已经没有思路了,简略了描述一下后面的想法:
设计方面的话题总是显得空泛,设计能力的提升来自于经验,脱离了实际去谈设计模式无疑纸上谈兵。设计上面对应需求,下面对应编码,实际又为项目服务,受项目资源制约。好的设计来自于坚持和妥协,也许往大了说很多东西也如此,祝朋友们多点积累,少点折腾,谢谢。