samedi 22 juin 2019

Design pattern for a global cache of objects with lookup

I am currently working on a project that requires a common interface for defining, sharing, and lookup of a hierarchy of objects. Essentially, there would be a shared project that defines a common structure for all objects, and consuming projects can use it to define objects and share them among other projects. I am wondering if there is an existing design pattern that fits this and implements this well.

I have already attempted this, but I'm not exactly satisfied with the results. My current approach defines a Tag class, which uusesa unique string as the name, and a Guid as a runtime-generated Id for faster lookup (hashing and equating Guid values is much faster than doing so with strings). Then, objects that I want to be shared among projects globally define that Tag as a member.

In this current design, Tag objects serve solely as an identifier, and contain no data. I would like to re-design this so that tags DO contain data and serve as a base class. However, this is becoming increasingly complex, and so I am hoping to find established design patterns or existing projects that implement a similar system to aid in development.

[DebuggerDisplay( "Name = {Name}, Children = {ChildCount}" )]
public class Tag : IDisposable, IEquatable<Tag>
{

  #region Data Members

  public readonly Guid Id;

  public readonly string Name;

  public readonly string FullName;

  public readonly Tag Parent;

  protected readonly HashSet<Tag> children_;

  protected readonly int hash_;

  protected bool isDisposed_;

  #endregion

  #region Properties

  public IReadOnlyCollection<Tag> Children
  {
    [MethodImpl( MethodImplOptions.AggressiveInlining )]
    get => children_;
  }

  public int ChildCount
  {
    [MethodImpl( MethodImplOptions.AggressiveInlining )]
    get => children_.Count;
  }

  public bool HasChildren
  {
    [MethodImpl( MethodImplOptions.AggressiveInlining )]
    get => children_.Count > 0;
  }

  public bool HasParent
  {
    [MethodImpl( MethodImplOptions.AggressiveInlining )]
    get => Parent != null;
  }

  #endregion

  #region Constructor

  public Tag( string name )
    : this( Guid.NewGuid(), name, null )
  {
  }

  public Tag( string name, Tag parent )
    : this( Guid.NewGuid(), name, parent )
  {
  }

  public Tag( Guid id, string name )
    : this( id, name, null )
  {
  }

  public Tag( Guid id, string name, Tag parent )
  {
    Id = id;
    FullName = Name = name;

    children_ = new HashSet<Tag>();
    hash_ = Name.GetHashCode( StringComparison.InvariantCultureIgnoreCase );

    if( parent != null )
    {
      Parent = parent;
      FullName = $"{parent.FullName}.{Name}";
      Parent.AddChild( this );
    }
  }

  ~Tag()
  {
    Dispose( false );
  }

  #endregion

  #region Public Methods

  [MethodImpl( MethodImplOptions.AggressiveInlining )]
  internal void AddChild( Tag child )
  {
    if( !children_.Add( child ) )
    {
      children_.TryGetValue( child, out var existingChild );
      throw new DuplicateTagChildException( this, existingChild, child );
    }
  }

  [MethodImpl( MethodImplOptions.AggressiveInlining )]
  internal void RemoveChild( Tag child )
  {
    children_.Remove( child );
  }

  [MethodImpl( MethodImplOptions.AggressiveInlining )]
  public bool HasChild( Tag child )
  {
    return children_.Contains( child );
  }

  #endregion

  #region Overrides

  [MethodImpl( MethodImplOptions.AggressiveInlining )]
  public bool Equals( Tag other )
  {
    return Name.Equals( other.Name, StringComparison.InvariantCultureIgnoreCase );
  }

  [MethodImpl( MethodImplOptions.AggressiveInlining )]
  public override bool Equals( object obj )
  {
    return obj is Tag other
      && Name.Equals( other.Name, StringComparison.InvariantCultureIgnoreCase );
  }

  [MethodImpl( MethodImplOptions.AggressiveInlining )]
  public override int GetHashCode()
  {
    return hash_;
  }

  #endregion

  #region IDisposable Methods

  public void Dispose()
  {
    Dispose( true );
    GC.SuppressFinalize( this );
  }

  public virtual void Dispose( bool disposing )
  {
    if( isDisposed_ )
      return;

    if( disposing )
    {
    }

    Parent?.RemoveChild( this );
    TagCache.TryRemove( this );

    isDisposed_ = true;
  }

  #endregion

}

Aucun commentaire:

Enregistrer un commentaire