Lets say I have the following interfaces
// used by generators and other read only systems
interface IRealmProvider
{
Tile GetTile(int x, int y);
}
// used by interactive realms that can be modified
interface IRealm : IRealmProvider
{
bool SetTile(int x, int y, Tile tile);
}
// used by realms that have a fixed size, whether read only or writable
interface IRealmSize
{
int TilesWide { get; }
int TilesHigh { get; }
}
The interfaces are separate as I have both infinite realms with no size and bounded realms with a fixed size. Some realms will be writable, while others will not. One class uses these realms, but requires that the realm has a fixed size.
class RealmRenderer<T> where T : IRealmProvider, IRealmSize
{
public RealmRenderer(T realm) { ... }
}
Is there any drawbacks to doing it like this?
I could see an alternative like the following
interface IBoundedRealmProvider : IRealmProvider, IRealmSize { /* intentionally empty */ }
class RealmRenderer
{
public RealmRenderer(IBoundedRealmProvider realm) { ... }
}
But this creates a stricter requirement for the various realm types to implement this specific interface just to satisfy the renderer, where using generics leaves this specific requirement out of the realm implementation. It also means I'll need yet another empty interface if I want a bounded realm that is writable.
interface IBoundedRealm : IRealm, IRealmSize { /* intentionally empty */ }
2 Answers 2
Your model isn't very clear on names and purpose and calling it a provider does not seem helpful. What I make of your story is this:
interface IReadOnlyRealm
{
Tile GetTile(int x, int y);
}
interface IRealm : IReadOnlyRealm
{
bool SetTile(int x, int y, Tile tile);
}
interface IBoundedRealm : IRealm
{
int TilesWide { get; }
int TilesHigh { get; }
}
class RealmRenderer<T> where T : IBoundedRealm
{
public RealmRenderer(T realm) { ... }
}
The trouble with this is that your "richest" interface has a limitation compared to its parent interface. Typically child interfaces are parent interfaces plus something extra. Your extra is a limitation rather than an enrichment. This violates the Liskov substitution principle.
Inheritance is not the right mechanism for implementing your bounded realm. It seems better to just have one type of realm with bounds and set the bounds to -1 or MaxInt if you want no bounds at all. This could be the default for a parameterless constructor. Another constructor could take bounds.
-
That was one consideration I had as well, however that puts the error checking on the run time which I was trying to avoid. By using generics, I can put the constraint on at compile time, leading to less error prone code.William– William08/31/2019 23:26:16Commented Aug 31, 2019 at 23:26
Your two suggestions mean different things, implementations of
class RealmRenderer<T> where T : IRealmProvider, IRealmSize
{
public RealmRenderer(T realm) { ... }
}
can render a single type T
which must implement both interfaces. In contrast
class RealmRenderer
{
public RealmRenderer(IBoundedRealmProvider realm) { ... }
}
can render any type which implements the IBoundedRealmProvider
interface. As you point out this extra sub-interface places an extra constraint on implementing classes. However you can use generics to implement this behaviour by making the method generic instead of the interface:
class RealmRenderer
{
public RealmRenderer<T>(T realm) where T : IRealmProvider, IRealmSize
}
interface IRealm
, but they are notpartial
.