2011-01-29 23 views
5

Các lớp học không thể thay đổi là tuyệt vời nhưng có một vấn đề lớn tôi không thể nghĩ ra một cách hợp lý để giải quyết - chu kỳ.Làm thế nào để mô hình chu kỳ giữa các trường hợp lớp không thay đổi?

class Friend { 
    Set<Friend> friends(); 
} 

Làm thế nào để một người mẫu có bạn với bạn, người mà lần lượt có tôi là bạn bè?

IMMUTABILITY Lớp học này từ thế giới bên ngoài chắc chắn sẽ không thay đổi. Giá trị được giữ trong nội bộ phải không đổi cho mục đích kiểm tra bình đẳng.

+0

Bạn có biết trước tất cả các mối quan hệ hai chiều bạn cần hoặc bạn có thêm từng mối quan hệ một lúc không? Trong trường hợp sau, thực sự không có cách nào để có được sự đảm bảo bất biến, vì bạn thực sự đang thay đổi các đối tượng. – templatetypedef

+1

Trong trường hợp này - không, im cố gắng giữ đơn giản này. –

Trả lời

8

[[[Edit: Nhập mã để chứng minh khái niệm hoàn toàn bất biến]]]

Đó là lý do tại sao các nhà xây dựng rất tốt đẹp cho immutables - chúng cho phép khả năng thay đổi trong khi xây dựng để mọi thứ được thiết lập trước khi bạn "đóng băng" nó. Trong trường hợp này, tôi đoán bạn cần một người xây dựng Friend hỗ trợ tạo chu kỳ.

final FriendBuilder john = new FriendBuilder().setName("john"); 
final FriendBuilder mary = new FriendBuilder().setName("mary"); 
final FriendBuilder susan = new FriendBuilder().setName("susan"); 
john 
    .likes(mary) 
    .likes(susan); 
mary 
    .likes(susan) 
    .likes(john); 
susan 
    .likes(john); 

// okay lets build the immutable Friends 
Map<Friend> friends = FriendsBuilder.createCircleOfFriends(john, mary, susan); 
Friend immutableJohn = friends.get("john"); 

Chỉnh sửa: Thêm dụ bất biến dưới đây để chứng minh phương pháp:

  • Có một số cuộc thảo luận trong các ý kiến ​​về việc liệu một phiên bản bất biến là có thể.

  • Các trường là cuối cùng và không thay đổi. Một bộ có thể sửa đổi được sử dụng trong hàm tạo, nhưng nó chỉ là tham chiếu không thể sửa đổi được giữ lại sau khi xây dựng.

  • Tôi có một phiên bản khác sử dụng Guava ImmutableSet cho bộ thực sự không thay đổi được chứ không phải là trình bao bọc không thể sửa đổi của JDK. Nó hoạt động tương tự, nhưng sử dụng công cụ xây dựng bộ tốt đẹp của Guava.

Code:

import java.util.Collections; 
import java.util.HashMap; 
import java.util.HashSet; 
import java.util.IdentityHashMap; 
import java.util.Map; 
import java.util.Set; 

/** 
* Note: potentially cycle graph - be careful of deep equals/hashCode/toString/etc. 
* Immutable 
*/ 
public class Friend { 

    public static class Builder { 

     private final String name; 
     private final Set<Builder> friends = 
      new HashSet<Builder>(); 

     Builder(final String name) { 
      this.name = name; 
     } 

     public String getName() { 
      return name; 
     } 

     public Set<Builder> getFriends() { 
      return friends; 
     } 

     void likes(final Builder... newFriends) { 
      for (final Builder newFriend : newFriends) 
      friends.add(newFriend); 
     } 

     public Map<String, Friend> createCircleOfFriends() { 
      final IdentityHashMap<Builder, Friend> existing = 
       new IdentityHashMap<Builder, Friend>(); 

      // Creating one friend creates the graph 
      new Friend(this, existing); 
      // after the call existingNodes contains all the nodes in the graph 

      // Create map of the all nodes 
      final Map<String, Friend> map = 
       new HashMap<String, Friend>(existing.size(), 1f); 
      for (final Friend current : existing.values()) { 
       map.put(current.getName(), current); 
      } 

      return map; 
     } 
    } 

    final String name; 
    final Set<Friend> friends; 

    private Friend(
      final Builder builder, 
      final Map<Builder, Friend> existingNodes) { 
     this.name = builder.getName(); 

     existingNodes.put(builder, this); 

     final IdentityHashMap<Friend, Friend> friends = 
      new IdentityHashMap<Friend, Friend>(); 
     for (final Builder current : builder.getFriends()) { 
      Friend immutableCurrent = existingNodes.get(current); 
      if (immutableCurrent == null) { 
       immutableCurrent = 
        new Friend(current, existingNodes); 
      } 
      friends.put(immutableCurrent, immutableCurrent); 
     } 

     this.friends = Collections.unmodifiableSet(friends.keySet()); 
    } 

    public String getName() { 
     return name; 
    } 

    public Set<Friend> getFriends() { 
     return friends; 
    } 


    /** Create string - prints links, but does not traverse them */ 
    @Override 
    public String toString() { 
     final StringBuffer sb = new StringBuffer(); 
     sb.append("Friend ").append(System.identityHashCode(this)).append(" {\n"); 
     sb.append(" name = ").append(getName()).append("\n"); 
     sb.append(" links = {").append("\n"); 
     for (final Friend friend : getFriends()) { 
      sb 
      .append("  ") 
      .append(friend.getName()) 
      .append(" (") 
      .append(System.identityHashCode(friend)) 
      .append(")\n"); 
     } 
     sb.append(" }\n"); 
     sb.append("}"); 
     return sb.toString(); 
    } 

    public static void main(final String[] args) { 
     final Friend.Builder john = new Friend.Builder("john"); 
     final Friend.Builder mary = new Friend.Builder("mary"); 
     final Friend.Builder susan = new Friend.Builder("susan"); 
     john 
      .likes(mary, susan); 
     mary 
      .likes(susan, john); 
     susan 
      .likes(john); 

     // okay lets build the immutable Friends 
     final Map<String, Friend> friends = john.createCircleOfFriends(); 

     for(final Friend friend : friends.values()) { 
      System.out.println(friend); 
     } 

     final Friend immutableJohn = friends.get("john"); 
    } 
} 

Output:

Node 11423854 { 
    value = john 
    links = { 
    susan (19537476) 
    mary (2704014) 
    } 
} 
Node 2704014 { 
    value = mary 
    links = { 
    susan (19537476) 
    john (11423854) 
    } 
} 
Node 19537476 { 
    value = susan 
    links = { 
    john (11423854) 
    } 
} 
+0

Sẽ rất hữu ích khi lưu ý rằng mẫu trình xây dựng chỉ che giấu thực tế rằng bạn đang thực hiện khởi tạo sau xây dựng. Lớp Friend không thể có một cấu trúc hoàn toàn cuối cùng để giữ bạn bè. –

+2

@Konstantin Komissarchik Bạn có thể làm điều này với một số bạn bè trên stack (đường kính của đồ thị trong trường hợp tốt nhất, tất cả trong tồi tệ nhất), và vẫn giữ bất biến. –

+0

@Tom Tôi không mua nó. Bạn bè sẽ phải có thứ gì đó không phải cuối cùng để cuối cùng kết thúc với hai đối tượng Friend tham chiếu lẫn nhau. Một tham chiếu cuối cùng cho một proxy (aka builder) mà nội bộ không phải là cuối cùng không được tính. Đó chỉ là một cách khác để khởi tạo chậm trễ. –

-1

Tính không thay đổi không cần phải được thực thi bởi trình biên dịch để có kiến ​​trúc hợp lệ. Bạn có thể có một đối tượng bất biến hợp pháp có các tham số khởi tạo sau xây dựng. Ví dụ:

private Object something; 

public void init(final Object something) 
{ 
    if(this.something != null) 
    { 
     throw new IllegalStateException(); 
    } 

    this.something = something 
} 

Trường thành viên "cái gì đó" không phải là cuối cùng, nhưng nó không thể được đặt nhiều hơn một lần.

biến

Một phức tạp hơn dựa trên thảo luận trong ý kiến ​​...

private boolean initialized; 
private Object a; 
private Object b; 

public void init(final Object a, final Object b) 
{ 
    if(this.initialized) 
    { 
     throw new IllegalStateException(); 
    } 

    this.initialized = true; 
    this.a = a; 
    this.b = b; 
} 

public Object getA() 
{ 
    assertInitialized(); 
    return this.a; 
} 

public Object getB() 
{ 
    assertInitialized(); 
    return this.b; 
} 

private void assertInitialized() 
{ 
    if(this.initialized) 
    { 
     throw new IllegalStateException("not initialized"); 
    } 
} 
+0

Tôi nghĩ rằng bạn có nghĩa là: nếu (một cái gì đó == null) – Mnementh

+0

Trên thực tế, tôi có nghĩa là "nếu (this.something! = Null)". Câu lệnh if có ở đó để bắt nỗ lực thiết lập một cái gì đó nhiều hơn một lần. –

+0

Ah, tôi hiểu rồi. Cảm ơn cho thanh toán bù trừ này lên. – Mnementh

0

Các cách chính xác để mô hình một chu kỳ là với một Graph. Và một nhận xét dòng mã nguồn duy nhất có thể đủ để thực thi tính không thể sửa đổi: "can't touch this".

Bạn đang tìm kiếm loại thực thi không thể sửa chữa nào? Bạn có muốn một chiếc velociraptor xuất hiện whenever you modify the inmutable Set không? Sự khác biệt giữa mutableinmutable chỉ là một quy ước. Tuy nhiên, các bit trên RAM có thể dễ dàng được sửa đổi và với Reflection API bạn có thể phá vỡ mọi quy tắc đóng gói và ẩn dữ liệu.

Bỏ qua biểu tượng tốc độ trong một lúc, Java không hỗ trợ loại không thể khắc phục. Để giải quyết vấn đề này, bạn cần phải mô hình hóa một kiểu dữ liệu hoạt động như một kiểu dữ liệu.

Và đối với thuộc tính không thể sửa chữa, bạn cần phải thực hiện Friend một interface, có một lớp triển khai: InmutableFriend và việc xây dựng đối tượng hoàn toàn xảy ra bên trong hàm tạo. Sau đó, vì biểu đồ chứa các chu trình, trước khi tạo các trường hợp không thể sửa chữa cuối cùng, bạn cần lưu trữ các nút biểu đồ trong một số cấu trúc tạm thời có thể thay đổi được. Bạn cũng cần trả lại một số unmodifiableSet theo phương thức InmutableFriend.friends().

Cuối cùng, sao chép biểu đồ bạn cần triển khai thuật toán Deep-copy như Breadth-first search trên biểu đồ Có thể thay đổi. Một câu hỏi là điều gì xảy ra khi biểu đồ không phải là fully connected.

interface Friend { 
    public Set<Friend> friends(); 
} 

class MutableFriend { 
    private Set<MutableFriend> relations = new HashSet<MutableFriend>(); 

    void connect(MutableFriend otherFiend) { 
     if (!relations.contains(otherFriend)) { 
      relations.add(otherFiend); 
      otherFriend.connect(this); 
     } 
    } 

    Friend freeze() { 
     Map<MutableFriend, InmutableFriend> table = ...; 

     /* 
     * FIXME: Implement a Breadth-first search to clone the graph, 
     * using this node as the starting point. 
     * 
     * TODO: If the graph is not connected this won't work. 
     * 
     */ 
    } 
} 

class InmutableFriend() implements Friend { 
    private Set<Friend> connections; 

    public Set<Friend> friends() { 
     return connections; 
    } 

    public InmutableFriend(Set<Friend> connections) { 
     // Can't touch this. 
     this.connections = Collections.unmodifiableSet(connections); 
    } 
} 
+0

Mẫu tủ đông thực sự xấu xí imho, im bắt đầu nghĩ rằng bạn sẽ trông không thay đổi nhưng sâu bên trong người xây dựng sẽ thêm sttuff vào nó và sau đó đóng băng, tại thời điểm đó nó có thể được trao cho thế giới bên ngoài ... –

+0

@mP: Tủ đông không phải là một mẫu, Đó là một thuật toán được gọi là Deep Copy: http://en.wikipedia.org/wiki/Object_copy#Deep_copy – vz0

+0

xin lỗi bạn đời tôi đã đọc nhầm MutableFrield = Field :) bỏ qua bình luận ban đầu của tôi. –

Các vấn đề liên quan