代码重构实战-将值对象改为引用对象(Change Value to Reference)

简介: 一个数据结构中可能包含多个记录,而这些记录都关联到同一个逻辑数据结构。例如,我可能会读取一系列订单数据,其中有多条订单属于同一个顾客。遇到这样的共享关系,既能将顾客信息作为值对象看待,也能将其视为引用对象

动机


一个数据结构中可能包含多个记录,而这些记录都关联到同一个逻辑数据结构。例如,我可能会读取一系列订单数据,其中有多条订单属于同一个顾客。遇到这样的共享关系,既能将顾客信息作为值对象看待,也能将其视为引用对象:


若将其视为值对象,则每份订单数据中都会复制顾客的数据

若将其视为引用对象,对于一个顾客,就只有一份数据结构,会有多个订单与之关联

若顾客数据永不修改,则两种方式都合理。 把同一份数据复制多次可能会造成一点困扰,但这种情况也


很常见,不是太大问题。过多的数据复制有可能会造成内存占用的问题,但就跟所有性能问题一样,这种情况并不常见。


若共享的数据需要更新,将其复制多份的做法就会遇到巨大困难。此时我必须找到所有副本,更新所有对象。漏掉一个副本没更新,就会导致数据不一致。这时,考虑将多份数据副本变成单一的引用,这样对顾客数据的修改就会立即反映在该顾客的所有订单中。


把值对象改为引用对象会带来一个结果:对于一个客观实体,只有一个代表它的对象。这通常意味着我会需要某种形式的仓库,在仓库中可以找到所有这些实体对象。只为每个实体创建一次对象,以后始终从仓库中获取该对象。


做法


为相关对象创建一个仓库(若还没这样的一个仓库)。 确保构造器有办法找到关联对象的正确实例。修改宿主对象的构造器,令其从仓库中获取关联对象。每次修改后测试。


案例


订单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<Order> 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<String, Customer> 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<String, Customer> 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);

}


目录
相关文章
|
机器学习/深度学习 人工智能 自然语言处理
第10章 经典智能算法——10.1 粒子群算法的MATLAB实现(1)
第10章 经典智能算法——10.1 粒子群算法的MATLAB实现(1)
|
JavaScript 前端开发
el-upload上传文件
el-upload上传文件
1485 0
|
消息中间件 人工智能 Cloud Native
社区胜于代码,我们在阿帕奇软件基金会亚洲大会聊了聊开源中间件的未来
阿帕奇基金会亚洲大会顺利召开,阿里云消息技术负责人林清山在主论坛做了《阿里云中间件持续进化:从分布式应用架构向云原生 AI 原生应用架构全面升级》的演讲,从云厂商的视角分享了贡献开源、推动社区发展的过程,希望通过 AI 开发框架+AI 观测能力+AI 网关 + 事件驱动,一站式助力大模型应用落地。
510 100
社区胜于代码,我们在阿帕奇软件基金会亚洲大会聊了聊开源中间件的未来
|
12月前
|
JSON API 数据格式
关键词搜索爱回收商品列表API接口(爱回收API系列)
爱回收作为二手电子产品交易平台,提供丰富的商品资源。其API接口允许开发者通过关键词搜索商品列表,获取商品名称、类别、品牌、预估回收价格等信息,支持分页展示和自定义每页数量。接口采用HTTP GET请求,响应格式为JSON。以下是Python示例代码,展示如何使用该接口进行搜索。
|
缓存 运维 关系型数据库
PolarDB产品使用问题之如何进行PolarDBX的本地部署
PolarDB产品使用合集涵盖了从创建与管理、数据管理、性能优化与诊断、安全与合规到生态与集成、运维与支持等全方位的功能和服务,旨在帮助企业轻松构建高可用、高性能且易于管理的数据库环境,满足不同业务场景的需求。用户可以通过阿里云控制台、API、SDK等方式便捷地使用这些功能,实现数据库的高效运维与持续优化。
|
存储 缓存 物联网
新EDPB指南:不只是Cookie
保障中国企业符合《个人信息保护法》合规,方案包括梳理基线、发现敏感信息、简化评估工作流、快速响应权利请求、满足告知同意要求,以及有效保护敏感数据。用九智汇整合自动化工具和规则引擎,确保高效风险评估、合规证据链建立,进一步保障数据安全。
320 1
|
前端开发 Java
springboot项目中外卖用户下单业务功能之需求分析+数据模型+功能开发(详细步骤)
springboot项目中外卖用户下单业务功能之需求分析+数据模型+功能开发(详细步骤)
370 0
|
安全 机器人 Shell
shell脚本实现Linux磁盘空间超过阈值自动钉钉机器人告警
shell脚本实现Linux磁盘空间超过阈值自动钉钉机器人告警
328 0
|
存储 Java 定位技术
【Android App】获取照片里的位置信息及使用全球卫星导航系统(GNSS)获取位置实战(附源码和演示 超详细)
【Android App】获取照片里的位置信息及使用全球卫星导航系统(GNSS)获取位置实战(附源码和演示 超详细)
1486 0
|
JavaScript
Vue颜色选择器实现(两种方法)
Vue颜色选择器实现(两种方法)
1322 0