摘要:另载于现代对象设计主张组合优于继承。对象的有效范围,是指对象从创建到丢弃不再引用的这段时间,不包括等待被销毁的时间。对于继承也是同理,父类和子类应当有相同的有效范围。同理,级的对象不要持有级的对象。
另载于 http://www.qingjingjie.com/blogs/9
现代对象设计主张“组合优于继承”。总之无论组合还是继承,对象都成了涉及多个类的复合结构。
“对象的有效范围”,是指对象从创建到丢弃(不再引用)的这段时间,不包括等待被GC销毁的时间。可以近似认为是对象的生命期。
单例对象(Singleton)的有效范围几乎是整个应用的开启时间,Socket的有效范围通常是网络连接的持续时间,而一个临时的Integer则可能瞬间就被丢弃了。Let"s 注意,不同范围的对象/类,不能随意地组合/继承在一起。
1. 不同范围的对象避免打包在一起(代码有点多,如果嫌烦可以跳过1.先看2.)
反面教材:我们来看一个客户端程序,它通过socket与某个服务器保持通信,不断发消息并收取响应。
public class Communication { private Topic topic; private Socket socket; public Communication(String host, int port) { socket = new Socket(host, port); } public void close() { socket.close(); } public void setTopic(Topic topic) { this.topic = topic; } public String sendReceive(String msg) { return sendRecv_(topic, msg); } private String sendRecv_(Topic topic, String msg) { ... // 具体处理 } }
我们有两个主题(topic) A和B,以不同主题发的消息,服务器会做不同处理。我们一会儿用主题A发消息,一会用主题B发消息。代码如下:
Communication comm = new Communication(host, port); comm.setTopic(A); comm.sendReceive("Hello!"); comm.sendReceive("How are you?"); comm.setTopic(B); comm.sendReceive("Good morning!"); comm.sendReceive("Let"s begin"); comm.sendTopic(A); comm.sendReceive("How old are you?");
切换来切换去,真麻烦。如果你不嫌麻烦,假设给Communication再加一个域"config",平均每发100条消息,要切换一次config,平均每发10条消息,要切换一次topic,还是交替进行,烦不烦!
再想想,如果多个线程在使用comm对象呢? 呵呵呵,完蛋了。
Communication的有效范围与socket一致,而topic的有效范围就小于socket了,因此topic就不该放在这个类里。虽然sendReceive()可以少填一个参数,看似方便,但是引发了更多麻烦。
对于继承也是同理,父类和子类应当有相同的有效范围。
所以还是这么写吧:
comm.sendReceive(A, "Hello!"); comm.sendReceive(A, "How are you?"); comm.sendReceive(B, "Good morning!");
稍微有点麻烦呢
或者这么写:
class CommByTopic { private Communication comm; private Topic topic; // 构造函数省略 public String sendReceive(String msg) { return comm.sendReceive(topic, msg); } } CommByTopic onA = new CommByTopic(comm, A); onA.sendReceive(msg); onB.sendReceive(msg);
缺点是comm关闭后要注意不能继续使用onA。所以不要长时间持有onA对象,最好能局限在方法作用域内。
或者试试简洁的lamda~
Lamda in Java 8:
FunctiononA = msg -> comm.sendReceive(A, msg); onA.apply("Hello!"); onA.apply("How are you?");
Lamda in Scala:
val onA: String => String = comm.sendReceive(A, _) onA("Hello!") onA("How are you?") // 柯里化的写法 val onA = comm.sendReceive(A) _2. 大范围对象不要持有小范围对象
上面说的comm就是大范围对象,socket也是大范围对象,topic是小范围对象。它们生命长短不同。
如果大范围对象持有了小范围对象,你就要疲于切换,甚至担心线程安全性。反过来,小范围对象持有大范围对象,就好了。当然了,持有相同范围的对象也是好的。
对运行于IoC容器的程序尤其明显。来溜一段基于Spring MVC的应用代码:
@Component @Scope("singleton") //单例对象 public class Manager { @Autowired private Account account; public void freezeAccount() { account.freeze(); merge(account); } } @Component @Scope("request") //request范围的对象 public class Account { ... }
这样的代码在系统启动时就崩了,因为account还没出现。就算你给Manager标上@Lazy (延迟初始化),让它在账户A发来请求时才初始化,它也只能正确处理这次的请求。下次账户B再来请求时,它还是使用上次的A的account来操作,而不会用B的account。呵呵呵,完蛋了。
同理,session级的对象不要持有request级的对象。
对于Servlet和Filter也是如此,它们是近似于单例的对象,让它们持有一些配置数据和常量就行了,如果让它们持有当前的userId,也很危险。
再提醒一下,其实小范围对象持有大范围对象也不要滥用,一不小心就会让对象承担过多职责,有过多依赖。设计要从职责出发。
结语之所以要从“有效范围”的角度谈对象设计的问题,就是想给大家提供一个明确可操作的分析视角,这可比“设计哲学”容易多了。
不过光会这个还不够,知识要全面。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/64139.html
摘要:在同多个云提供商合作之前,请评估他们在计算存储和安全等方面的服务。企业必须在多个云供应商中做出抉择。在与多个云供应商合作时有一些策略和技巧,能够得到其优点同时限制了重复劳动和其他额外的工作。 在同多个云提供商合作之前,请评估他们在计算、存储和安全等方面的服务。企业必须在多个云供应商中做出抉择。亚马逊网络服务是行业巨头,而微软Azure则提供了一整套越来越有竞争力的服务。还有谷歌云平台对于那些...
摘要:从业务流程上,应得到以下信息主流程是什么条件备选流程是什么数据流向是什么关键的判断条件是什么测试用例设计完成以上两步则可进行测试用例设计,功能测试用例,应尽量考虑边界异常性能的情况,以便发现更多的隐藏问题。 为什么测试人员要参加需求分析?也就是进行测试需求分析的目的是什么? 第一、把用户需求...
摘要:需要结合其他测试用例设计的方法进行补充。比如边界值边界值在软件中边界值测试方法是发现错误能力最强的一种。其中,原因是表示输入条件,结果是对输入执行的一系列计算后得到的输出。与取值或,表示某状态不出现,则表示某状态出现。 ...
摘要:性能最好具有可量化可监测以及可改动的特性。下文是一份年的前端性能优化清单,阐述了作为前端开发人员,为了确保反馈速度以及浏览器兼容性我们需要考虑的问题。地图设计的决定违背了性能理念,所以他在这份清单内的顺序有待考虑。 2017前端性能优化清单 你开始使用渐进启动了么?是不是已经使用过React和Angular中tree-shaking和code-splitting两个工具?有没有用过Br...
阅读 3622·2021-11-19 09:40
阅读 3054·2019-08-30 15:54
阅读 2297·2019-08-30 15:44
阅读 3173·2019-08-29 15:35
阅读 3311·2019-08-29 12:22
阅读 2844·2019-08-28 18:01
阅读 3116·2019-08-26 13:54
阅读 882·2019-08-26 12:24