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