华为技术专家教你重构代码将值对象改为引用对象
动机
一个数据结构中可能包含多个记录,而这些记录都关联到同一个逻辑数据结构。例如,我可能会读取一系列订单数据,其中有多条订单属于同一个顾客。遇到这样的共享关系,既能将顾客信息作为值对象看待,也能将其视为引用对象: 若将其视为值对象,则每份订单数据中都会复制顾客的数据 若将其视为引用对象,对于一个顾客,就只有一份数据结构,会有多个订单与之关联
若顾客数据永不修改,则两种方式都合理。 把同一份数据复制多次可能会造成一点困扰,但这种情况也
很常见,不是太大问题。过多的数据复制有可能会造成内存占用的问题,但就跟所有性能问题一样,这种情况并不常见。
若共享的数据需要更新,将其复制多份的做法就会遇到巨大困难。此时我必须找到所有副本,更新所有对象。漏掉一个副本没更新,就会导致数据不一致。这时,考虑将多份数据副本变成单一的引用,这样对顾客数据的修改就会立即反映在该顾客的所有订单中。
把值对象改为引用对象会带来一个结果:对于一个客观实体,只有一个代表它的对象。这通常意味着我会需要某种形式的仓库,在仓库中可以找到所有这些实体对象。只为每个实体创建一次对象,以后始终从仓库中获取该对象。 做法
为相关对象创建一个仓库(若还没这样的一个仓库)。 确保构造器有办法找到关联对象的正确实例。修改宿主对象的构造器,令其从仓库中获取关联对象。每次修改后测试。 案例
订单Order类,其实例对象可从一个JSON文件创建。用来创建订单的数据中有一个顾客(customer)ID,我们用它来进一步创建Customer对象。 package com.javaedge.refactor.ttt; import lombok.AllArgsConstructor; import lombok.Getter; import java.math.BigDecimal; import java.util.Objects; /** * @author JavaEdge * @date 2022/4/1 */ @Getter public class Order { private Customer customer; public Order(String customerName) { customer = new Customer(customerName); } public String getCustomerName() { return customer.getName(); } void setCustomerName(String customerName) { customer = new Customer(customerName); } }
此外,还有一些代码也会使用 *Customer* 对象; package com.javaedge.refactor.ttt; import lombok.Getter; import java.util.Collection; /** * @author JavaEdge * @date 2022/4/1 */ @Getter public class Order { private Customer customer; public Order(String customerName) { customer = new Customer(customerName); } public String getCustomerName() { return customer.getName(); } void setCustomerName(String customerName) { customer = new Customer(customerName); } private static int numberOfOrdersFor(Collection orders, String customer) { int result = 0; for (Order each : orders) { if (each.getCustomerName().equals(customer)) { result++; } } return result; } }
到目前为止,Customer对象还是值对象。就算多份订单属于同一客户,但每个 *Order* 对象还是拥有各自的 *Customer* 对象。我希望改变这现状,使得一旦同一客户拥有多份不同订单,代表这些订单的所有 *Order* 对象就能共享同一个Customer对象。本例中,就意味着: 每 一个客户名称只该对应一个Customer对象。
首先我使用 Replace Constructor with Factory Method ,控制 *Customer* 对象的创建过程。我在 *Customer* 中定义工厂方法: package com.javaedge.refactor.ttt; import lombok.AllArgsConstructor; import lombok.Getter; /** * @author JavaEdge * @date 2022/4/1 */ @Getter @AllArgsConstructor class Customer { private String name; public static Customer create(String name) { return new Customer(name); } }
然后把原本调用构造函数的地方改为调用工厂函数:
然后再把构造函数声明为private: package com.javaedge.refactor.ttt; import lombok.AllArgsConstructor; import lombok.Getter; /** * @author JavaEdge * @date 2022/4/1 */ @Getter class Customer { private String name; private Customer(String name) { this.name = name; } public static Customer create(String name) { return new Customer(name); } }
现在,我必须决定如何访问Customer对象。我比较喜欢通过另一个对象(例如Order中的一个字段)来访问它。但本例并没有这样一个明显的字段用于访问Customer对象。
这时,我通常会创建一个注册表对象来保存所有Customer对象,以此作为访问点。简化例子,我把这个注册表保存在Customer类的static字段中,让Customer类作为访问点:
然后我得决定: 在接到请求时,创建新的Customer对象 还是预先将它们创建好
这里我选择后者。在应用程序的启动代码中,先把需要使用的Customer对象加载妥当。这些对象可能来自数据库,也可能来自文件。简单起见,我在代码中明确生成这些对象。反正以后我总是可以使用Substitute Algorithm改变它们的创建方式。 package com.javaedge.refactor.ttt; import lombok.AllArgsConstructor; import lombok.Getter; import java.util.Dictionary; import java.util.HashMap; import java.util.Map; /** * @author JavaEdge * @date 2022/4/1 */ @Getter class Customer { private String name; private static Map instances = new HashMap<>(); private Customer(String name) { this.name = name; } public static Customer create(String name) { return new Customer(name); } static void loadCustomers() { new Customer("Java").store(); new Customer("Edge").store(); new Customer("公众号").store(); } private void store() { instances.put(this.getName(), this); } }
修改工厂函数,让它返回预先创建好的Customer对象 package com.javaedge.refactor.ttt; import lombok.AllArgsConstructor; import lombok.Getter; import java.util.Dictionary; import java.util.HashMap; import java.util.Map; /** * @author JavaEdge * @date 2022/4/1 */ @Getter class Customer { private String name; private static Map instances = new HashMap<>(); private Customer(String name) { this.name = name; } public static Customer create(String name) { return instances.get(name); } static void loadCustomers() { new Customer("Java").store(); new Customer("Edge").store(); new Customer("公众号").store(); } private void store() { instances.put(this.getName(), this); } }
由于create()总是返回既有的Customer对象,所以我应该使用Rename Method修改这个工厂函数的名称,以便强调这点: public static Customer getNamed(String name) { return instances.get(name); }
iOS15更新发现,更智能iOS15更新发现,更智能关注公众号果粉机帮助果粉们快速成长Apple今天向果粉开发者发布了一款全新的开发工具,完全需要运行在iOS15的设备上,果粉在不安全的WiFi网络上或Wi
摩托罗拉MotoG60S发布搭载HelioG95,支持50W快充IT之家8月12日消息今天,摩托罗拉新机MotoG60S在该公司的巴西网站上线,这也是该机全球首次亮相。摩托G60S配备了6。8英寸FHD120Hz屏幕,搭载联发科HelioG95
为什么说摩托罗拉edge双新机是不妥协的诚意之作8月5日,摩托罗拉在上海举行了新品发布会,宣布推出Edge双新机edgespro和edge轻奢版。一强悍配置不妥协,摩托罗拉edge双新机惊艳亮相本场发布会的主题为亿起惊艳,而其推
vivoS10在这个夏季惊喜上市啦藏不住的自然美陕西篇vivoS10在这个夏季惊喜上市啦!不但质感很轻薄,颜值也是绝绝子!集美们一定要闭眼入呀这款作为蔡徐坤同款手机,还引入了最新的光致变色技术,一晒阳光还可以变色哦
性价比较高的安卓二手机我就给大家介绍一些性价比较高的安卓二手机,因为毕竟人人都不是手机发烧友,买的洋垃圾不一定会捣鼓。50以下小米2s,几顿早餐钱就能带走一部能听歌看小说的手机。这部手机搭载高通骁龙60
三星GalaxyZFlip35G亮相设计配置全方位升级,体验无死角2020年,三星推出了旗下全新的折叠屏产品GalaxyZFlip系列,纵向折叠的全新形态,搭配上UTG超薄玻璃的内屏材质,给折叠屏的发展提出了一个新的思路。如今,三星旗下第三代折叠
三星新品折叠手机支持防水?8月11日,三星举行新品发布会,推出了两款折叠屏智能手机GalaxyZFold35G和GalaxyZFlip35G。三星对折叠屏手机市场非常看好,其公布一组数据,折叠屏市场正以年增
鸿蒙2。0系统更新后,你不知道的5个手机技巧,真的可惜我们都知道,这两年华为的热度只增不减,自鸿蒙系统的出现,再一次让华为的热度大大提高,前段时间的鸿蒙2。0系统更新,更是传的沸沸扬扬,我相信照这个趋势,鸿蒙之后的系统更新一定会受到华
七夕男友礼物选什么?懂行的人今年都选这几款手机1,小米MIX4这是小米刚刚发布的今年的旗舰手机,4999的价格对于这顶配旗舰来说划算合理,全球首发的骁龙888plus处理器加上CUP全面屏,在手机性能和使用感受上体验拉满了!同
七夕节,送女朋友这几款手机,她肯定会喜欢对手机这一类数码产品比较感兴趣的一般男生居多,而女生则对手机这类数码产品了解比较少,她们最常使用的功能是拍照片(包括自拍),看电视剧,玩游戏,聊天社交,同时颜值在线,手感要好,要够
麦芒系列正式回归,脱离华为后,麦芒10SE终于支持5G哈喽大家好,我是你们的老朋友小馨,每天都会给大家更新我的原创内容,近日,曾作为华为旗下的麦芒系列品牌,如今却带来了一款5G千元机麦芒10SE,这款起售价为1699元的新品,搭载了一