凭什么要用面向对象编程——面向对象重要设计原则概述- 伍迷家园- 博客园

本文是我打算离开公司前的一次培训讲座的准备材料,讲得是几个面向对象的设计原则,没什么新鲜东西,都是比较浅显的内容,推荐刚接触面向对象编程的新手阅读,高手达人们路过飘过即可。文章略长,不过内容过渡上比较平缓,应该比较容易阅读。
 

{dy}种讲法

今天我要讲这么几个设计原则(开始念)

开放-封闭原则:是说软件实体(类、模块、函数等等)应该可以扩展,但是不可修改。

里氏代换原则:子类型必须能够替换掉它们的父类型。

依赖倒转原则:A. 高层模块不应该依赖低层模块。两个都应该依赖抽象。B. 抽象不应该依赖细节。细节应该依赖抽象。

讲解完毕,今天培训结束。

反响:捣浆糊讲解,大家笑骂。

 

第二种讲法

开放-封闭原则举例

1982年1月,小平同志在会见美国华人协会主席李耀滋时,{dy}次正式使用了“一国两制”的名词。
1982年9月,英国首相撒切尔夫人同小平同志{dy}次会见。小平谈到:{dy},中国决心按照“一国两制”的设想,于1997年收回整个香港地区,主权问题不容谈判;第二,希望中英合作实现平稳过渡;第三,如谈不成,中方将单独 采取行动;第四,如出现动乱,就将采取非和平方式提前收回香港。撒切尔夫人在走下人民大会堂东大门台阶时摔了一跤。大陆新闻片为留其面子没有这个镜头。但在香港看片子,片子里老出现这个镜头。
1997年7月,香港正式回归。
2007年6月,英国前首相撒切尔夫人说,香港归还以后的发展证明,当初英方的担心是过虑了。她又说,当时她对“一国两制”也没信心,现在她想告诉xxx,他的办法很好。

“伟人牛就牛在不会编程,也是会用设计原则的。‘一国两制’,就是‘开放封闭原则’的应用。在不改变大陆原有社会主义制度的前提下,一个国家在回归的特区实行一种新的管理制度方式,所有的问题都变得不是问题了。在编程中,面对时常更改的需求,也可以做到类似的思维方式来解决问题。”

 

里氏代换原则举例

感冒,有点发烧,邻居正好是内科医生,于是就敲门想请帮忙看看病。哪知不巧,邻居不在家。
熟人不在家,难不成这病就不看了?身体吃不消时还是得去医院。
医院挂完号后,到门诊室前,此时里面医生是男是女,姓啥叫啥,都不清楚。可{wy}清楚的就是,他是医生,相对普通人来说,是看病的专家,可以对症下药。
里氏代换原则:子类型必须能够替换掉它们的父类型。在这里很好理解了,任何一个具体的比如张医生、王医生、李医生,他们的共同点,都是医生。也就是,他们都可以代表医生这个角色。

 

依赖倒转原则举例

更加复杂的电脑能修,而简单的收音机却不会修,这是为什么?
收音机就是典型的耦合过度,只要收音机出故障,不管是声音没有、不能调频、有杂音,反正都很难修理,不懂的人根本没法修,因为任何问题都可能涉及其它部件,各个部件相互依赖,难以维护。
电脑却不一样, 内存坏了换内存,硬盘坏了换硬盘,主板烧了换主板,反正普通人,只要有点常识,基本都可以鼓捣几下。当然,电脑的所谓修也就是更换配件,CPU或内存坏了,老百姓是没法修这个器件的。
无论主板、CPU、内存、硬盘都是在针对接口设计的,如果针对实现来设计,内存就要对应到具体的某个品牌主板,那就会出现换内存需要把主板也换了的尴尬。
这就是高层模块不应该依赖低层模块。两个都应该依赖抽象。依赖倒转原则的根本要义。

讲解完毕,今天培训结束。
反响:对几个原则有点直观感觉,但不明白如何应用。

 

第三种讲法

编程场景引入,代码重构与演变方式讲解。


2007-7-1 20:13 小菜接到创业开公司不久的好朋友老卞的电话
老卞:小菜呀,能帮我个忙吗?帮我写个小网站的程序好吗?要求不多,也就是对我那几个员工的日常信息管理。比如“登录”……(其它功能略)等。
小菜:好呀,没什么问题,我最近也没什么事。这点功能容易,过两天写好。

 

 

1 用户登录界面

 

2 登录页面代码

 



 

2007-7-25  21:48 小菜再次接到老卞的电话
老卞:小菜,网站效果很不错,能不能再出个手机上网的版本,现在这个在手机上看起来效果不太好。
小菜:可以,没什么问题。

 

思路:考虑到同时两个登录页面要有登录功能,所以写了一个可供调用的用户管理类,让两个页面可以都调用它。起到代码复用的作用。


 

3 手机登录界面


 
4 用户管理类 登录方法


 
5 调用登录方法的界面事件代码

 

 


 

2007-7-29  18:51 小菜第三次接到老卞的电话
老卞:小菜,现在上手上网站效果真不错,但因我的员工大部分时间都在公司,如果它还可以是一个客户端的应用程序,能用一些快捷键和自动运行的网站不容易做到的功能就好了。
小菜:哦,你的意思就是在现在b/s架构的基础上,再要一个c/s架构的程序呗,容易。

 

思路:为了便于让桌面应用程序和网站都可以复用登录代码,所以增加了一个DataAccessLayer的Project,基本代码未改变。这样就可以让网站和客户端程序都调用UserAdmin类,避免了代码的重复。


 
6 应用程序登录界面


 
7 源代码结构图
 

 


2007-8-3  18:51 小菜第四次接到老卞的电话
老卞:小菜,你太牛了,我提的需求你都能满足。现在发生了这样的情况,本来我在托管的服务器里装了盗版的sql server的,最近人家通知我,微软查得非常紧,所以我想……
小菜:你想把数据库换成免费版的?
老卞:对呀,MySql或者Access都可以,反正就是别让微软抓到把柄就行。
小菜:这的确是麻烦一些,不过问题也不太大,等我消息吧。

 

思路:必须要用到接口、继承和多态等面向对象的特性了(若不理解此样例代码,请先学习继承和多态的基础知识)。

 
8 源代码结构图 用户管理接口与各个子对象


 
9 接口代码


 
10 Access实现接口类(其它类类似)


 
11 网页或窗体调用代码


说明:由于要考虑Access的实现,所以在所有调用登录接口处都要写一句
IUserAdmin ua = new UserAdminAccess();
目前样例中是三个地方,就需要写三遍。若要改成访问Sqlserver数据库的登录代码,就需要改三个地方。

 

 


2007-8-8  21:29 小菜第五次接到老卞的电话
老卞:小菜,上次我让你改成Access的代码有改了没有?
小菜:改好了,明天就部署上去,你可以用了。
老卞:啊,是这样呀,因为我后来想来想去,还是用SqlServer功能更强大一些,以后可扩展的可能性大一些。能不能两种方式都保留呀?
小菜:嘿嘿,你想法够多。行了,我可以做到的。

思路:如果写死一种方式,比如IUserAdmin ua = new UserAdminAccess();就会使得更换数据库时,带来麻烦。所以面对可能更改数据库代码的变化,所以用一个工厂类来把变化给封装掉,此时,要修改也只是修改工厂类而已,对外面的引用代码不需要修改。

 

 
12 增加用户管理工厂类


 
13 增加用户管理工厂类


 
14 各个登录页面的代码

 

 


2007-8-15  19:24 小菜第六次接到老卞的电话
老卞:小菜,真的不好意思,又要麻烦你了。我一朋友看来你为我做的这个管理软件,感觉非常好,想在他的企业内部也使用,他们已经购买过Oracle正版使用权了,所以你能不能再给出一个访问Oracle的版本,放心吧,只要好用,他一定不会亏待你的。
小菜:啊,你这变化实在是好快呀。
老卞:不然人家怎么都叫我“老变”呢。帮帮忙!
小菜:好吧,我有想办法解决的。

 

思路:可惜原有的工厂类,还是需要修改的,为了更好的满足“开放封闭”,于是把工厂类再次改造,让它依赖于配置文件的信息,此时,就更加灵活的实现了应对变化(此处用到了.net的反射技术,不理解的请研究相关内容知识)。

 

15 改进用户管理工厂类

 

 
16 web.config的配置信息

 

 


2007-9-23  20:47 小菜第七次接到老卞的电话
老卞:小菜,好久没来骚扰你了。最近可好呀?
小菜:老卞呀,你好你好,欢迎骚扰,你的“老变”让我的程序越来越适应变化了,哈,我不怕你变的。说,又有什么新想法了?
老卞:那样{zh0}了,是这样的,你的软件在我公司已经广泛应用,就登录这个功能,还有些小缺陷。我们员工每天上班都要用考勤系统,以前是“打卡”的,现在直接是“指纹”的,其实既然已经人来上班了,我们的软件系统也就不需要再输入用户名和密码登录了,你看可不可能增加通过“打卡”和“指纹”登录系统的功能?
小菜:不用“用户名”和“密码”方式登录了?
老卞:不是不用,而是说增加其它的登录方式。
小菜:你这个需求够狠。好吧,我想想看。

 

思路:如果增加下面两个接口方法,再针对它们做一个实现,其实也没有彻底解决这种变化带来的麻烦。
 
17 增加两个接口方法


 
18 实现指纹登录代码


思路:这样的办法,并不是{zh0}的办法,因为难讲以后还可能出现其它的登录要求,难道每次都去增加方法?应该考虑更好的设计方式。登录的本质是什么?为了方便系统能识别该用户的身份,从而保持该用户的使用习惯或使用数据。显然用户名、密码、员工考勤卡、员工指纹,都是具体的可以代表员工身份的属性。因此,登录行为的变量不应该是具体的属性,而应该是抽象的用户对象本身。

 

 
19 让登录方法的参数是抽象的用户对象

 

 
20 用户对象可以包含所有用户可能属性


 
21 登录调用代码(用户名和密码)

 

 
22 登录调用代码(指纹识别登录)

 

说明:具体实现Login的方法相对复杂一些,但不外乎就是对相关属性的比对判断。由于通过不断的抽象,使得程序越来越能适应变化。

 

 


2007-10-01  13:04 小菜第八次接到老卞的电话
老卞:小菜,你实在是太棒了,我提的要求你都能满足,你写的代码现在一切运行正常。今天正好休息,我请你吃饭。另外我的做开发的美女郝雪也想通过这个机会向你请教一下你的编程经验。
小菜:你太客气了,这也没什么的,说白了,就是我在面向对象编程而已。
老卞:太好了,到晚上时,我通知你地点,到时见。

 

 


 

2007-10-01  19:51 小菜和老卞,以及老卞的朋友郝雪在一起吃饭
郝雪:菜老师,你到底是如何做到,无论需求如何变,你的程序面对变化,却依然能从容不迫呢?
小菜:这个说来其实不算复杂。

 

比如,为了使得常用代码可以复用,一般都会把这些常用代码写成许许多多函数的程序库,这样我们就可以在做新项目时,去调用这些低层的函数就可以了。比如我们做的项目大多要访问数据库,所以我们就把访问数据库的代码写成了函数,每次做新项目时就去调用这些函数。这也就叫做高层模块依赖低层模块。


我们要做新项目时,发现业务逻辑的高层模块都是一样的,但客户却希望使用不同的数据库或存储信息方式,这时就出现麻烦了。我们希望能再次利用这些高层模块,但高层模块都是与低层的访问数据库绑定在一起的,没办法复用这些高层模块,这就非常糟糕了。


就象刚才说的,PC里如果CPU、内存、硬盘都需要依赖具体的主板,主板一坏,就所有的部件都没用了,这显然不合理。反过来,如果内存坏了,也不应该造成其它部件不能用才对。

而如果不管高层模块还是低层模块,它们都依赖于抽象,具体一点就是接口或抽象类,只要接口是稳定的,那么任何一个的更改都不用担心其它受到影响,这就使得无论高层模块还是低层模块都可以很容易的被复用。这才是{zh0}的办法。


面向对象的好处是可维护、可扩展、可复用和灵活性好。有了开放封闭思想和依赖倒转原则,我们就可以大胆的应用它实现这些好处了。


 

对于这个登录的例子来说,一个没有经验的程序员,可能每一次的设计都只是刚刚满足需求的(比如这个例子经过这么多次演变才成为最终的样子),这本身也不是大问题,毕竟一开始就考虑太多,xx有可能走入过渡设计的怪圈(过渡设计的{zd0}问题是开发的成本大大增加)。但当发现原有的设计不足以应对变化时,的确就需要通过重构来让代码更加灵活(比如抽象了一些类或接口,利用某些设计模式来封装变化等)。这是一种自下而上的设计方法。

另外,如果有了一些经验,当客户提出要一个“登录”的功能时,就已经可以考虑到创建一个 int Login(Users user); 这样的接口方法,至于将来是否要把这个方法实现多次,有多少个程序会调用它,那都是以后考虑的问题。这样,当客户表述清楚需求后,其实你已经就开始了接口代码的设计工作,而这个工作是整个编程工作中最重要的一个环节。这就是自上而下的设计方法。

此两种设计方法没有优劣之分,在需求很清楚时,自上而下,在需求不明时,考虑先做满足当前需求的设计,后再根据情况重构,总的来说,当你的经验越来越丰富以后,你就越发能做出合适的选择。

 

依赖倒转其实可以说是面向对象设计的标志,用哪种语言来编写程序不重要,如果编写时考虑的都是如何针对抽象编程而不是针对细节编程,即程序中所有的依赖关系都是终止于抽象类或者接口,那就是面向对象的设计,反之那就是过程化的设计。

掌握了这几个原则,你以后学习面向对象设计模式,就会发现,它们其实都是做了同样的事,目的都是为了针对抽象编程,从而带来容易维护扩展复用的好处,也就是说,设计模式用了哪一些、会用的有多少并不重要,你是否有这样的设计想法才是最重要的。

所以说,为什么要用面向对象来编程呢?说白了,就是为了让程序更容易的应对需求变化。

 

郝雪:你刚才讲得真是太好了,你看哪天有空,到我们公司给大家讲讲你的面向对象编程思想好吗?
小菜:不用了吧,这其实也就是很简单的原则,我只不过就是谈谈为什么要用面向对象来编程,它的好处是什么。
郝雪:那就以“凭什么要用面向对象编程”来作为标题做一次讲座,好吧,就这么定了,谢谢!。
小菜:……

 

好了, 讲解完毕,您辛苦了,如果坚持看到了这里,说明您真的是一个很有耐心的人,程序员同样需要有足够的耐心和坚持,谢谢您的阅读,还请您给予批评指正,您的回复对我的创作很重要。

 

posted on 2010-05-26 17:21 阅读(1301)   所属分类: , ,

评论

+1
行云流水般畅快。
   private static readonly string AssemblyName = "DataAccessLayer";
        private static readonly string db = ConfigurationManager.AppSettings["DB"];
        
        public static IUserAdmin CreateUserAdmin()
        {
            string className = AssemblyName + ".UserAdmin" + db;
            IUserAdmin iua = (IUserAdmin)Assembly.Load(AssemblyName).CreateInstance(className);
            return iua;
        }


支持哈,有个问题..
这儿使用反射仅仅是为了以后增加其他类型,不需要改这个工厂代码?有其他好处没有?
这样反射创建的和直接New的有区别没有..
谢谢老师指点哈..
增加一种新的登录方式,下面以卡举例
1、增加一个类,实现IUserAdmin
public class UserAdminCard : IUserAdmin
{
#region IUserAdmin Members

public int Login(User user)
{//比较用户卡信息

throw new NotImplementedException();
}

#endregion
}
2、在配置中修改db的类型
<add key="DB" value="Card"/>
是不是呢?
文章写得不错,把常用的登录功能和设计模式结合起来,以及代码重构等。

我有点想法,最终的User类包含username,password, cardid, fingermark 等信息,感觉User类不能拥有如此的多的信息。觉得如果业务逻辑更复杂的话,可以把User类抽象(名称改为ICredentials,和Dot net framework同)。

那么login逻辑变成了
int login(ICredentials credential)
{
... business logic here
}

这样可以更大程度和Net 平台类库功能结合起来,也能充分利用Net平台优势。
当然你的文章主题不在于此。

昵称:

主页:

邮箱:(仅博主可见)

评论内容:

    

[使用Ctrl+Enter键快速提交评论]

郑重声明:资讯 【凭什么要用面向对象编程——面向对象重要设计原则概述- 伍迷家园- 博客园】由 发布,版权归原作者及其所在单位,其原创性以及文中陈述文字和内容未经(企业库qiyeku.com)证实,请读者仅作参考,并请自行核实相关内容。若本文有侵犯到您的版权, 请你提供相关证明及申请并与我们联系(qiyeku # qq.com)或【在线投诉】,我们审核后将会尽快处理。
—— 相关资讯 ——