我想我希望类型之间的关系尽可能空灵。虽然大多数类型很容易相关,但有些类型具有复合键或奇怪的关系,而你永远不知道......所以我将从类型本身中外部化相关类型的发现。我们中只有少数幸运儿拥有全球唯一的一致密钥类型。
我可以想象让所有类型都成为观察者和可观察者。我从来没有大声做过这样的事情......至少,不是这样,但这是一个有趣的可能性......并且给了 500 分,我认为值得一试;-)
我使用Tag 一词来关注您的评论。也许Base 对您更有意义?无论如何,在下文中,Tag 是一种通知观察标签并监听可观察标签的类型。我将observables 设为Tag.Subscription 的列表。通常,您只会有一个 IDisposable 实例的列表,因为这就是 observable 通常提供的所有内容。这样做的原因是Tag.Subscription 让您发现底层Tag...以便您可以在派生类型中为您的类型的列表属性刮取订阅(如下面的Author 和Book 所示。 )
我设置了Tag 订阅者/通知机制,无需值 本身就可以工作......只是为了隔离机制。我假设大多数Tags 都会有值...但也许有例外。
public interface ITag : IObservable<ITag>, IObserver<ITag>, IDisposable
{
Type TagType { get; }
bool SubscribeToTag( ITag tag );
}
public class Tag : ITag
{
protected readonly List<Subscription> observables = new List<Subscription>( );
protected readonly List<IObserver<ITag>> observers = new List<IObserver<ITag>>( );
bool disposedValue = false;
protected Tag( ) { }
IDisposable IObservable<ITag>.Subscribe( IObserver<ITag> observer )
{
if ( !observers.Contains( observer ) )
{
observers.Add( observer );
observer.OnNext( this ); //--> or not...maybe you'd set some InitialSubscription state
//--> to help the observer distinguish initial notification from changes
}
return new Subscription( this, observer, observers );
}
public bool SubscribeToTag( ITag tag )
{
if ( observables.Any( subscription => subscription.Tag == tag ) ) return false; //--> could throw here
observables.Add( ( Subscription ) tag.Subscribe( this ) );
return true;
}
protected void Notify( ) => observers.ForEach( observer => observer.OnNext( this ) );
public virtual void OnNext( ITag value ) { }
public virtual void OnError( Exception error ) { }
public virtual void OnCompleted( ) { }
public Type TagType => GetType( );
protected virtual void Dispose( bool disposing )
{
if ( !disposedValue )
{
if ( disposing )
{
while ( observables.Count > 0 )
{
var sub = observables[ 0 ];
observables.RemoveAt( 0 );
( ( IDisposable ) sub ).Dispose( );
}
}
disposedValue = true;
}
}
public void Dispose( )
{
Dispose( true );
}
protected sealed class Subscription : IDisposable
{
readonly WeakReference<Tag> tag;
readonly List<IObserver<ITag>> observers;
readonly IObserver<ITag> observer;
internal Subscription( Tag tag, IObserver<ITag> observer, List<IObserver<ITag>> observers )
{
this.tag = new WeakReference<Tag>( tag );
this.observers = observers;
this.observer = observer;
}
void IDisposable.Dispose( )
{
if ( observers.Contains( observer ) ) observers.Remove( observer );
}
public Tag Tag
{
get
{
if ( tag.TryGetTarget( out Tag target ) )
{
return target;
}
return null;
}
}
}
}
如果绝对所有标签都有值,您可以将以下实现与上述实现合并......但我认为将它们分开感觉更好。
public interface ITag<T> : ITag
{
T OriginalValue { get; }
T Value { get; set; }
bool IsReadOnly { get; }
}
public class Tag<T> : Tag, ITag<T>
{
T currentValue;
public Tag( T value, bool isReadOnly = true ) : base( )
{
IsReadOnly = isReadOnly;
OriginalValue = value;
currentValue = value;
}
public bool IsReadOnly { get; }
public T OriginalValue { get; }
public T Value
{
get
{
return currentValue;
}
set
{
if ( IsReadOnly ) throw new InvalidOperationException( "You should have checked!" );
if ( Value != null && !Value.Equals( value ) )
{
currentValue = value;
Notify( );
}
}
}
}
虽然这看起来有点忙,但主要是普通订阅机制和可处置性。派生类型将非常简单。
注意受保护的Notify() 方法。我开始将其放入界面中,但意识到从外部世界访问它可能不是一个好主意。
所以...举个例子;这是一个示例Author。注意AddBook 是如何建立相互关系的。并非每种类型都有这样的方法......但它说明了它是多么容易:
public class Author : Tag<string>
{
public Author( string name ) : base( name ) { }
public void AddBook( Book book )
{
SubscribeToTag( book );
book.SubscribeToTag( this );
}
public IEnumerable<Book> Books
{
get
{
return
observables
.Where( o => o.Tag is Book )
.Select( o => ( Book ) o.Tag );
}
}
public override void OnNext( ITag value )
{
switch ( value.TagType.Name )
{
case nameof( Book ):
Console.WriteLine( $"{( ( Book ) value ).CurrentValue} happened to {CurrentValue}" );
break;
}
}
}
...和Book 会相似。关于相互关系的另一种想法;如果你不小心通过Book 和Author 定义了关系,没有害处,没有犯规......因为订阅机制只是悄悄地跳过重复(为了确定我测试了这个案例):
public class Book : Tag<string>
{
public Book( string name ) : base( name ) { }
public void AddAuthor( Author author )
{
SubscribeToTag( author );
author.SubscribeToTag( this );
}
public IEnumerable<Author> Authors
{
get
{
return
observables
.Where( o => o.Tag is Author )
.Select( o => ( Author ) o.Tag );
}
}
public override void OnNext( ITag value )
{
switch ( value.TagType.Name )
{
case nameof( Author ):
Console.WriteLine( $"{( ( Author ) value ).CurrentValue} happened to {CurrentValue}" );
break;
}
}
}
...最后,一个小测试工具,看看它是否有效:
var book = new Book( "Pride and..." );
var author = new Author( "Jane Doe" );
book.AddAuthor( author );
Console.WriteLine( "\nbook's authors..." );
foreach ( var writer in book.Authors )
{
Console.WriteLine( writer.Value );
}
Console.WriteLine( "\nauthor's books..." );
foreach ( var tome in author.Books )
{
Console.WriteLine( tome.Value );
}
author.AddBook( book ); //--> maybe an error
Console.WriteLine( "\nbook's authors..." );
foreach ( var writer in book.Authors )
{
Console.WriteLine( writer.Value );
}
Console.WriteLine( "\nauthor's books..." );
foreach ( var tome in author.Books )
{
Console.WriteLine( tome.Value );
}
...吐出了这个:
Jane Doe happened to Pride and...
Pride and... happened to Jane Doe
book's authors...
Jane Doe
author's books...
Pride and...
book's authors...
Jane Doe
author's books...
Pride and...
虽然我的列表属性为IEnumerable<T>,但您可以将它们设为延迟加载的列表。您需要能够使列表的后备存储无效,但这可能会很自然地从您的 observables 中流出。
有数百种方法可以解决所有这些问题。我尽量不被带走。不知道...需要进行一些测试才能弄清楚这有多实用...但是想想确实很有趣。
编辑
我忘记说明的东西……书签。我猜书签的值是可更新的页码?比如:
public class Bookmark : Tag<int>
{
public Bookmark( Book book, int pageNumber ) : base( pageNumber, false )
{
SubscribeToTag( book );
book.SubscribeToTag( this );
}
public Book Book
{
get
{
return
observables
.Where( o => o.Tag is Book )
.Select( o => o.Tag as Book )
.FirstOrDefault( ); //--> could be .First( ) if you null-check book in ctor
}
}
}
那么,Book 可能具有 IEnumerable<Bookmark> 属性:
public class Book : Tag<string>
{
//--> omitted stuff... <--//
public IEnumerable<Bookmark> Bookmarks
{
get
{
return
observables
.Where( o => o.Tag is Bookmark )
.Select( o => ( Bookmark ) o.Tag );
}
}
//--> omitted stuff... <--//
}
关于这一点的巧妙之处在于,作者的书签就是他们书籍的书签:
public class Author : Tag<string>
{
//--> omitted stuff... <--//
public IEnumerable<Bookmark> Bookmarks => Books.SelectMany( b => b.Bookmarks );
//--> omitted stuff... <--//
}
对于 yuks,我让书签拿了一本关于建筑的书……只是为了说明一种不同的方法。根据需要混合和匹配;-) 请注意,书签没有书籍列表......只有一本书......因为它更适合模型。有趣的是,您可以从单个书签中解析所有书籍书签:
var bookmarks = new List<Bookmark>( bookmark.Book.Bookmarks );
...同样轻松获取所有作者的书签:
var authBookmarks = new List<Bookmark>( bookmark.Book.Authors.SelectMany( a=> a.Bookmarks ) );