如何建模不可变类实例之间的循环?

mP.*_*mP. 6 java class-design

不可变的类是伟大的,但有一个大问题,我不能想到一个明智的解决方法 - 周期.

class Friend {
   Set<Friend> friends();
}
Run Code Online (Sandbox Code Playgroud)

我是如何模仿我作为朋友而将你作为朋友回来的?

不可变性 来自外部世界的这类绝对应该是不可改变的.为了进行平等检查,内部持有的值应保持不变.

Ber*_*t F 8

[[[编辑:添加代码以演示完全不可变的概念]]]

这就是为什么构建者对于不可变的东西是如此的好 - 它们允许在构造期间的可变性在你"冻结"它之前完成所有设置.在这种情况下,我猜你需要一个支持创建周期的朋友构建器.

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");
Run Code Online (Sandbox Code Playgroud)

编辑:在下面添加了不可变示例来演示方法:

  • 评论中有一些关于是否可以使用不可变版本的讨论.

  • 字段是最终的和不可变的.在构造函数中使用了可修改的集合,但只有在构造之后才保留不可修改的引用.

  • 我有另一个版本使用Guava ImmutableSet作为真正不可变的集合,而不是JDK的不可修改的包装器.它的工作方式相同,但使用了Guava的优秀设置构建器.

码:

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

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)
  }
}
Run Code Online (Sandbox Code Playgroud)

  • @Konstantin Komissarchik`A级{最终B b; A(){this.b = new B(this); B类{final A a; B(A a){this.a = a; 我相信`功能语言中众所周知的东西. (3认同)
  • @Konstantin Komissarchik你可以和堆叠上的一些朋友(最好的情况下的图表直径,最糟糕的情况)这样做,并且仍然保持不变性. (2认同)