Reputation: 163
public interface IBlackBox<T> {
bool IsValid();
T GetValue();
}
public abstract class Box<T>:IBlackBox<T> {
public bool IsValid() {
return true;
}
public abstract T GetValue();
}
A Box
produces a value on demand; could be an on-demand computation, or random, or just a fancy nullable, etc. A BlackBox
does the same thing, except that it might expire or otherwise become invalid. Box
es need to fit in BlackBox
lists (and emphatically not the inverse), which dictates the inheritance hierarchy.
What I don't like is Box
being an abstract class instead of an interface. This causes two headaches. First, it means I can't derive a struct
, which would be very much preferred in the case of a Box
which contains only a static value.
Second, it means I can't subclass anything else. For instance, one intended use is a Dictionary<string,BlackBox<int>>
which is itself a Box
and sums its values on demand. There's no good reason this shouldn't just subclass Dictionary
, and at least one very good reason it should: not wrapping the IDictionary
interface line-for-line! But there are places where I need the Box
guarantee of validity, and it seems like I can only have one or the other.
I could have an interface IBox<T> : IBlackBox<T>
which is empty and serves only as a "trust me" label. That would mean duplicating Box
's one-line IsValid()
in every class that implements IBox<T>
, but that's considerably less hassle than hand-wrapping IDictionary
. I'm hoping for a more concrete solution, though.
Upvotes: 1
Views: 98
Reputation: 81307
I would suggest having an IValidBox<T> : IBlackBox<T>
which a contract which specifies that no legitimate implementation may ever have IsValid
return false, and that consumers are entitled to call GetValue
without checking IsValid
. Additionally, to reduce boilerplate, you could define an abstract class ValidBoxBase<T>
which implements IValidBox<T>
, so that implementations of IValidBox<T>
that don't need to inherit anything else could save some boilerplate. Not much in this brief example, but perhaps a significant amount in more fleshed-out scenarios.
Upvotes: 0
Reputation: 163
Here's the best solution I've come up with. This is just the bare bones, but I've put a full implementation (with better names) on GitHub.
public interface IBlackBox
public interface IBlackBox<T> : IBlackBox {
Func<T> GetValue;
}
public interface IBox { }
public interface IBox<T> : IBlackBox<T>, IBox { }
public interface IWrappedBox { }
public interface IWrappedBox<T> : IBox<IBlackBox<T>>, IWrappedBox { }
public interface IEmptyBox { }
public static class EmptyBox {
public static EmptyBox<T> Get<T>() {
return EmptyBox<T>.value;
}
sealed class EmptyBox<T> : IBlackBox<T>, IEmptyBox {
public static readonly EmptyBox<T> value = new EmptyBox<T>();
public Func<T> GetValue {
get {
throw new NotSupportedException
("Cannot extract a value from an empty box.");
}
}
}
}
The non-generic interfaces allow testing at the type level, for instance:
IBlackBox something = someWrappedBox.GetValue();
if (something is IEmptyBox)
HandleEmpty();
else
HandleSomething(something.GetValue());
This still has to be combined with three rules that the compiler can't check, but the structure makes it easy to conform:
IBlackBox<T>
, IBlackBox
and IEmptyBox
cannot be inherited directly.IWrappedBox<T>.GetValue()
can return base IBlackBox<T>
.IEmptyBox
.IEmptyBox
is guaranteed to show up only when explicitly permitted; IBox<T>
is guaranteed to contain a good T
; and everything that can be inherited is an interface. Mission accomplished!
Upvotes: 2