鸵鸟非鸟?

TQ:

有如下定义:

Class 鸟{。。。;public virtual void Fly(){};。。。}

Class 麻雀{。。。;public virtual void Fly(){...};。。。}

Class 大雁{。。。;public virtual void Fly(){...};。。。}

。。。。。。

如果是鸵鸟不能飞,就在飞的方法里抛出异常。。。异常不能算是一种正常编程逻辑吧

XJ

异常并不是一种好的方法

FX·SL

异常是可以的……异常名就是NotImplException,该鸵鸟没实现飞这个接口。。。

TQ

这在很多时候都是一种反例

XJ

我的意思是?既然你要抛出NotImplException??那是不是也是说明了?驼鸟根本就不应该有??飞?这个方法

此时是不是不应该使用继承,而是使用接口

TQ

如果违背语意实现了,算不上好的设计实现

FFT

违反LSP了

FX·SL

没违背啊,鸵鸟也是从鸟这个抽象类继承的啊

XJ

但是它不会飞呀

FX·SL

难道他就不是鸟了?

XJ

那就反过来,鸟并不是都是会飞的

TQ

它是鸟,但是你关注飞,此时比较好的方式,是鸟分为能飞或者不能飞,或者干脆提出飞的接口

XJ

对,提出飞的接口

FX·SL

其实不奇怪,设计抽象类的时候不可能考虑全面,就跟你一般认为鸟都会飞,但个别不会飞也没啥奇怪的,现实世界已经如此,为啥要搞出个比现实世界更完美的模型?

TQ

嗯,这也是设计时候或者写鸵鸟的时候比较为难的地方。抽象鸟类设计者是无法考虑,但是为什么要从鸟类继承,语意我觉得此时可以放弃,如果希望使用鸟类实现,用组合替代。如果是其他地方有鸟类为参数类型就比较不好解决了。。。

TQ

不过抛出异常这种方式,除非实在没有办法,否则用的人并不多。

STST

异常只能处理真正的无法处理的问题,代替程序逻辑本来该做的事情,除了不懂以外,更大的原因就是偷懒

FFT

父类实现了该方法,子类抛异常,这个肯定是违反了LSP

XJ

程序员要学会偷懒

STST

但这个懒不能偷

FX·SL

模型是对现实世界的映射和抽象,如果不能反应现实世界,那有点走偏了吧

"父类实现了该方法,子类抛异常,这个肯定是违反了LSP",这种其实实际使用中很常见

STST

程序员偷懒是指:用一个方法去解决N问题,而不是用N个方法去解决N个问题

"父类实现了该方法,子类抛异常,这个肯定是违反了LSP",这个不容置疑

FX·SL

比如你的基类是第三方提供的饿……

STST

FX·SL

而你实现的子类基于权限原因就是不能开放这功能。。。,反模式并不一定不合理,只是需要仔细推敲

STST

反模式?的意思理解了吗?是不用模式吗?

FX·SL

反模式,就是找出常见的坑,不走寻常路。。。

XJ

模型是?现实?世界的?抽象!!!!是要反应现实世界,但是某一方面,不可能完全

FX·SL

你理解错方向了

STST

反模式的意思是不要为了模式而曲解问题本身, 由问题出发,而不是从模式出发

?

TQ(475177821)

@FX·SL?是为了进一步追求语意,但是这里如果追求了语意,鸵鸟进去了,结果飞不起来,反而有异常出现,这会出乎很多人的意料,相比于这样,语意就不是那么重要了

FX·SL

对啊,所以违反了LSP,就违反了吧,如果飞只是几百个特性里面的一个,那异常就异常吧,所以一般我们认为鸵鸟依然是鸟,而鸟我们一般认为是能飞的,不冲突

TQ

对啊,你是这样想的。其他程序也是根据这个设计的。但是鸵鸟进去了,飞不起来,还给你抛出了异常。你这边程序怎么办?

STST

把飞从鸟身上分出去,鸟不具有飞的行为,问题就解决了

FX·SL

有异常很正常啊

TQ

。。。

FX·SL

第三方提供的基类,你要怎么去呢?

STST

代码量太少了,还不够理解这些

XJ

其实我想问?除了驼鸟,还有其它不会飞的鸟吗?

FX·SL

多啊,只是相对比例不多而已

STST

软件里只有Yes和No,没有统计的概念啊

FX·SL

一样有的

STST

有一个不符合抽象的,就说明抽象有问题

FX·SL

设计和架构本来就是权衡

FX·SL

抽象有问题,但现实可能是抽象标准已经确定

STST

需要改的是抽象本身,而不是要求客户去忍受"不合理的抽象",否则必然被遗弃

FX·SL

……第三方可能就是客户的老板,第三方确定的基类抽象,你让去改?这个很常见的事啊

STST

DDD里有一个概念叫防腐层啊,用一个薄薄的层把第三方隔离一下啊, 模型最好不要与第三方直接打交道

?

FX·SL

主要我是觉得丢异常这种方式很好。。。其实也可以视作是业务异常

XJ

省事,是真的

FX·SL

只是看你把遇到一只不能飞的鸟作为系统异常,还是业务可容忍范围的东西

STST

这种情况丢异常,一个问题是滥用异常,第二个问题是根本不解决问题, 只是把问题全交给客户去做了

FX·SL

我倾向于,让系统做的更具弹性,解决问题,业务异常,无非就是这个业务处理不了,跟鸵鸟最终飞不了一个效果

STST

客户不满意的模型,能长久吗?

FX·SL

呵,客户关心的是解决问题,没有模型的软件开发不也横行了那么多年了么

STST

你连lisp原则都不理解,我很难想象你理解的弹性是什么?

FX·SL

我不是不理解,而是你一开始就理解错我说的反模式和我要表达的东西好么

赣州小啊

关于鸵鸟飞的行为设计,还是看需求。和实际业务关联更有意义。
比如,业务关注候鸟飞行迁移,鸵鸟甚至可以不划分在鸟类。如果关注鸟类物种划分,那么和飞的行为没有必然关联。

STST

根据我的经历,你理解的弹性就是"非常过度的设计",这是我以前认为的弹性,不一定和你现在理解的弹性一样

FX·SL

在纯理论的谈论设计层面的时候,你说的都没错,但实际操作的时候,我已经没那么教条了

STST

我从来没有离开过一线编程,但是你这么不信OO的几个基本原则,我感觉很怪,这些原则,我可以说,是世界软件工程发展的最大的精华

FX·SL

我不是不信,而是我会根据情况选择是否完全遵循,因为OOAD本身也不那么完美,软件的世界不只是OOAD的,大量事件啊,批处理的世界是不适合OOAD建模的

STST

"如果飞只是几百个特性里面的一个,那异常就异常吧",看到这个说法,感到有些吃惊

FX·SL

说句你一定听过,也信奉的话,没有银弹,要追求完美的抽象请转战到数学教材

STST

是的,你说的没错,没有银弹。

企鹅的这个例子是一个讨论的非常广泛的典型例子,这个例子非常适合讨论

各方观念也是五花八门,但是占优势的还是OO

TQ

我是觉得,对于OO原则那些以及最佳实践,一般是被大家所遵从并理解的语意。一般情况下我们应当追求并且尽量符合语意。而确实特殊的情况下,偶尔违反确实也没办法,但是心里得清楚自己因为某些原因违背了语意。否则可能遵从了小语意,而违背了大的语意。毕竟软件和现实世界不完全等同。

STST

前面有人解释了,"父类实现了该方法,子类抛异常,这个肯定违反了LSP"

STST

违背了lsp必然会违背其他的原则,OO是一个很奇妙的东西,各种设计原则密切相关

TQ

oo原则都接触过,靠近它,我想一样的道理

FX·SL

OO这些都没问题,但架构的核心是结构平衡术,而OO本身也是设计,我说的各个领域的语义,你觉得,哪个你不赞同

TQ

和实践有关,我前面也说了@FX·SL?说,抛出异常,特殊情况也并非不可以(比如,老板固执,不让修改,或者第三方没办法修改,限制条件太苛刻),大多数时候如果可以的话还是遵从OO比较好,这些原则第一步考虑,我想大多这样吧。OO原则很重要,追求它,但也没有人说一定不要破坏它。否则做一个软件把可能的模式都用上,可以吗,初学者喜欢这样吧,"过度设计"这个词为什么会出现,就是一种折中吧。我们中没有人说没有违背过OO吧【何况好多原则之间可能有冲突】

FX·SL

所以才有反模式

TQ

必须得权衡,但是首要的是,我们追求它,尽可能遵从

FX·SL

经常违背LSP也违背出经验了,这个经验总结下就叫反模式……

TQ

态度是第一位,我今天实在没办法违背了,我得考虑好后果是什么?下次能否避免,我想起来了书中的某句话:?如果你违背了某条原则,那么不会被处以绞刑,但是警钟得在你耳边响起?。?我觉得特有道理,除非你清楚自己在做什么。

CD

我觉得,这里的人大多数的问题不在于是否赞同遵从oo,而是,粒度如何把握,针对什么进行OO

TQ

这个和人经历有关,比较喜欢听有很多编码量的前辈级人物讲解

CD

针对具体的需求描述,进行OO设计,粒度到满足现有需求,不做预设计,预开发,你还是乖乖得写代码吧,我觉得,你且得过度设计几年,回顾我的历史,我似乎一直在进行过度设计,现在依然存在,只是,基本能分辨出来了而已,因为有些东西,是拿公司的东西搞实验嘛

TQ

我不好说。就像那个面试题,我个人觉得追求语意,结构和扩展性以及复杂度之间做了个均衡,但是人把需求变了下(可移植性),我瞬间有子类爆炸的风险。。。。。。。唉

CD

是的,先搞清楚需求,按照需求设计,是非常重要的

FX·SL

推翻重来一般比重构靠谱

STST

重构以测试为前提,你可以先打听下有多少人写测试了得

FX·SL

话说,你们有项目重构很成功的吗?个人项目不在列

TQ

我那个确实几乎得重来,因为当时考虑的扩展点不在人说的哪里。。。

CD

因为你做预设计了,是的,没有测试,说毛的重构

FX·SL

写了单元测试一样构不动

TQ

@CD??你一点预设计都不用做?

CD

我来现在的公司,第一件事就是建立回归测试,我会考虑,但是,我不做,仅满足现有需求

FX·SL

回归测试不是测试的事呢?成开发的了?

CD

理解业务,进行合理得解耦

TQ

。。。考虑,你不在代码架构上做出一番调整,考虑了似乎用途不大吧,代码结构

CD

你没理解我的意思?

FX·SL

过度设计其实是会导致对象建模偏差的?

ZJT

任何人都会说合理的设计合理的设计,这个合理用什么来界定?能否量化?如果不行,那你们就像一群小孩子在吹着牛逼说自己爸爸比人家爸爸更厉害?

STST

"满足现在的需求",设计只要到这一步,多了就是累赘,多了就是过度设计,很有害

FX·SL

@STST??至于你说的违反LSP设计的事,其实微软的.NET框架代码里面都满地是

举两个例子:Stream.Length,所有Stream都可以获得Length?IList.Add如果是个数组就可能抛出NotSupportedException

这个看你如何看这个问题

这个在我看这个是一个业务异常

因为我实际上就是不会飞啊

然后你就要开除我鸟籍?还是要重构鸟类的定义?

XJ

我编译成功,但一运行告诉我不支持ADD方法?????

FX·SL

业务异常,因为数组不支持Add这个功能,微软的代码里面全是这种方式,Java也应该差不多,Stream里面很多方法都有这种情况,比如Seek就很多子类不支持的

FFT

networkstream?length是抛异常的,但是stream并没有实现length方法,这没有违反lsp

FX·SL

这和鸵鸟实现飞接口,抛出异常有区别?

STST

IList.Add如果是个数组就可能抛出NotSupportedException

我自认为对DotNet的库比较熟悉,那个List的Add会抛这么一个异常?

关于鸵鸟的问题,毫无疑问是"需要重构鸟的定义,把飞的行为分出去" ,这才是正道

FX·SL

自己认为自己的正道去吧,这问题看得说了

STST

那你告诉我,到底哪个List会抛这么一个异常?我还真不知道

STST

" networkstream?length是抛异常的,但是stream并没有实现length方法,这没有违反lsp"

关于前一个长度的问题,这个说的很好,基类本身没有实现,子类当然可以不用实现

然后还有一个细节要注意,实现接口和继承一个类的语义是不一样的,LSP是针对后者而言

FX·SL

鸟作为抽象类需要实现了飞?继续作吧

别的不说,你就把Stream.Length抛NotSupportedException和鸟.飞可能子类是鸵鸟时候抛出异常之间的区别说清楚就行了,不然就别瞎哔哔丢人了

去让微软把Steam抽象类重定义,去掉Length属性和Seek方法吧

STST


这整个都是描述接口,异常,错误返回值都是接口的一部分

STST

如果哪个具体的类抛出了除 NotSupportedException之外异常,就说明没有满足LSP

FX·SL

嗯,IList这个例子确实不够说明鸟的问题的,所以还是回到Stream的问题上来聚焦吧,那Stream下面的一半类都没满足,为啥现在要除呢?

下午鸵鸟的问题不是说违反了LSP么?

STST

关于Stream的Seek和length的属性问题,你看一下,Stream处于继承链的什么位置?

FX·SL

那鸟处于什么位置呢?

STST

Stream位于所有流的最顶端,上面就是Object了

FX·SL

最顶层和非最顶层只和你的业务领域边界有关,别东扯西扯,鸟就是顶层类有问题么?

STST

Stream当然有权利也有责任定义其子类可以在什么情况下抛什么异常,因为这也是其接口的一部分

FX·SL

Stream没有定义异常,谢谢,那是他的子类跑出来的,基类能定义子类的异常,也是醉了

STST

如果子类抛除了这两个异常之外的异常,肯定是不满足LSP的

因为Stream的客户端可能如下:

doSomthing(stream?s)
{
?try{。。。。}
Catch(NotSuportedException ex){......}
Catch(ObjectDisposedException ex){......}
}

所以子类抛出了这两个之外的异常,就会对现有的调用端产生冲突, 鸟事抽象类,没错,但是飞从头到尾没有说到其有异常返回

FX·SL

这么说也有点道理,但实际上.NET源码里面没这个定义,也就是说是语法限制,一样的,飞这个抽象方法要定义异常而已

STST

错误,异常都是接口的一部分,不能分开,特别是Java里,直接可以声明可以抛什么异常

在接口里定义了异常,那么必然对客户端提出了更多的要求,客户端程序必须处理这些异常,否则有问题就是客户端的问题

FX·SL

这么说倒是说的过去

FFT

这是契约式编程的一部分,.net?framework其实严格的遵守lsp,泛型协变,逆变可以看出微软对这一点的重视程度

FX·SL

所以鸵鸟完全可以实现鸟.飞,只要基类定义个NotSupportedEx就行了

STST

就如同这个,如果你的客户端程序没有处理这两个异常,别人抛出的这两个异常,造成你调用的异常,是客户端的问题,而不是子类的问题

TQ

"所以鸵鸟完全可以实现鸟.飞,只要基类定义个NotSupportedEx就行了",这个还是要重构基类-?-?

STST

一语中的,是的,这就是需要修改鸟的抽象定义

FX·SL

嗯,这个受教了。

STST

鸟的定义才是问题的根源,从这里才能真正解决问题,其他的方式是属于掩盖问题

FX·SL

忘了抽象类还可以定义异常这个事了

STST

修改鸟的抽象定义,这里可能每个人的做法有区别,有的是修改抽象类,添加异常描述

我的倾向是,把飞的行为分离出一个IFlyable接口,可以飞的鸟实现这个接口,鸵鸟就别实现了

这样其他一个纸飞机也可以实现这个接口,虽然其并不是一只鸟

TQ

我还是觉得,在以后定义某个类的时候如果是打算作为基类,那么可以考虑定义某种异常,这样的话,可以取得一个折衷(因为无法预料后期的变化)。

STST

不要预料,至少不要过多地预料,把现在的事情做好,DRY是第一原则,明天即使改了90%,我也可以很轻松地知道,还有10%可以用

追求大而全永远稳定的完美确实是徒劳,一旦开始过度设计,就很回头

TQ

而且在真实项目里,一般是不敢去修改的(除非这个项目你非常熟悉),否则你不敢预言哪些代码用到了基类,你敢随意给里面加东西或者移除。而早先能考虑到把某个行为提出来的"牛人"不是很多-?-【遇到这种情况,捏着鼻子骂一句,然后找一个自己觉得"过意的去"的方式解决了就完事了】

STST

没有测试保护的修改确实是靠运气,对再牛的人也是一样,自动化的一套测试,能极大地提高"修改"的信心

修改完,1分钟之类就能知道改对了还是错了,这种修改有了源代码管理器的配合,完全不用担心,但是如果要等半个月或者一年以后的一次上线操作才能验证你这次改动是正确还是错误的,那真的不知道哪里来信心

时间: 2024-10-26 00:25:28

鸵鸟非鸟?的相关文章

LSP-里氏替换原则

1 什么是里氏替换原则 里氏替换原则是由麻省理工学院(MIT)计算机科学实验室的Liskov女士,在1987年的OOPSLA大会上发表的一篇文章<Data Abstraction and Hierarchy>里面提出来的,主要阐述了有关继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中的蕴涵的原理.2002年,我们前面单一职责原则中提到的软件工程大师Robert C. Martin,出版了一本<Agile Software Development Princip

面向对象五大设计原则

以前一直认为程序中的类有使用到封装继承多态就是面向对象设计,其实不然 封装,继承,多态只是面向对象的三大特性,但是在设计程序的时候并不是说类的结构使用到了(或是体现出了)这三个特性就是面向对象, 其实真正的面向对象设计是要符合下面的五大原则, 面向对象的五大基本原则单一职责原则(SRP)开放封闭原则(OCP) 里氏替换原则(LSP) 依赖倒置原则(DIP) 接口隔离原则(ISP) 单一职责原则(SRP) ?      一个类应该有一个职责,仅有一个引起它变化的原因(最简单,最容易理解却最不容易做

设计模式之里氏代换原则(LSP)

里氏代换原则(Liskov Substitution Principle, LSP) 1 什么是里氏代换原则 里氏代换原则是由麻省理工学院(MIT)计算机科学实验室的Liskov女士,在1987年的OOPSLA大会上发表的一篇文章<Data Abstraction and Hierarchy>里面提出来的,主要阐述了有关继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中的蕴涵的原理.2002年,我们前面单一职责原则中提到的软件工程大师Robert C. Martin,

c++ 继承,组合

1.什么是继承 A继承B,说明A是B的一种,并且B的所有行为对A都有意义 eg:A=WOMAN B=HUMAN A=鸵鸟 B=鸟 (不行),因为鸟会飞,但是鸵鸟不会. 2.什么是组合 若在逻辑上A是B的“一部分”(a part of),则不允许B从A派生,而是要用A和其它东西组合出B. 例如眼(Eye).鼻(Nose).口(Mouth).耳(Ear)是头(Head)的一部分,所以类Head应该由类Eye.Nose.Mouth.Ear组合而成,不是派生而成 3.继承的优点和缺点 优点: 容易进行新

c++ 继承和组合的区别

1.什么是继承 A继承B,说明A是B的一种,并且B的所有行为对A都有意义 eg:A=WOMAN B=HUMAN A=鸵鸟 B=鸟 (不行),因为鸟会飞,但是鸵鸟不会. 2.什么是组合 若在逻辑上A是B的“一部分”(a part of),则不允许B从A派生,而是要用A和其它东西组合出B. 例如眼(Eye).鼻(Nose).口(Mouth).耳(Ear)是头(Head)的一部分,所以类Head应该由类Eye.Nose.Mouth.Ear组合而成,不是派生而成 3.继承的优点和缺点 优点: 容易进行新

【C++基础】类的组合

所谓类的组合是指:类中的成员数据是还有一个类的对象或者是还有一个类的指针或引用.通过类的组合能够在已有的抽象的基础上实现更复杂的抽象. 比如: 1.按值组合 #include<iostream.h> #include<math.h> class Point { public: Point(int xx,int yy)//构造函数 { x=xx; y=yy; cout<<"Point's constructor was called"<<e

Java/Android中的函数调用&amp;回调函数&amp;自定义回调函数

在做Android自定义控件时遇到要自定义回调函数的问题,想想自己还暂时没有那么精深的技术,赶紧返过头回来再重新研究Java中回调函数的问题.然而不幸的是,网上太多杂乱的帖子和博客都是转来转去,而且都是那一篇"C中的回调函数.....指针.....java....",一点看不出来是自己的思路,估计都是哪哪哪抄来的!(呵呵,要么就是吐槽对了,要么就是我水平太烂读不懂还妄加评论)还有一些很不错的文章,我会在最后参考中加上链接,大家可以看看. 那么来开始我们的正题--什么是回调函数? 我们一

简洁 Abstract Factory模式(3.1)

镇楼 在某些书中使用了二维图说明抽象工厂模式.非常好,可是yqj2065不喜欢他们的产品族/产品等级,改成品牌/产品类型. 抽象工厂模式(abstract factory pattern)较工厂方法模式强大之处.是一个工厂可以创建多个配套的产品. 日常生活中.抽象工厂模式比比皆是.比如服装厂能够生产配套的上衣/Tops.下装/Bottoms.电器公司如美的.海尔能够生产其品牌的冰箱.空调.电视机等. 抽象工厂模式的特点就是工厂接口中有两个以上的工厂方法. 例程 2-6 品牌公司 package

谈谈自己对面向对象的理解

三个特征 封装 把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏. 继承 继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展.其继承的过程,就是从一般到特殊的过程. 组合 如果鸟是可以飞的,那么鸵鸟是鸟么?鸵鸟如何继承鸟类?[美国某著名分析软件公司2005年面试题] 解析:如果所有鸟都能飞,那鸵鸟就不是鸟!回答这种问题时,不要相信自己的直觉!将直觉和合适的继承联系起来还需要一段时间. 根据题干