2

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
    Possible duplicate of Generics vs common interface? – Ben Cottrell Aug 31 at 8:36
  • As far as I can tell, the duplicate focuses a lot on how you can know the generic type after the object has been made, with the examples being that returning a T is better than returning an interface. But in my case, I'm not reading anything out, I am just interested in if this is a bad way to reduce the number of interfaces and the strict requirement of implementing those interfaces along with the others. – William Mariager Aug 31 at 11:26
  • 1
    Is there a typo? You have two definitions of interface IRealm, but they are not partial. – Erik Eidt Aug 31 at 16:08
  • I would question if making a "this specific renderer only works with bounded realms" is really necessary to have compile-time check. I feel that making a complicated type system for this constrain is just not worth it. And that runtime check is just fine. – Euphoric Sep 2 at 15:14
3

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 Mariager Aug 31 at 23:26
1

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
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.