2016-05-08 8 views
1

Ich entwickle eine TUI-Bibliothek in C# und ich benötige Ratschläge, wie man Farbthemen für Anzeigeobjekte erstellen kann. Objekte, die alle auf dem Bildschirm gezeichnet werden können erben von dieser Schnittstelle:Force-Klassen, die eine eigene spezifische Implementierung einer abstrakten Klasse enthalten/verwenden

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

    } 

Oder besser gesagt, genauer gesagt, die Schnittstellen für jedes ziehbar Objekt implementiert diese Schnittstelle (IWindow ist ein IDrawable). Jeder IDrawable wird auf einen bestimmten Teil des Konsolenfensters von der Fläche Struktur dargestellt gezeichnet:

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) 
     { 
      //.... 
     } 
    } 

Die eigentliche Zeichnung von Objekten wird durch Verfahren in einer statischen Display Klasse behandelt, die Console.Write() auf jedem Element in DisplayChars nennen. Ich möchte für jede Klasse, die von IDrawable erbt, gezwungen werden, ihre eigenen Regeln zu implementieren, wie ihr Bereich in einzelne Farbbereiche unterteilt werden kann, zum Beispiel können Popup-Fenster separate färbbare Bereiche für ihre äußeren Rahmen haben, ihren Titel (innerhalb seine äußere Grenze) und sein innerer Bereich.

Ich habe schon eine Weile darüber nachgedacht, wie ich das in meinem Kopf machen kann. Ich muss einen Typ eingeben, ColorScheme, um die Regeln für welche Zeichen in welcher Farbe zu schreiben enthalten. Ich entschied, dass der beste Weg dies zu tun wäre, es zu einer abstrakten Klasse zu machen, die eine Liste von "Unterbereichen" enthält, auf die Farben separat angewendet werden können.

Ich möchte für jedes nicht-abstrakte IDrawable eine eigene Klasse implementieren müssen, die von ColorScheme erbt. Zum Beispiel hätte die Klasse Window : IWindow keine solche Implementierung, aber die Klasse PopupWindow : Window müsste einen entsprechenden Typ von PopupWindowColorScheme : ColorScheme haben, in dem der Autor von PopupWindow definieren würde, wie die Klasse 'Area in separate Bereiche aufgeteilt werden soll. Jede PopupWindow würde eine eigene Instanz dieses Typs haben, um ihre spezifischen Farben zu enthalten.

Ist das möglich? Wenn nicht, gibt es eine andere Möglichkeit, Autoren von IDrawable Typen zu zwingen, eine Methode anzugeben, um ihre Bereiche in färbbare Regionen aufzuteilen?

Antwort

0

Sie können sie nicht IDrawable zwingen, eine einzigartige Implementierung von ColorScheme zu haben (zum Beispiel mehr verschiedener Implementierungen von IDrawable könnte PopupWindowColorScheme verwenden). Sie können jedoch generic type constraints verwenden, um zusätzliche Anforderungen für die Schnittstelle implementiert, wie folgt aus:

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

Nun muss jede Implementierung von IDrawable auf eine Art ColorScheme angeben zu verwenden. Aber ein Verbraucher könnte nur IDrawable<ColorScheme> implementieren, die den Zweck (abhängig von Ihren Anforderungen) besiegt. Wir können ein wenig weiter gehen:

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

public abstract class ColorScheme { } 

Da hier ColorScheme abstrakt ist, und der generische Typeinschränkung erfordert die bereitgestellte Typparameter einen parameterlosen Konstruktor (new()) zu implementieren, ColorScheme selbst nicht als Parameter verwendet werden kann. Jede implementierende Klasse muss eine benutzerdefinierte Implementierung von ColorScheme angeben, die einen öffentlichen, parameterlosen Konstruktor bereitstellt.

Aber wir können noch weiter gehen:

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() 
{ 
} 

Hier ist jede Implementierung von IDrawable hat festgelegt, welche ColorScheme es verwendet und jedes ColorScheme muss auch angeben, was IDrawable es gilt. Und da jeder einen parameterlosen Konstruktor benötigt, kann keiner den gemeinsamen Basistyp angeben. Die Umsetzung dieses jetzt ein bisschen seltsam aussieht:

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

public class MyColorScheme : ColorScheme<MyDrawable> { } 

Es ist immer noch möglich, eine wiederverwendbare ColorScheme oder IDrawable, zu implementieren (z MyOtherDrawable : MyDrawable verwendet MyColorScheme). Meiner Meinung nach wird dies jedoch zunehmend umständlich und langwierig. Im Allgemeinen, wenn Sie keine technischen Gründe haben, warum Sie eine Art Einschränkung verwenden, würde ich vermeiden, es zu verwenden, weil Sie es häufig zu begrenzend in der Zukunft finden werden.

+0

Dies sollte perfekt sein! Vielen Dank für die schnelle Antwort :) Jetzt muss ich nur noch die Innereien herausfinden, wie ein ColorScheme aussehen sollte, vorzugsweise so, dass die Draw-Methoden für jedes Zeichen, das sie auf den Bildschirm schreiben, keine Farbe überprüfen und einstellen müssen . – thetiniestmouse

+0

Hmm, kleines Problem, wegen schlechter Vererbungshierarchie. Ich habe derzeit Schnittstellen für abstrakte Klassen, die von IDrawable erben, und spezifische Typen, die indirekt über ihre abstrakte Basis erben, mit der Idee, dass Klassen wie "WindowManager" und "ControlManager" ihre jeweiligen Typen über ihre Schnittstellen handhaben und trotzdem behandeln könnten sie als Zeichen. Wäre es zu schrecklich, Casting zu verwenden und sich auf diejenigen verlassen zu müssen, die benutzerdefinierte Typen erstellen, z. B. "IDrawable" und "IWindow" zu verwenden, wenn ein neuer Fenstertyp erstellt wird, wie sie sollen? – thetiniestmouse

+0

@thetiniestmouse In diesem Fall würde ich empfehlen, eine nicht generische Schnittstelle 'IDrawable' zu ​​behalten, die alle Kerneigenschaften und Methoden * anders * als' ColorScheme' und eine Unterschnittstelle 'IDrawable : IDrawable' spezifiziert, die' ColorScheme 'liefert '-spezifische Funktionalität. Sie können sogar eine Eigenschaft auf 'IDrawable' haben, die die Basis 'ColorScheme' freilegt und sie 'IDrawable 'mit' new T ColorScheme {get; } '. (Dies ist ähnlich wie "IEnumerable : IEnumerable" funktioniert) –

Verwandte Themen