Share via


Design pattern for read-only vs. read-write collections

Interesting discussion over in WinFX land today, thought you might want to chime in:

What is better from the standpoint of the design guidelines:
1) Single class FooCollection with an IsReadOnly property and an AsReadOnly method returning a FooCollection
or
2) Two classes: FooCollection and ReadOnlyFooCollection

One suggestions, which I think is a pretty good one:

It depends. I would do #2 in commonly used API as it’s cleaner in terms of the Object Model. I would do #1 in to save one type is less commonly used API.

Of course with generics this gets a little easier...

What do you think?

Comments

  • Anonymous
    February 04, 2004
    I agree with the suggestion you posted. You could also consider placing a set of ReadOnly classes into a .ReadOnly namespace, much as System.Collections.Specialized classes have their own namespace.
  • Anonymous
    February 04, 2004
    The comment has been removed
  • Anonymous
    February 04, 2004
    The comment has been removed
  • Anonymous
    February 04, 2004
    I would prefer a single class. If the framework ever gets const support then I think it would be cleaner to convert in the future.
  • Anonymous
    February 04, 2004
    It absolutely, in all circumstances, MUST BE A SINGLE CLASS.

    That is a no-brainer. It HAS TO BE A SINGLE CLASS.

    Can I shout any louder here?! ;)

    Consistency is key to an API: if half of the readonly collections are referred to by a different class name, and the other half are referred to invoking a method on a ReadWrite class, then that is just about the WORST THING THAT I CAN IMAGINE.

    That is a pit of despair, for users of the API anyway. Bad, bad, bad. Please do this with one class. It's even bad OO, IMO: a readonly collection of widgets is not a different THING to a readwrite colleciton of widgets. The writeability of the collection is so clearly a property of a single object, that it hurts. Please don't implement this as two classes.
  • Anonymous
    February 04, 2004
    we really need:
    IReadOnlyKindOfCollection
    {
    // only getter stuff
    // no modifications on collection permited
    // (no Add, Remove but can permit to modify inner datas)
    }

    and

    IKindOfCollection
    {
    // return a read only view of the current datas in this IKindOfCollection (share and not copy)
    IReadOnlyKindOfCollection AsReadOnly
    }

    It can provide us a unified way to export a read only view of an internal (non read only) collection.

    And each kind of collection (Array, ArrayList, List, Hashtable, and so on) must have same pattern.

    This can be achieved by inheriting IKindOfCollection from IReadOnlyKindOfCollection and AsReadOnly can be as simple as returning this. It's easy but not secure. It's always possible to reinterprete a read only view with a single cast.

    A better implementation should return a new object implementing IReadOnlyKindOfCollection but using shared datas.
  • Anonymous
    February 04, 2004
    I think you should use an interface based approach:

    interface IReadOnlyFooCollection
    {
    // Readonly members
    }

    interface IFooCollection : IReadOnlyFooCollection
    {
    // Add, Remove...
    }

    And use an appropriate wrapper similar to ArrayList.Synchronized/ArrayList.ReadOnly to prevent casting to the non-readonly version.

    In my opinion there should only be a single-class implementation to prevent unnecessary copying operations at runtime.
  • Anonymous
    February 04, 2004
    I'm down with Michael's idea about having a mutable thing inherit from the readonly thing. That way, APIs that don't need to modify the instance can indicate that they are not by taking the readonly version. APIs that are going to mutate the instance take the mutable version. ie:

    There are problem with this, at least in the current version of c# (maybe they're fixed in 2.0). Can I turn a read only property into a read/write property in a subclass in 2.0? ie:

    class Map<T> {
    public T this[int index] { get; }
    }

    class MutableMap<T> : Map<T> {
    public T this[int index] { get; set; }
    }

    ? If not, you have to do some goofy workarounds to get compile-time verification that you're not trying to call a mutator on the instance.

    Would people rather have compile-time or run-time notification that they're trying to mutate a readonly collection? Personally, I'd like compile-time.
  • Anonymous
    February 04, 2004
    Actually, looking at the code I just posted, I bet I can do that in 1.0 & 1.1 if I take out the generics and slap a new on the Mutable indexer. The real case I'm wondering about deals with straight properties:

    class Person {
    public virtual string Name { get; }
    }

    class MutablePerson : Person {
    public override string Name { get; set; }
    }

    Can I do that in 2.0? Or do I still have to do:

    class Person {
    protected string name;
    public Person(string name) { this.name = name; }
    public string Name { get { return "Ben"; } }
    }

    class MutablePerson : Person {
    public MutablePerson(string name) : base(name) {}
    public void SetName(string value) { this.name = value; }
    }

    Thanks.
  • Anonymous
    February 04, 2004
    The comment has been removed
  • Anonymous
    February 04, 2004
    Read-only to who?

    You need to be able to fill it somehow. The question is really how do you pass the read-only semantics to others?

    I really think STL had this one nailed. I also think that too few under stood the possibilities available to them.

    If a function only needs the ability to iterate over the elements contained, you pass a range of iterators.

    If the ability to add items is needed you give an insert_iterator.

    If the ability to delete is needed you pass the container.

    Of course there is also the issue of the elements in the container are immutable.
  • Anonymous
    February 05, 2004
    I'd vote for one class and a ReadOnly flag. I can understand the argument that a separate class might be apropriate, but I'd prefer to think of ReadOnly-ness as a state, rather than a behavior. If it's a behavior then two classes make sense, but I think in this case it's easier to model it as a state.
  • Anonymous
    February 05, 2004
    My ultimate preference (and I'm already ducking) is the C++ way, whereby params for a method can be marked const and forbids calling non-const methods of that parameter within the method, but that is a feature of the language as opposed to the framework. And to be honest, I've never tried using the const (or equivalent) modifier in a managed language, so maybe it even works (although I doubt it, given that I've never seen it done outside of C++)
  • Anonymous
    February 05, 2004
    There can be no doubt about it... compile time checks makes the world a better place. My vote is on the pattern with two types, where one is a specialized version of the other.
  • Anonymous
    February 05, 2004
    I'm going to opt for a single class, but with three private nested classes:

    1) Uses a factory class to return an initial ReadWrite collection.
    2) Can convert (by cloning) to and from read write/read only.
    3) Only downside is that the Add method is now a function that returns a collection reference.

    Public MustInherit Class TCollection
    Implements IEnumerable

    Public MustOverride Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
    Public MustOverride Function Add(ByVal item As Object) As TCollection
    Public MustOverride ReadOnly Property isReadOnly() As Boolean

    Private Sub New()
    End Sub

    Public Shared Function CreateInstance() As TCollection
    Return New TCollectionReadWrite
    End Function

    Private MustInherit Class TCollectionBase
    Inherits TCollection

    Protected al As New System.Collections.ArrayList

    Public Sub New()
    MyBase.New()
    End Sub

    Public Sub New(ByVal ts As TCollection)
    Me.New()
    For Each t As Object In ts
    al.Add(t)
    Next
    End Sub

    Public Overrides Function GetEnumerator() As System.Collections.IEnumerator
    Return al.GetEnumerator
    End Function

    End Class

    Private Class TCollectionReadOnly
    Inherits TCollectionBase

    Public Sub New(ByVal ts As TCollection)
    MyBase.New(ts)
    End Sub

    Public Sub New(ByVal ts As TCollection, ByVal newItem As Object)
    Me.New(ts)
    al.Add(newItem)
    End Sub

    Public Overrides Function Add(ByVal item As Object) As TCollection
    Return New TCollectionReadOnly(Me, item)
    End Function

    Public Overrides ReadOnly Property isReadOnly() As Boolean
    Get
    Return True
    End Get
    End Property

    Public ReadOnly Property GetReadWrite() As TCollection
    Get
    Return New TCollectionReadWrite(Me)
    End Get
    End Property

    End Class

    Private Class TCollectionReadWrite
    Inherits TCollectionBase

    Public Sub New()
    MyBase.New()
    End Sub

    Public Sub New(ByVal ts As TCollection)
    MyBase.New()
    End Sub

    Public Overrides Function Add(ByVal item As Object) As TCollection
    al.Add(item)
    Return Me
    End Function

    Public Overrides ReadOnly Property isReadOnly() As Boolean
    Get
    Return False
    End Get
    End Property

    Public ReadOnly Property GetReadOnly() As TCollection
    Get
    Return New TCollectionReadOnly(Me)
    End Get
    End Property

    End Class

    End Class
  • Anonymous
    February 07, 2004
    Why not just default the Collection classes to readonly or immutable and have a method that returns a mutable copy? So you would have a Hashtable class that is specialized by a HashtableMutable class that has a MutableCopy method.

    Just a thought, probably makes no sense.