Java重构:Value转Reference与Reference转Value技术示例讲解请求
Let's break down these two refactoring techniques with concrete Java examples—they boil down to choosing whether an object should act as a value (immutable, compared by content) or a reference (unique, compared by identity).
When to use this:
You have multiple instances of an object that represent the same real-world entity, and you need to:
- Share a single instance across all usages
- Modify the entity's state and have all usages see the updated state
Refactoring Before (Value Object)
A value object is immutable, has no identity beyond its content, and we create new instances even for identical data:
import java.math.BigDecimal; import java.util.Objects; public class Customer { private final String id; private final String name; public Customer(String id, String name) { this.id = id; this.name = name; } // Equality is based on content (id + name) @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Customer customer = (Customer) o; return Objects.equals(id, customer.id) && Objects.equals(name, customer.name); } @Override public int hashCode() { return Objects.hash(id, name); } // Only getters—no way to modify state after creation public String getId() { return id; } public String getName() { return name; } } public class Order { private Customer customer; private BigDecimal amount; public Order(Customer customer, BigDecimal amount) { this.customer = customer; this.amount = amount; } public Customer getCustomer() { return customer; } } // Client code public class Main { public static void main(String[] args) { // Two separate instances for the same customer Customer alice1 = new Customer("C001", "Alice"); Order order1 = new Order(alice1, new BigDecimal("100.00")); Customer alice2 = new Customer("C001", "Alice"); Order order2 = new Order(alice2, new BigDecimal("200.00")); // Same content, but different objects System.out.println(alice1 == alice2); // false } }
Refactoring After (Reference Object)
We convert it to a reference object by ensuring only one instance exists per unique entity (using a repository pattern), making it mutable so changes propagate to all usages:
import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; import java.util.Objects; public class Customer { private String id; private String name; // Private constructor—only the repository can create instances private Customer(String id, String name) { this.id = id; this.name = name; } // Mutable: we can update the customer's name public void setName(String name) { this.name = name; } // Equality is now based on identity (id only) @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Customer customer = (Customer) o; return Objects.equals(id, customer.id); } @Override public int hashCode() { return Objects.hash(id); } public String getId() { return id; } public String getName() { return name; } // Repository to manage shared instances public static class CustomerRepository { private static final Map<String, Customer> customers = new HashMap<>(); public static Customer getCustomer(String id, String name) { // Reuse existing instance or create new if needed customers.computeIfAbsent(id, k -> new Customer(k, name)); return customers.get(id); } } } public class Order { private Customer customer; private BigDecimal amount; public Order(Customer customer, BigDecimal amount) { this.customer = customer; this.amount = amount; } public Customer getCustomer() { return customer; } } // Client code public class Main { public static void main(String[] args) { // Get the same instance from the repository Customer alice = Customer.CustomerRepository.getCustomer("C001", "Alice"); Order order1 = new Order(alice, new BigDecimal("100.00")); Customer sameAlice = Customer.CustomerRepository.getCustomer("C001", "Alice"); Order order2 = new Order(sameAlice, new BigDecimal("200.00")); // Same object instance System.out.println(alice == sameAlice); // true // Update the name—all orders see the change alice.setName("Alice Smith"); System.out.println(order1.getCustomer().getName()); // Alice Smith System.out.println(order2.getCustomer().getName()); // Alice Smith } }
When to use this:
You have a reference object that's causing unexpected state sharing bugs, and you want:
- Immutable objects that don't share state across usages
- Changes to create new instances instead of modifying existing ones
Refactoring Before (Reference Object)
A reference object is mutable, shared across multiple contexts, leading to unintended side effects:
import java.math.BigDecimal; public class Money { private BigDecimal amount; private String currency; public Money(BigDecimal amount, String currency) { this.amount = amount; this.currency = currency; } // Modifies the existing instance's state public void add(BigDecimal value) { this.amount = this.amount.add(value); } // Getters and setters allow direct state changes public BigDecimal getAmount() { return amount; } public void setAmount(BigDecimal amount) { this.amount = amount; } public String getCurrency() { return currency; } public void setCurrency(String currency) { this.currency = currency; } } public class ShoppingCart { private Money total; public ShoppingCart(Money total) { this.total = total; } public void addItem(BigDecimal price) { total.add(price); } public Money getTotal() { return total; } } // Client code public class Main { public static void main(String[] args) { Money initialTotal = new Money(BigDecimal.ZERO, "USD"); ShoppingCart cart1 = new ShoppingCart(initialTotal); cart1.addItem(new BigDecimal("50.00")); // Reuse the same Money instance for another cart ShoppingCart cart2 = new ShoppingCart(initialTotal); cart2.addItem(new BigDecimal("30.00")); // Unintended side effect: both carts show $80 total System.out.println(cart1.getTotal().getAmount()); // 80.00 System.out.println(cart2.getTotal().getAmount()); // 80.00 } }
Refactoring After (Value Object)
We convert it to a value object by making it immutable—all "changes" return a new instance, eliminating shared state:
import java.math.BigDecimal; import java.util.Objects; public class Money { private final BigDecimal amount; private final String currency; public Money(BigDecimal amount, String currency) { this.amount = amount; this.currency = currency; } // Returns a NEW Money instance instead of modifying this one public Money add(BigDecimal value) { return new Money(this.amount.add(value), this.currency); } // Equality based on content (amount + currency) @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Money money = (Money) o; return Objects.equals(amount, money.amount) && Objects.equals(currency, money.currency); } @Override public int hashCode() { return Objects.hash(amount, currency); } // Only getters—no way to modify state after creation public BigDecimal getAmount() { return amount; } public String getCurrency() { return currency; } } public class ShoppingCart { private Money total; public ShoppingCart(Money total) { this.total = total; } public void addItem(BigDecimal price) { // Replace the total with a new Money instance this.total = this.total.add(price); } public Money getTotal() { return total; } } // Client code public class Main { public static void main(String[] args) { Money initialTotal = new Money(BigDecimal.ZERO, "USD"); ShoppingCart cart1 = new ShoppingCart(initialTotal); cart1.addItem(new BigDecimal("50.00")); ShoppingCart cart2 = new ShoppingCart(initialTotal); cart2.addItem(new BigDecimal("30.00")); // No more side effects—each cart has its own independent total System.out.println(cart1.getTotal().getAmount()); // 50.00 System.out.println(cart2.getTotal().getAmount()); // 30.00 } }
Quick Recap
- Change Value to Reference: Use when you need shared state and identity (e.g., a customer that multiple orders reference, where updates should reflect everywhere).
- Change Reference to Value: Use when you want isolated, immutable state (e.g., money amounts, where changes to one shouldn't affect others).
内容的提问来源于stack exchange,提问作者user1298426

