您的位置 首页 java

华为技术专家教你重构代码:将值对象改为引用对象

动机

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

  • 若将其视为值对象,则每份订单数据中都会复制顾客的数据
  • 若将其视为引用对象,对于一个顾客,就只有一份数据结构,会有多个订单与之关联

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

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

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

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

做法

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

案例

订单Order类,其实例对象可从一个 JSON 文件创建。用来创建订单的数据中有一个顾客(customer)ID,我们用它来进一步创建Customer对象。

  package com.javaedge.refactor.ttt;
 
 import lombok.AllArgs Constructor ;
 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  number OfOrdersFor(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);
 }  

文章来源:智云一二三科技

文章标题:华为技术专家教你重构代码:将值对象改为引用对象

文章地址:https://www.zhihuclub.com/190770.shtml

关于作者: 智云科技

热门文章

网站地图