简体   繁体   中英

Force classes to contain/use their own specific implementation of an abstract class

I'm developing a TUI library in C# and I need advice on how to do color themes for display objects. Objects that can be drawn on the screen all inherit from this interface:

public interface IDrawable
    {
        Area ScreenArea { get; }
        List<char[]> DisplayChars { get; }
        //some other properties...

    }

Or rather, more specifically, the interfaces for each drawable object implements this interface ( IWindow is a IDrawable ). Each IDrawable is drawn on a specified part of the console window represented by the Area struct:

public struct Area
    {
        public readonly int EndX;
        public readonly int EndY;
        public readonly int Height;
        public readonly int StartX;
        public readonly int StartY;
        public readonly int Width;

        public Area(int startX, int endX, int startY, int endY)
        {
            StartX = startX;
            EndX = endX;
            StartY = startY;
            EndY = endY;
            Height = endY - startY;
            Width = endX - startX;
        }

        /// <summary>
        /// Get the overlapping area between this area and another.
        /// </summary>
        /// <param name="refArea"></param>
        /// <returns>Overlap area relative to the upper left corner of the ref area.</returns>
        public Area OverlapWith(Area refArea)
        {
            //....
        }
    }

The actual drawing of objects is handled by methods in a static Display class, which call Console.Write() on each element in DisplayChars. I would like for each class that inherits from IDrawable to be forced to implement its own rules for how its area can be divided into separate areas of color, for example, popup windows might have separate colorable areas for its outer borders, its title (within its outer border), and its inner area.

I've been tossing over how to do this in my head for a while now. I need to make a type, ColorScheme , to contain the rules for what characters to write in what color. I decided the best way to do this would be to make it an abstract class, which contains a list of "sub-areas" that colors can be applied to separately.

I'd like for each non-abstract IDrawable to have to implement its own class inheriting from ColorScheme . For instance, the abstract Window : IWindow class would have no such implementation, but PopupWindow : Window class would have to have a corresponding type of PopupWindowColorScheme : ColorScheme in which the author of PopupWindow would define how to split the class' Area into separate regions. Each PopupWindow would have its own instance of this type to contain its specific colors.

Is this possible? If not, is there another way to force authors of IDrawable types to specify a method for splitting up their areas into colorable regions?

You can't force each IDrawable to have a unique implementation of ColorScheme (eg multiple different implementations of IDrawable could use PopupWindowColorScheme ). However, you can use generic type constraints to add additional requirements for implementing your interface, like this:

public interface IDrawable<TColorScheme> 
    where TColorScheme : ColorScheme
{
    Area ScreenArea { get; }
    List<char[]> DisplayChars { get; }
    //some other properties...
    TColorScheme ColorScheme { get; }
}

Now, every implementation of IDrawable needs to specify a type of ColorScheme to use. But a consumer could just implement IDrawable<ColorScheme> which sort of defeats the purpose (depending on your requirements). We can go a little further:

public interface IDrawable<TColorScheme> 
    where TColorScheme : ColorScheme, new()
{
}

public abstract class ColorScheme {  }

Here, since ColorScheme is abstract, and the generic type constraint requires the provided type parameter to implement a parameterless constructor ( new() ), ColorScheme itself cannot be used as a parameter. Any implementing class would need to specify a custom implementation of ColorScheme that provides a public, parameterless constructor.

But we can go even further:

public interface IDrawable { } 
public interface IDrawable<TDrawable, TColorScheme> : IDrawable
    where TDrawable : IDrawable, new()
    where TColorScheme : ColorScheme<TDrawable>, new()
{
    object ScreenArea { get; }
    List<char[]> DisplayChars { get; }
    //some other properties...
    TColorScheme ColorScheme { get; }
}

public abstract class ColorScheme<TDrawable>
    where TDrawable : IDrawable, new()
{
}

Here, each implementation of IDrawable has to specify what ColorScheme it uses and each ColorScheme also has to specify what IDrawable it applies to. And because each requires a parameterless constructor, neither would be able to specify the common base type. Implementing this now looks a bit strange:

public class MyDrawable : IDrawable<MyDrawable, MyColorScheme> { }

public class MyColorScheme : ColorScheme<MyDrawable> { }

It's still possible to implement a reusable ColorScheme or IDrawable , (eg MyOtherDrawable : MyDrawable uses MyColorScheme ). However, in my opinion, this is starting to get rather cumbersome and tedious to implement. In general, unless you have technical reasons why you have use a type constraint, I'd avoid using it, as you'll often find it too limiting in the future.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM