在保持数据完整性的同时对循环依赖关系进行建模

Modeling circular dependencies while maintaining data integrity

提问人:Amirhosein Al 提问时间:8/7/2021 最后编辑:John KugelmanAmirhosein Al 更新时间:8/10/2021 访问量:50

问:

我正在设计一个音乐信息系统。我有几个相互连接的实体。
以下是域代码的一部分。

class Album {
    
    private Set<Track> tracks;
    private boolean published;
    
    public Set<Track> getTracks() {
        return this.tracks;
    }
    public boolean isPublished() {
        return this.published;
    }

    public void publish() {
        System.out.println("Album.publish() called");
        this.published = true;
        this.tracks.forEach(track -> track.publish());
    }

}

class Track {

    private boolean published;
    private Album album;

    public boolean isPublished() {
        return this.published;
    }
    public Album getAlbum() {
        return this.album;
    }

    public void publish() {
        // if track is single (this.album == null), set published to true
        // if track is part of an album and the album is NOT published, return;
        // if track is part of an album and the album is published, set published to true
        if(this.album != null && !this.album.isPublished())
            return;
        this.published = true;
    }

}

Track 是一个独立的实体。它可以是单个曲目(即没有专辑)。所以这个属性实际上是需要的。
一个域规则是,当专辑被存档(即未发布)时,它的曲目也不能发布,如果专辑被发布,它的任何曲目都可以被发布或存档。
问题在于,当一张专辑被发布时(例如),它的曲目方法也会被调用。但是,根据专辑已有的副本(未发布)检查专辑是否已发布。
我该如何解决问题?
albumalbum1.publish()publish()track1.publish()

Java OOP 设计模式 传递

评论

0赞 erickson 8/7/2021
如果是一个实体,则不应该有“副本”——一个实体具有唯一的标识,并且所有引用都应指向同一个对象。Album
0赞 Amirhosein Al 8/7/2021
正确。我应该如何在 Java 中实现它?据我所知,Java 总是按值传递。@erickson
0赞 Taylor 8/7/2021
stackoverflow.com/questions/40480/......关于通过值与通过参考的良好阅读
0赞 erickson 8/7/2021
是的,将对父级的引用传递给 (按值,因为所有引用都是在 Java 中传递的)。强制执行数据完整性的一种方法是删除任何直接设置 的 API,而只提供一种方法来更新曲目集合设置 via package-private access。AlbumTrackAlbumTrackaddTrack(Track)AlbumAlbum

答:

0赞 rocket-3 8/10/2021 #1

如果按行为拆分域模型实体,则可以摆脱所描述的限制

让我们为这些实体提供一些接口:

interface AlbumId{
   String asString();
   AlbumId Absent = () -> "NO ALBUM AT ALL";
}

interface Publication{
   void publish() throws Exception;
   void archive() throws Exception;
   boolean published();
}

interface Track{
    TrackId id(); 
    AlbumId albumId(); //smart type (as DDD suggest), therefore, no more nulls
}

现在,您可以通过创建类来强制执行规则,这些类将为您提供可以发布的曲目列表:

public class TracksReadyToPublishOf implements Supplier<Map<TrackId, TrackPublication>>{ 
    //this class may access to cache and have dosens of other optimizations
    public TracksReadyToPublishOf(AlbumId id){...}
    @Override public get(){...} 
}

然后,您可以在任何地方重复使用您的代码来检查您的规则:

public class TrackPublication implements Publication {
    private final Track track;
    private final Supplier<Map<TrackId, TrackPublication>> allowedTracks;

    //easy for unit testing
    public SmartTrackPublication(Track track, Supplier<Map<TrackId, TrackPublication>> allowedTracks){
        this.track = track;
        this.allowedTracks = allowedTracks;
    }

    public SmartTrackPublication(Track track){
        this(track, new TracksReadyToPublishOf(track.albumId());
    }

    @Override
    public publish() throws AlbumArchivedException{
        if(this.albumId != AlbumId.Absent){
            if(!this.allowedTracks.get().containsKey(this.track.id())){
                throw new AlbumArchivedException();
            }
        }
        this.allowedTracks.get().get(this.id()).publish();
    }

}

对于专辑发行:

public class AlbumPublication implements Publication{

   private final AlbumId id;
   private final Producer<Map<TrackId, TrackPublication>> tracks
   
   private AlbumWithTracks(AlbumId id, Producer<Map<TrackId, TrackPublication>> tracks){
       this.id = id;
       this.tracks = tracks;
   }

   public AlbumWithTracks(AlbumId id){
       this(id, new TracksReadyToPublishOf(id))
   }
   ...
   @Override publish() throws Exception{
      //code for publishing album
         
      for(TrackPublication t : Arrays.asList(
          this.tracks.get() 
      )){
          t.publish(); //track can publish anyway if it presents in list above
      }
   }
}