一 : ai多个对象怎么合并? ai对象编组的教程
ai中想要对对象进行合并编组,该怎么操作呢?下面我们就来看看详细的教程。
1、首先,在 AI 主界面中点击右侧面板中的“图层”面板。
2、然后,将要合并的对象选中,选中对象的方法是点击对象右侧的小圆圈。对象选中之后,会出现一个蓝色的小方块标识。
3、随后,按住键盘上的 shift 键,继续选择其他要合并的对象。在此提醒大家注意,如要选择多个对象,必须先按下shift 键,否则只能选中一个对象。
4、待要合并的对象全部选择好以后,点击左侧工具栏中的“选择工具”。
5、然后,在选中的对象上右击鼠标,在弹出的菜单中选择“编组”。
6、这时,在图层中就可以看到我们所选择的对象全部合并成了一个组。
7、这时,如果你要对整个组进行操作,只需选中组即可。但如果想要对组中的某个对象操作怎么办呢?我们可以点击组前的小箭头将其展开,接着再选中其中要操作的对象即可。
以上就是ai对象合并的教程,希望大家喜欢,请继续关注61阅读。
相关推荐:
AI怎么剪裁合并图片?
AI怎么取消编组? ai编组与取消编组的方法
ai线段断点怎么连接? ai两条线合并的两种方法
二 : 面向对象不是计算机编程的基本原子
在过去的25年里,编程世界发生了巨大的变化,如今,我们有大量的有用的、灵活的数据类型可以使用,但在25年前,你需要花大量的额外时间自己去构造这些类型。
C和Pascal语言——当时的标准语言——提供了少量的面向机器的数据类型:数字,指针,数组,形式上的字符串,以及把多种数据组合到一起的结构体或record。重要的是,以这些基本的类型为基石,我们可以构造出更多有趣的类型,例如栈,树,链接表,哈希表,可变数组等。
在Perl或Python,或Erlang语言里,我不需要考虑这些东西。我在使用list、string或array时,根本不关心它们能容纳多少元素,或放在内存的什么地方。最常使用的还有字典,同样,根本不担心它的容量或哈希冲突是如何避免的细节内容。
除此外,我仍然需要一些新的数据类型,但它们更多的是现有类型的一种变换,而不是重新构造。任意维度的vector实际就是array。一个RGB颜色值实际上一个3元tuple。一个多项式既可以是一个tuple,也可以说list。我惊奇于这些array,tuple,list,dictionary等数据类型大大的消除了我在大学课程里学到的那些基本数据类型上的不便。在实现一个平衡二叉树时,你的注意力放在如何让二叉树平衡,而不是痛苦的纠结于乱如麻的指针操作。
将已有的小方块搭建成一个新的建筑,这将会引起比小方块出现带来的更大的变化。这些小方块是如何出现的已经不是人们关心的重点。在很多的编程课程和教材中,本来很好的教学中突然出现了一批新词汇:对象,构造器,抽象基础类,以及私有方法。于是,下一次作业中,用简单的三元tuple来表达的RGB颜色值变成了由一个具有get、set方法,多高构造器的类来代替,更要命的,出现了大量的代码。
这就是为什么有人会不停的呼吁、解释为什么面向对象不是个好东西、会使编程失去乐趣的原因。但很少奏效。
并不是面向对象不好,或含有什么缺陷。而是面向对象不是计算机编程的基本原子,它们不是人们想象的天生就存在的。不设门槛的任意使用面向对象来解决问题会让代码变得臃肿和过度技术化,然而,很多人还是坚持锲而不舍的用对象来解决所有问题。这非常糟糕,因为这样做让人们辨不清面向对象风格的做法是否真的产生了使问题简化并易于理解的效果。
本文英文原文链接:OOP Isn't a Fundamental Particle of Computing
三 : 面向对象编程的弊端是什么?
[面向对象编程]面向对象编程的弊端是什么?网友Milo Yip对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
我想从一个游戏程序员的角度探讨OOP的一个问题──性能。
现时C++可以说是支持OOP范式中最为常用及高性能的语言。虽然如此,在C++使用OOP的编程方式在一些场合未能提供最高性能。 [1]详细描述了这个观点,我在此尝试简单说明。注意:其他支持OOP的语言通常都会有本答案中提及的问题,C++只是一个合适的说明例子。
历史上,OOP大概是60年代出现,而C++诞生于70年代末。现在的硬件和当时的有很大差异,其中最大的问题是内存墙_百度百科。
图1: 处理器和内存的性能提升比较,处理器的提升速度大幅高于内存[2]。图1: 处理器和内存的性能提升比较,处理器的提升速度大幅高于内存[2]。
跟据Numbers Every Programmer Should Know By Year:
图2:2014年计算机几种操作的潜伏期(latency)。图2:2014年计算机几种操作的潜伏期(latency)。
从这些数据,我们可以看出,内存存取成为现代计算机性能的重要瓶颈。然而,这个问题在C++设计OOP编程范式的实现方式之初应该并未能考虑得到。现时的OOP编程有可能不缓存友好(cache friendly),导致有时候并不能发挥硬件最佳性能。以下描述一些箇中原因。
1. 过度封装
使用OOP时,会把一些复杂的问题分拆抽象成较简单的独立对象,通过对象的互相调用去实现方案。但是,由于对象包含自己封装的数据,一个问题的数据集会被分散在不同的内存区域。互相调用时很可能会出现数据的cache miss的情况。
2. 多态
在C++的一般的多态实现中,会使用到虚函数表。虚函数表是通过加入一次间接层来实现动态派送。但在调用的时候需要读取虚函数表,增加cache miss的可能性。基本上要支持动态派送,无论用虚函数表、函数指针都会形成这个问题,但如果类的数目极多,把函数指针如果和数据放在一起有时候可放缓问题。
3. 数据布局
虽然OOP本身并无限制数据的布局方式,但基本上绝大部分OOP语言都是把成员变量连续包裹在一段内存中。甚至使用C去编程的时候,也通常会使用到OOP或Object-based的思考方式,把一些相关的数据放置于一个struct之内:
struct Particle { Vector3 position; Vector4 velocity; Vector4 color; float age; // ...};
网友蒙面大侠对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
不忘初心,方得始终。
前面几个答案都说到了点子上:OOP最大的弊端,就是很多程序员已经忘记了OOP的初心,潜意识中把OOP教条主义化(如同对GOTO语句的禁忌一般),而不是着眼于OOP着力达到的、更本质的目标,如:
- 改善可读性
- 提升重用性
但是OOP最重要的目标,其实是OCP,即「开闭原则」。这一点很多答案都没有提到。
遵循开闭原则设计出的模块具有两个主要特征:
(1)对于扩展是开放的(Open for extension)。这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。
(2)对于修改是关闭的(Closed for modification)。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、DLL或者.EXE文件,都无需改动。
这就是为什么会有「用多态代替switch」的说法。在应该使用多态的地方使用switch,会导致:
1 - 违反「开放扩展」原则。假如别人的switch调用了你的代码,你的代码要扩展,就必须在别人的代码里,人工找出每一个调用你代码的switch,然后把你新的case加进去。
2 - 违反「封闭修改」原则。这是说,被switch调用的逻辑可能会因为过于紧密的耦合,而无法在不碰switch的情况下进行修改。
但是OCP不是免费的。如果一个模块根本没有扩展的需求,没有多人协作的需求,花时间达成OCP又有什么意义呢?设计类关系的时候忘记了OOP的初心,可能就会写出很多没有帮助的类,白白浪费人力和运行效率。
所以,假如所有代码都是你一个人维护,没有什么扩展的需求,那么多用一些switch也未尝不可;假如你的代码是要被别人使用或者使用了别人的代码,OOP很可能就是你需要用到的工具。
除了OOP,Type class和Duck Typing都是可以帮助你达成OCP原则的工具。当然,如果你使用的语言是Java,这两种工具都不用想了。
网友蒙面大侠对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
很多时候为了OO而OO就是它最大的弊端。
网友蒙面大侠对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
弊端是,没有人还记得面向对象原本要解决的问题是什么。
1、面向对象原本要解决什么(或者说有什么优良特性)
似乎很简单,但实际又很不简单:面向对象三要素封装、继承、多态
(警告:事实上,从业界如此总结出这面向对象三要素的一刹那开始,就已经开始犯错了!)。
封装:封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项,或者叫接口。
有了封装,就可以明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者;而外部调用者也可以知道自己不可以碰哪里。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
继承+多态:继承和多态必须一起说。一旦割裂,就说明理解上已经误入歧途了。
先说继承:继承同时具有两种含义:其一是继承基类的方法,并做出自己的扩展——号称解决了代码重用问题;其二是声明某个子类兼容于某基类(或者说,接口上完全兼容于基类),外部调用者可无需关注其差别(内部机制会自动把请求派发[dispatch]到合适的逻辑)。
再说多态:基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同。
很显然,多态实际上是依附于继承的第二种含义的。让它与封装、继承这两个概念并列,是不符合逻辑的。不假思索的就把它们当作可并列概念使用的人,显然是从一开始就被误导了。
实践中,继承的第一种含义(实现继承)意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
继承的第二种含义非常重要。它又叫“接口继承”。
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
归一化使得外部使用者可以不加区分的处理所有接口兼容的对象集合——就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,如果你需要,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
归一化的实例:
a、一切对象都可以序列化/toString
b、一切UI对象都是个window,都可以响应窗口事件。
——必须注意,是一切(符合xx条件的)对象皆可以做什么,而不是“一切皆对象”。后者毫无意义。
显然,归一化可以大大简化使用者的处理逻辑:这和带兵打仗是类似的,班长需要知道每个战士的姓名/性格/特长,否则就不知道该派谁去对付对面山坡上的狙击手;而连长呢,只需知道自己手下哪个班/排擅长什么就行了,然后安排他们各自去守一段战线;到了师长/军长那里,他更关注战场形势的转变及预期……没有这种层层简化、而是必须直接指挥到每个人的话,累死军长都没法指挥哪怕只是一场形势明朗的冲突——光一个个打完电话就能把他累成哑巴。
软件设计同样。比如说,消息循环在派发消息时,只需知道所有UI对象都是个window,都可以响应窗口消息就足够了;它没必要知道每个UI对象究竟是什么——该对象自己知道收到消息该怎么做。
合理划分功能层级、适时砍掉不必要的繁杂信息,一层层向上提供简洁却又完备的信息/接口,高层模块才不会被累死——KISS是最难也是最优的软件设计方法,没有之一。
总结:面向对象的好处实际就这么两点。
一是通过封装明确定义了何谓接口、何谓接口内部实现、何谓接口的外部调用者,使得大家各司其职,不得越界;
二是通过继承+多态这种内置机制,在语言的层面支持归一化的设计,并使得内行可以从代码本身看到这个设计——但,注意仅仅只是支持归一化的设计。不懂如何做出这种设计的外行仍然不可能从瞎胡闹的设计中得到任何好处。
显然,不用面向对象语言、不用class,一样可以做归一化的设计(如老掉牙的泛文件概念、游戏行业的一切皆精灵),一样可以封装(通过定义模块和接口),只是用面向对象语言可以直接用语言元素显式声明这些而已;
而用了面向对象语言,满篇都是class,并不等于就有了归一化的设计。甚至,因为被这些花哨的东西迷惑,反而更加不知道什么才是设计。
2、人们以为面向对象是什么、以及因此制造出的悲剧以及闹剧
误解一、面向对象语言支持用语言元素直接声明封装性和接口兼容性,所以用面向对象语言写出来的东西一定更清晰、易懂。
事实上,既然class意味着声明了封装、继承意味着声明了接口兼容,那么错误的类设计显然就是错误的声明、盲目定义的类就是无意义的喋喋不休。而错误的声明比没有声明更糟;通篇毫无意义的喋喋不休还不如错误的声明。
除非你真正做出了漂亮的设计,然后用面向对象的语法把这个设计声明出来——仅仅声明真正有设计、真正需要人们注意的地方,而不是到处瞎叫唤——否则不可能得到任何好处。
一切皆对象实质上是在鼓励堆砌毫无意义的喋喋不休。大部分人——注意,不是个别人——甚至被这种无意义的喋喋不休搞出了神经质,以至于非要在喋喋不休中找出意义:没错,我说的就是设计模式驱动编程,以及如此理解面向对象编程。
误解二、面向对象三要素是封装、继承、多态,所以只要是面向对象语言写的程序,就一定“继承”了语言的这三个优良特性。
事实上,如前所述,封装、继承、多态只是语言层面对良好设计的支持,并不能导向良好的设计。
如果你的设计做不出真正的封装性、不懂得何谓归一化,那它用什么写出来都是垃圾。
误解三、把软件写成面向对象的至少是无害的。
要了解事实上是什么,需要先科普几个概念。
什么是真正的封装?
——回答我,封装是不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”?
显然不是。
如果功能得不到满足、或者未曾预料到真正发生的需求变更,那么你怎么把一个成员变量/函数放到private里面的,将来就必须怎么把它挪出来。
你越瞎搞,越去搞某些华而不实的“灵活性”——比如某种设计模式——真正的需求来临时,你要动的地方就越多。
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在;而不是外部调用者为了完成某个功能、却被碍手碍脚的private声明弄得火冒三丈;最终只能通过怪异、复杂甚至奇葩的机制,才能更改他必须关注的细节——而且这种访问往往被实现的如此复杂,以至于稍不注意就会酿成大祸)。
一个设计,只有达到了这个高度,才能真正做到所谓的“封装性”,才能真正杜绝对内部细节的访问。
否则,生硬放进private里面的东西,最后还得生硬的被拖出来——当然,这种东西经常会被美化成“访问函数”之类渣渣(不是说访问函数是渣渣,而是说因为设计不良、不得不以访问函数之类玩意儿在封装上到处挖洞洞这种行为是渣渣)。
一个典型的例子,就是C++的new和过于灵活的内存使用方式之间的耦合。
这个耦合就导致了new[]/delete[]、placement new/placement delete之类怪异的东西:这些东西必须成对使用,怎么分配就必须怎么释放,任何错误搭配都可能导致程序崩溃——这是为了兼容C、以及得到更高执行效率的无奈之举;但,它更是“抽象层次过于复杂,以至于无法做出真正透明的设计”的典型案例:只能说,c++设计者是真正的大师,如此复杂的东西在他手里,才仅仅付出了如此之小的代价。
(更准确点说,是new/delete和c++的其它语言元素之间是非正交的;于是当同时使用这些语言元素时,就不可避免的出现了彼此扯淡的现象。即new/delete这个操作对其它语言元素非透明:在c++的设计里,是通过把new/delete分成两层,一是内存分配、二是在分配的内存上初始化,然后暴露这个分层细节,从而在最大程度上实现了封装——但比之其它真正能彼此透明的语言元素间的关系,new/delete显然过于复杂了)
这个案例,可以非常直观的说明“设计出真正对外透明的封装”究竟会有多难。
接口继承真正的好处是什么?是用了继承就显得比较高大上吗?
显然不是。
接口继承没有任何好处。它只是声明某些对象在某些场景下,可以用归一化的方式处理而已。
换句话说,如果不存在“需要不加区分的处理类似的一系列对象”的场合,那么继承不过是在装X罢了。
封装可应付需求变更、归一化可简化(类的使用者的)设计:以上,就是面向对象最最基本的好处。
——其它一切,都不过是在这两个基础上的衍生而已。
换言之,如果得不到这两个基本好处,那么也就没有任何衍生好处——应付需求变更/简化设计并不是打打嘴炮就能做到的。
了解了如上两点,那么,很显然:
1、如果你没有做出好的抽象、甚至完全不知道需要做好的抽象就忙着去“封装”,那么你只是在“封”和“装”而已。
这种“封”和“装”的行为只会制造累赘和虚假的承诺;这些累赘以及必然会变卦的承诺,必然会为未来的维护带来更多的麻烦,甚至拖垮整个项目。
正是这种累赘和虚假的承诺的拖累,而不是所谓的为了应付“需求改变”所必需的“灵活性”,才是大多数面向对象项目代码量暴增的元凶。
2、没有真正的抓到一类事物(在当前应用场景下)的根本,就去设计继承结构,是必不会有所得的。
不仅如此,请注意我强调了在当前应用场景下。
这是因为,分类是一个极其主观的东西,不存在普适的分类法。
举例来说,我要研究种族歧视,那么必然以肤色分类;换到法医学,那就按死因分类;生物学呢,则搞门科目属种……
想象下,需求是“时尚女装”,你却按“窒息死亡/溺水死亡/中毒死亡之体征”来了个分类……你说后面这软件还能写吗?
类似的,我遇到过写游戏的却去纠结“武器装备该不该从游戏角色继承”的神人。你觉得呢?
事实上,游戏界真正的抽象方法之一是:一切都是个有位置能感受时间流逝的精灵;而某个“感受到时间流逝显示不同图片的对象”,其实就是游戏主角;而“当收到碰撞事件时,改变主角下一轮显示的图片组的”,就是游戏逻辑。
看看它和“武器装备该不该从游戏角色继承”能差多远。想想到得后来,以游戏角色为基类的方案会变成什么样子?为什么会这样?
最具重量级的炸弹则是:正方形是不是一个矩形?它该不该从矩形继承?如果可以从矩形继承,那么什么是正方形的长和宽?在这个设计里,如果我修改了正方形的长,那么这个正方形类还能不能叫正方形?它不应该自然转换成长方形吗?什么语言能提供这种机制?
造成这颗炸弹的根本原因是,面向对象中的“类”,和我们日常语言乃至数学语言中的“类”根本就不是一码事。
面向对象中的“类”,意思是“接口上兼容的一系列对象”,关注的只不过是接口的兼容性而已(可搜索 里氏代换);关键放在“可一视同仁的处理”上(学术上叫is-a)。
显然,这个定义完全是且只是为了应付归一化的需要。
这个定义经常和我们日常对话中提到的类概念上重合;但,如前所述,根本上却彻彻底底是八杆子打不着的两码事。
就着生活经验滥用“类”这个术语,甚至依靠这种粗浅认识去做设计,必然会导致出现各种各样的偏差。这种设计实质上就是在胡说八道。
就着这种胡说八道来写程序——有人觉得这种人能有好结果吗?
——但,几乎所有的面向对象语言、差不多所有的面向对象方法论,却就是在鼓励大家都这么做,完全没有意识到它们的理论基础有多么的不牢靠。
——如此作死,焉能不死?!
——你还敢说面向对象无害吗?
——在真正明白何谓封装、何谓归一化之前,每一次写下class,就在错误的道路上又多走了一步。
——设计真正需要关注的核心其实很简单,就是封装和归一化。一个项目开始的时候,“class”写的越早,就离这个核心越远。
——过去鼓吹的各种面向对象方法论、甚至某些语言本身,恰恰正是在怂恿甚至逼迫开发者尽可能早、尽可能多的写class。
误解四、只有面向对象语言写的程序才是面向对象的。
事实上,unix系统提出泛文件概念时,面向对象语言根本就不存在;游戏界的精灵这个基础抽象,最初是用C甚至汇编写的;……。
面向对象其实是汲取以上各种成功设计的经验才提出来的。
所以,面向对象的设计,不必非要c++/java之类支持面向对象的语言才能实现;它们不过是在你做出了面向对象的设计之后,能让你写得更惬意一些罢了——但,如果一个项目无需或无法做出面向对象的设计,某些面向对象语言反而会让你很难受。
用面向对象语言写程序,和一个程序的设计是面向对象的,两者是八杆子打不着的两码事。纯C写的linux kernel事实上比c++/java之类语言搞出来的大多数项目更加面向对象——只是绝大部分人都自以为自己到处瞎写class的面条代码才是面向对象的正统、而死脑筋的linus搞的泛文件抽象不过是过程式思维搞出来的老古董。
——这个误解之深,甚至达到连wiki词条里面,都把OOP定义为“用支持面向对象的语言写程序”的程度。
——恐怕这也是没有人说泛文件设计思想是个骗局、而面向对象却被业界大牛们严厉抨击的根本原因了:真正的封装、归一化精髓被抛弃,浮于表面的、喋喋不休的class/设计模式却成了”正统“!
借用楼下PeytonCai朋友的链接:
名家吐槽:面向对象编程从骨子里就有问题
————————————————————————————
总结: 面向对象其实是对过去成功的设计经验的总结。但那些成功的设计,不是因为用了封装/归一化而成功,而是切合自己面对的问题,给出了恰到好处的设计。
让一个初学者知道自己应该向封装/归一化这个方向前进,是好的;用一个面向对象的条条框框把他们框在里面、甚至使得他们以为写下class是完全无需思索的、真正应该追求的是设计模式,则是罪恶的。
事实上,class写的越随意,才越需要设计模式;就着错误的实现写得越多、特性用得越多,它就越发的死板,以至于必须更加多得多的特性、模式、甚至语法hack,才能勉强完成需求。
只有经过真正的深思熟虑,才有可能做到KISS。
到处鼓噪的面向对象编程的最大弊端,是把软件设计工作偷换概念,变成了“就着class及相关教条瞎胡闹,不管有没有好处先插一杠子”,甚至使得人们忘记去关注“抽象是否真正简化了面对的问题”。
一言以蔽之:没有银弹。任何寄希望于靠着某种“高大上”的技术——无论是面向对象、数据驱动、消息驱动还是lambda、协程等等等等——就能一劳永逸的使得任何现实问题“迎刃而解”的企图都是注定要失败的,都不过是外行的意淫而已;靠意淫来做设计,不掉沟里才怪。
想要做出KISS的方案,就必须对面对的问题有透彻的了解,有足够的经验和能力,并经过深思熟虑,这才能做出简洁的抽象:至于最终的抽象是面向对象的、面向过程的还是数据驱动/消息驱动的,甚至是大杂烩的,都是可能的。只要这个设计能做到最重要、也是最难的KISS,那它就是个好设计。
的确有成功的经验、正确/合理的方向:技术无罪,但,没有银弹。
网友冯东对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
面向对象的弊端在于作为一种建模技术没有很好的定义自己的适用范围。面向对象脱胎的环境有两个重要因素,一是基于 WIMP (Window, Icon, Menu, Pointer) 的图形化界面,二是早期提供图形界面接口的机器缺乏代码级别之外的组件管理方式 (比如 Unix 的进程和 IPC)。
面向对象在 WIMP 的环境中是很必要也是很成功的。原因是 WIMP 环境需要重量的实现继承提供的重用,WIMP 的对象种类能很好的被单继承模拟,WIMP 的属性和类别容易区分。而面向对象扩展到 WIMP 之外的环境中就失败了:实际世界是多纬度的,属性和类别不好区分。红苹果是 color 属性为 red 的苹果,还是 Apple 的子类?实际世界的工具是用来完成任务的。而不是象 WIMP 那样构建一个虚拟的空间化界面。《人月神话》指出,编写 reusable code 比编写普通 code 至少要多花三倍的工作量。而面向对象的模糊了代码的重用和使用。使被重用的代码的依赖复杂化。导致很多不适合被重用的代码被重用。编写代码时要过分考虑重用的可能性。其它管理复杂度的机制越来越流行。
网友PeytonCai对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
名家吐槽:面向对象编程从骨子里就有问题
我觉得只有封装和接口才是软件设计的精髓,这也应该是为什么GO语言了只实现了接口这个特性。
网友飞龙对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
过度封装而降低性能,其实这两个东西是个trade-off。
网友李遥对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
明白背后的运行原理之后,懂得怎么避免陷阱的话没有弊端
网友jun lee对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
经历过多次需求变更的人,才能深刻理解面向对象的意义。
有的人项目做完后,人一走,这个项目就死了,后来人根本无法接手,一团麻的关系。别人只能重新写。需求一变,他能产生一堆BUG,弄的产品经理都不敢改需求了。
有的人,需求变更不怕,随便搞几下就可以了。
网友pansz对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
可能我的看法有些简单粗暴。但是我希望表达一下我的「一家之言」。
面向对象编程有很多用途,很多用法,但是我们会发现「设计模式驱动」成为了非常有代表性的流派。有时我们会疑惑,为什么会有设计模式,为什么面向对象编程会出现如此恐怖的这么复杂的类关系。
我认为这是为了满足「增量式开发」。它假定几个前提:
1,将一份代码测试调试稳定所需要花的时间,远远大于撰写代码的时间。
2,已经通过测试的旧代码永远不修改,只对新代码进行新增,是最可靠的开发方式。
3,你经常需要重用没有源代码的库,并且扩展和修改其功能。
很多面向对象的设计,其实是为了满足一个很基本的目的:不修改旧代码,只新增代码。代码只增不改,所以才会出现「继承」这种东西。因为你不需要原有的源代码也不需要修改原有的类,而是派生一个类增加新的方法。「继承」的本来目的看起来就是为了解决这个问题。
因此,很多类层次关系的设计,不是为了更高的效率,不是为了代码看起来更清晰,而是为了「保证旧代码不需要被修改」这个目的。
不过,这是不是现实呢?在某些公司,这是事实,在很多公司,以上的假定不是现实。
1,很多代码并没有经过长时间的充分的测试,因而没有必要为了不浪费原有测试资源而拒绝修改旧代码。
2,修改旧代码在大多数公司是不可避免的。
3,很多时候我们提倡读懂库的源代码,而不会盲目使用无源代码的库。
4,现在流行的敏捷开发模型中宣传要拥抱变化。在敏捷模型中,测试案例是被固化的,而代码与架构都可以经常被修改,换句话说,不修改旧代码,以及通过类层次关系设计系统架构,这些特性对敏捷来说都不重要,甚至背道而驰。
我的看法与 酷壳「如此理解面向对象编程」恰恰相反,他认为过度 OO 设计是敏捷带来的后果,而我认为敏捷开发恰恰使得过度 OO 设计变得毫无意义。——那些过度 OO 设计的人并没有真正的懂敏捷。真正的敏捷是在保证测试案例通过的前提下,直接简单粗暴的修改源代码,而不是为了避免修改三行源代码制造出十多个类。
如果我们的开发基于「旧代码就是需要被经常修改的」,那么面向对象中的一部分特性其实变成了毫无意义的累赘。当然这并意味着 OOP 完全无用,在这个前提下,其实我们需要重新审视面向对象的程序设计,取其精华去其糟粕。
封装性,继承性,多态性,这些特性在敏捷开发的情况下还那么重要吗?
封装性:在所有代码都公开,随时可能被修改被重构的情况下,封装性意义没有想象的那么大,虽然它仍然有价值。它的价值在于使程序行为更可预期。
继承性:继承的本目的是在不修改代码的情况下扩展代码的功能,但如果我们能够自由修改代码,那继承在绝大多数时候并不是最佳选择。事实上,巨大的类设计开销往往都是继承性带来的。继承性在今天不但没有太大的价值,反而常常有害。(这里所说的继承性指的是 invalid s 所说的第一种情况,对他说的第二种情况,我不认为那算作继承性,而应该被算作多态性。)
多态性:多态性要求一个函数在接受不同的类型对象作为参数时自动表现出不同的行为。或者说要求不同类型的对象都可以调用相同名字的方法,并且自己处理应该有的不同行为。——这个特性在今天依然重要,而且是最重要的特性,它的存在意味着 OOP 的价值仍然存在——即便对于非 OOP 的程序设计语言。只要应用了多态性思想,那么其实它就是 OOP 的。考虑一下 Linux 中的「一切皆文件」的概念,其实换句话说就是对一切对象皆可调用用于文件的那些方法,这妥妥的是「多态性」。图形界面的编程 API 中,一切对象皆可 Paint(),一切对象皆可 toString(),这妥妥的也是「多态性」。
从某种意义上说,「模板」这个一般认为不属于 OOP 的特性,却非常好的体现了多态性。模板允许同名函数接收不同类型的参数自动表现出不同的行为(虽然编译器实际上是生成了多个函数,但从编程的角度来讲它其实是一个函数)。因此我认为懂得模板技术在 OOP 设计中也非常重要,他用更小的开销更好的可读性实现了 OOP 思想。
结语:很多时候,巨大的类关系设计开销是为了避免修改旧代码而不是为了使代码更可读。而在修改旧代码不可避免或者根本不需要避免的时候,很多类关系设计开销是不必要的,我们在使用面向对象设计方法时,最好是取其精华去其糟粕,避免那些额外的不必要的类设计开销,以提升代码的可读性为目标来进行设计,而不要以避免修改代码为目标进行设计,在这种情况下 OOP 仍然有其存在的价值。
网友朱娜对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
太容易走神
网友Sting对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
解决所有复杂问题的不二法门是分而治之,不同的编程语言的做法不同,比如C是面向过程(程序=算法+数据结构),C++/Java是面向对象,Golang是基于连接和组合。面向对象编程的基本思路是先从问题中提取主要概念,然后用类表示这些概念,再用封装、继承、多态设计这些类,形成产品的基本组件。根据问题的实际需要,组合这些组件,从而形成整个产品。
面向对象编程的核心是封装、继承、多态。这三板斧中,封装和多态没什么问题。问题比较大的是继承,继承的主要意图是复用代码,但这种复用其实和高内聚低耦合的基本设计原则是矛盾的:哪些代码应该放入父类,哪些应该放在子类,边界常常是模糊的;而且,由于父类不论是接口设计和内部实现都会影响到子类,(甚至有时候为了满足某个子类的特定需求,常常在父类中添加私货,而有时候由于父类的实现考虑不周,也需要在子类中做workaround),导致在软件演化的过程中,对父类/接口的改进是很困难的。这就反过来要求程序员在最初设计接口/基类/继承体系的时候,需要对产品需求有非常深入的了解,而且希望在未来,这些需求比较稳定,不会发生非常大的变化。但是,在互联网/移动互联网时代,一切都变的非常快,崇尚试错、敏捷、快速迭代,面向对象编程构造出来的继承体系很难适应这种开发需求。
作为对比,我们可以看看Unix的代码复用思路。Unix推崇每个程序只解决一个问题,做到最好,然后提供丰富的输入/输出(即接口)跟外部交互。解决新的问题则通过组合不同的程序来完成。这种代码复用哲学产生的效果是惊人的,像grep, awk, wc, sed等一大堆Unix工具,这么多年来一直在用。这种哲学,其实就是基于连接和组合--Golang的核心语言特征。很有意思的是,Golang恰恰选择了不支持继承。而没有继承就谈不上面向对象,因此Golang是反对面向对象编程范式的。
网友mu peng对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
间接的导致一些程序员对继承的滥用
网友kubisoft对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
大部分问题是面向对象无法解决的。能够采用面向对象简化模型得到代码重用性的时候,就用;不能的时候,就放弃,不要到处都用。
有的场合下,面向对象会增加代码复杂度,增加维护的困难。
除了面向对象,我们还有很多办法来实现我们要做的功能。不谈更加高端大气的函数式编程,其实用一串switch case或者else if就可以实现,代码还简单。
所有需要的东西都放在一起,同一个文件里面同一段代码,阅读起来并不困难,很整齐。某个case下面太长的时候,也可以独立出来包装成一个函数调用;你爱放在同一个文件里面还是另开一个文件都可以。
可是面向对象是一个很重量级的方法。你得设计类的继承关系。得写类的声明,得实现不同的虚方法;实现时要注意是否要调用父类的方法。调试的时候经常搞不清楚到底执行哪个类的虚方法了。而用一大堆switch case,语句执行顺序一目了然。
可能还是觉得代码层级过多?有很多办法可以整理得更干净。用了面向对象,省了switch case,却要写更多的virtual function的声明和实现。
业务逻辑本身是复杂的,无法避免。没有银弹。
网友cgi scott对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
这个世界的天生属性,就是从不同的角度去看,就会有不同的风景。
面向对象就是一种视角,去描述和抽象现实世界。
不存在一个视角看遍并领略所有景色,或者有些风景只有某些角度才领悟体会。
所以,面向对象去解决特定问题得时候就是不够好,或是说有更好得方法。
比如,事物之间关联很多,在不同的层面有不同的关联,面向对象难以从一个层面切入划分关系。
网友蒙面大侠对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
作为C原教旨教徒和C++一生黑以及从不用类的Python党怒答。有函数有数据结构就可以了,面向对象就是个骗局………
网友林建入对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
指着 C++/Java 说面向对象是错的,就像指着一个太监说全世界的男人都不行一样。
问题不是出在“面向对象”上,而是出在“如何实现面向对象”上。许多人谈论面向对象,都是在谈论 C++ 的面向对象,或者 Java 的面向对象。这是因为许多人第一次接触面向对象这一编程理念,就是来源于这些 C 家族的后继语言。但仔细阅读他们的观点,会发现尽管他们谈论的观点来自实际的工程项目体验,有一定的道理,但是他们似乎并没有意识到自己在谈论的只是“C++/Java 实现的面向对象”有多糟糕,却把它当成了“面向对象思想本身”有多糟糕。
关于 C++/Java 实现面向对象带来的弊端已经有许多人回答了。我并没有什么新鲜的观点。但是关于面向对象本身,这个问题下面的大多数答案其实都没有真正谈及。
因为如果你要考察面向对象思想本身,那必须要考察 Smalltalk 这门语言。因为全世界第一次出现面向对象编程这一思想,就来自这门语言。
当然,为了避免误解,我应当指明,object、instance、class 这些思想并非是由 Smalltalk 所创造的,而是出现得很早,甚至在 lisp 出现后不久就有了相关讨论,并且也有了一些零散的实验性实现例如 Simula 语言,但 Smalltalk 的作者 Alan Kay 是第一个系统性提出的 Object-Oriented-Programming 的人。因此,现在也把 Smalltalk 视为面向对象编程的真正开端。
一些朋友可能会奇怪,说这些有什么意义呢?C++/Java 难道不是比 Smalltalk 更厉害得多吗——毕竟你看 Smalltalk 好像都没人用了,而 C++/Java 却享誉世界。就算是 Smalltalk 是面向对象编程的立派者,这对我们现在讨论面向对象思想又有什么影响,难道我用 C++/Java 所理解的面向对象还和 Smalltalk 的不同?您还真别说,确实是不同。
在 Smalltalk 里的“对象”实际上是一种高级的动态模块。一个 Smalltalk 程序,是由一系列这样的高级动态模块构成的。你可以在程序运行时(注意是运行时,而不是编译时)动态的修改、替换任意模块,而无需停止整个程序。
之所以在模块二字前加上“高级”和“动态”,实际上是对比其他非面向对象语言而言的。
例如 C 语言中你可以把 printf() 理解为一个控制台输出模块。但是这样的模块,用起来容易,却不太灵活——如果你有实际的编程经验肯定能有所体会。例如,你能够在程序运行的过程中,动态的替换掉 printf() 这个模块吗?你能够动态的改变 printf() 这个模块的代码吗?你能够动态的获取 printf() 这个模块的基本信息吗(参数列表,返回值甚至代码)?
别说,你还真的能,因为你可以通过各种技巧间接的实现这一点,但是这这是你的发明创造,而不是 C 语言本身的特性。所以我们可以知道,C 语言对模块的支持仅仅止步于函数级别。
Smalltalk 的亮点就在于,它在语言层面引入了一种称为“对象”的高级动态模块系统。一个 Smalltalk 程序由一系列的高级动态模块构成,每个模块之间通过通信进行协同。
也就是说,Smalltalk 所秉承的面向对象思想使得整个软件系统的可分割性和可组合性迈上了一个新台阶。这是面向对象思想的光辉所在。
现在我们回过头来看看 C++ 和 Java 中的面向对象。
事实上,C++ 和 Java 在实现面向对象的路途上遇到的第一道坎是他们本身都是静态类型的语言。也就是说,这类语言的设计信条是一切结构皆须预先描述,因为编译器要检查。于是没什么悬念的的就走上了 Class-based OOP 这条路(另一条路是 Prototype-based OOP)。
Class-based OOP 的一个特征是对象的结构需要预先声明,并且在运行过程中不允许改变—— C++ 和 Java 的作者有一千个理由这么干,最基本的原因就是性能考虑——但这样做的代价首先就削弱了系统的动态性。
更糟糕的是,C++ 和 Java 中,连对象的可替换性也需要预先声明。我这么说一部分朋友可能没办法马上反应过来。其实就是说,在 Smalltalk 中,我们可以用任何一个对象随意替换掉另外一个,只要他们对外界而言行为一致,那么系统依然可以正常运行,这一点,在大家更熟悉的 Ruby、Javascript 等语言中,被称为 Duck-Type 概念。
在 C++ 和 Java 中,你不能随意找个对象 x 来替换掉另外一个对象 y。即使他们拥有完全相同的行为也不行。因为 C++ 和 Java 是 Class-based OOP 所以连可替换性也需要预先声明!这种声明方式就是让无数人潸然泪下的——继承!
即使一个对象 x 和 y 的行为是完全一样的,你也不能用 x 去替换 y。允许你替换的唯一前提是,x 被声明为继承自 y 的。
在有空的时候我们可以再详细讨论一下继承。但是在这里,简单的来说,“继承”是一人分饰两角的典型——它既作为代码复用的一种手段,同时又成为了可替换性的一种声明。这种设计非常失败,难以使用到直接导致了面向对象在 C++ 和 Java 中成为了一个阉割后的太监。
为了弥补继承的这种缺陷,于是引入了 Interface (只表明可替换性,不复用代码),但这也改变不了什么了。毕竟 Interface 竟然也开始互相玩起了继承的游戏……
于是最后,我们看到,尽管 C++ 和 Java 一直声称自己是面向对象的,但是和 Smalltakl 之类的语言一比,这种面向对象的实现几乎是个去势后的太监,他们敢自称为男人真是个笑话。
所以,如果可以说得直白一些的话,C++ 本质上只能算是一个带有少量面向对象支持的 C,而 Java 更像是一个去掉了指针,带上了垃圾回收的 C++。
但再怎么说,面向对象这回事,在 C++ 和 Java 之类的语言里简直都只能算个点缀(可怕的是这个点缀会花掉你 70% 的学习时间)。
对于面向对象。在结构层次,对象之所以高级,是因为它具有动态性。它是在程序运行中动态构造(可以构造一个,也可以构造一百个),动态销毁的;在抽象层次,对象之所以高级,是因为它具有隔离性。它并不直接依赖于另一个对象,而是通过通信来与之协作的。另外,对象的可替换性也是系统弹性的关键。
当然我们都知道,实现上述特性在目前的编译技术条件下,很难保证高性能。所以这就是为什么 C++/Java 之类的语言要自我阉割的原因。这里面不得不说确实是透着一丝无奈。
但是既然是讨论面向对象,咱们还是区别清楚。不要因为 C++ 和 Java 坏了你的心情,你就从此再也不看面向对象一眼了。这样他们会很委屈的。
网友蒙面大侠对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
『它禁锢了码农的思想』
网友张李对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
在《领域驱动设计》中提出了“边界”、“聚合”、“聚合根”这些概念,用以处理类间关系过度复杂的问题。值得一看。
——————
手机打字不便,容后补上一些自己的理解
网友魏敬贤对[面向对象编程]面向对象编程的弊端是什么?给出的答复:
“对象”的概念适用于“一个一个”的东西,当面对“一坨一坨的”、“一滩一滩连起来的”、“无处不在的”、“无限长的一条”、“分不清个的”东西时,就晕了。
61阅读| 精彩专题| 最新文章| 热门文章| 苏ICP备13036349号-1