C#design-在没有空接口的情况下,如何将类和枚举本质上分组到一个列表中?

C#design-在没有空接口的情况下,如何将类和枚举本质上分组到一个列表中?,c#,list,design-patterns,interface,C#,List,Design Patterns,Interface,我正在设计一个文本渲染器。我正在构建如何将字符串拆分为适合文本框的行的逻辑。我想这样做的方法是首先将整个字符串拆分为“单词”、“空格”和“换行符”,然后构建一个算法来测量一行上可以容纳多少,然后再移动到下一行 然而,“Word”必须是一个类/结构,因为它想要保存额外的属性,如“FontStyle”、“color”等。“Space”和“NewLine”只是标记,它们不需要任何数据 目前,我有以下框架: interface ILineComponent { } struct Word : ILin

我正在设计一个文本渲染器。我正在构建如何将字符串拆分为适合文本框的行的逻辑。我想这样做的方法是首先将整个字符串拆分为“单词”、“空格”和“换行符”,然后构建一个算法来测量一行上可以容纳多少,然后再移动到下一行

然而,“Word”必须是一个类/结构,因为它想要保存额外的属性,如“FontStyle”、“color”等。“Space”和“NewLine”只是标记,它们不需要任何数据

目前,我有以下框架:

interface ILineComponent
{
}

struct Word : ILineComponent
{
    public string Text;
    public FontStyle Style;
    public Colour Colour;

    public Word(string text, FontStyle style, Colour colour)
    {
        Text = text;
        Style = style;
        Colour = colour;
    }
}

struct Space : ILineComponent
{
}

struct NewLine : ILineComponent
{
}

struct Line
{
    public List<ILineComponent> Segments;

    public Line(List<ILineComponent> segments)
    {
        Segments = segments;
    }
}
但这种设计违反了法律


如何更好地设计它?

空接口不仅违反了CA1040。它还违反了DRY的干净代码原则,因为您很可能会在代码的多个位置使用if/else语句。如果不根据空接口的存在做出一些决定,那么对于空接口,您还会做什么

所以,是的,让我们摆脱它。感谢您关心编译器警告并尝试编写更好的代码

首先,我认为
空间的定义是不完整的。如果没有字体,空间无法确定其宽度。因此,我认为空格应该类似于
Word
。也许它不需要颜色,当然也不需要文本作为参数

struct Space : ILineComponent
{
    public readonly string Text = " ";
    public FontStyle Style;

    public Word(FontStyle style)
    {
        Style = style;
    }
}
<>我不认为这是一个特殊的例子:<代码> Word < /代码>。稍后你会看到,为什么会这样。这是因为我提议的界面

而且我认为这个代码有误导性

foreach (ILineComponent component in line)
由于您还不知道将有多少行,因此变量
line
不正确。这应该是
componentsOfText
或类似内容。在循环内部,您可以在需要时启动新行

接下来,我建议您将这三个组件需要做的所有事情都放到接口中。据我所知,那就是:

interface ILineComponent
{
    int Measure();
    bool IsTrimmedAtEndOfLine;
    bool TerminatesLine;
}
对于
换行符
Measure()
只返回0

然后,您的代码如下所示:

//       IList<LineComponent> is a line
// IList<       -"-          > are many lines
// Maybe you want to define an alias for that, or even a class
IList<IList<ILineComponent>> lines = new ...;

int remainingSpace = 2000; // px or whatever
while (componentsOfText.Length > 0)
{
     component = componentsOfText[0];

     var space = component.Measure();
     if (space > remainingSpace)
     {
          // finish current line and start a new one, 
          // i.e. add a new List<LineComponent>,
          // reset remaining space,
          // do the line post processing (e.g. space width adjustment)

          // Do not remove component from the list
          continue;
     }

     if (component.TerminatesLine)
     {
           // Finish current line and start a new line
           // just as before
     }

     remainingSpace -= space;
     componentsOfText.Remove(component);
}

我用托马斯的答案来帮助构建更强大的东西

助手类:

internal enum LineComponentType { Word, Space, NewLine }

internal interface ILineComponent
{
    float Length { get; }
    LineComponentType Type { get; }
    Colour Colour { get; }
}

internal class NewLine : ILineComponent
{
    public float Length => 0;
    public LineComponentType Type => LineComponentType.NewLine;
    public Colour Colour => Colour.Transparent;
}

internal class Space : ILineComponent
{
    public Space(HFont font, FontStyle fontStyle)
    {
        Length = font.MeasureString(' ', fontStyle).x;
    }

    public float Length { get; }

    public LineComponentType Type => LineComponentType.Space;
    public Colour Colour => Colour.Transparent;
    public override string ToString() => " ";
}

internal class Word : ILineComponent
{
    private readonly HFont _font;

    public Word(string text, HFont font, FontStyle style, Colour colour)
    {
        Text = text;
        _font = font;
        Style = style;
        Colour = colour;

        Length = font.MeasureString(text, style).x;
    }

    public float Length { get; }

    public LineComponentType Type => LineComponentType.Word;

    public string Text { get; }

    public FontStyle Style { get; }

    public Colour Colour { get; }

    public float MeasureCharAtIndex(int index) => _font.MeasureString(Text[index], Style).x;

    public Word SubWord(int startIndex) => new Word(Text.Substring(startIndex), _font, Style, Colour);

    public Word SubWord(int startIndex, int length) => new Word(Text.Substring(startIndex, length), _font, Style, Colour);

    public override string ToString() => Text;
}
用于渲染的主类:

public class HText : IRectGraphic
{
    //...

    private List<List<ILineComponent>> SplitTextToLines(string text)
    {
        //Convert text into a list of IComponents
        var allComponents = new List<ILineComponent>();

        text.Replace("\r\n", "\n");
        text.Replace("\r", "\n");

        while (!text.IsNullOrEmpty())
        {
            if (text[0] == ' ')
            {
                allComponents.Add(new Space(Font, FontStyle));
                text = text.Remove(0, 1);
            }
            else if (text[0] == '\n')
            {
                allComponents.Add(new NewLine());
                text = text.Remove(0, 1);
            }
            else //it's a word
            {
                var words = text.Split(new[] { '\n', ' ' });
                allComponents.Add(new Word(words[0], Font, FontStyle, Colour));
                text = text.Remove(0, words[0].Length);
            }
        }

        var lines = new List<List<ILineComponent>>();

        //Split IComponents into lines
        var currentLine = new List<ILineComponent>();

        bool oneLetterWiderThanWholeLine = false;

        while (!allComponents.IsEmpty())
        {
            var component = allComponents[0];

            switch (component.Type)
            {
                case LineComponentType.Word:
                    var word = (Word)component;

                    if (currentLine.Sum(c => c.Length) + word.Length <= w) //if it fits, add it to the current line
                    {
                        //if we started a new line with a space then a word, remove the space
                        if (lines.Count != 0 && currentLine.Count == 1 && currentLine[0].Type == LineComponentType.Space)
                            currentLine.RemoveAt(0);

                        currentLine.Add(component);
                        allComponents.RemoveAt(0);
                    }
                    else if (currentLine.Count == 0) //if doesn't fit, but it is the first word in a line, we will split and wrap it
                    {
                        int splitAt = 0;
                        float width = 0;

                        float nextCharLength;
                        while (width + (nextCharLength = word.MeasureCharAtIndex(splitAt)) <= w)
                        {
                            width += nextCharLength;
                            splitAt++;
                        }

                        if (splitAt == 0) //if one letter is too wide to fit, display it anyway
                        {
                            splitAt = 1;
                            oneLetterWiderThanWholeLine = true;
                        }

                        var word1 = word.SubWord(0, splitAt);
                        var word2 = word.SubWord(splitAt);

                        currentLine.Add(word1);
                        lines.Add(currentLine);
                        currentLine = new List<ILineComponent>();
                        allComponents.RemoveAt(0);
                        allComponents.Insert(0, word2);
                    }
                    else //push the word to the next line
                    {
                        lines.Add(currentLine);
                        currentLine = new List<ILineComponent>();
                        //we don't add the next word to the next line yet - it might not fit
                    }
                    break;

                case LineComponentType.Space:
                    if (currentLine.Sum(c => c.Length) + component.Length > w)
                    {
                        lines.Add(currentLine);
                        currentLine = new List<ILineComponent>();
                    }
                    currentLine.Add(component);
                    allComponents.RemoveAt(0);
                    break;

                case LineComponentType.NewLine:
                    lines.Add(currentLine);
                    currentLine = new List<ILineComponent>();
                    allComponents.RemoveAt(0);
                    break;

                default:
                    throw new HException("Htext/SplitTextToLines: Unhandled component type {0}", component.Type);
            }
        }

        if (currentLine.Count > 0)
            lines.Add(currentLine);

        //Size warnings
        if (lines.Count * Font.BiggestChar.y > h)
            HConsole.Log("HText/SetVertices: lines total height ({0}) is bigger than text box height ({1})", lines.Count * Font.BiggestChar.y, h);

        if (oneLetterWiderThanWholeLine)
            HConsole.Log("HText/SetVertices: a single letter is beyond the text box width ({0})", w);

        return lines;
    }

    private void SetVertices()
    {
        var vertices = new List<Vertex>();

        var lines = SplitTextToLines(Text);

        var dest = new Rect();

        switch (VAlignment)
        {
            case VAlignment.Top:
                dest.y = 0;
                break;
            case VAlignment.Centre:
                dest.y = (h - lines.Count * Font.BiggestChar.y) / 2;
                break;
            case VAlignment.Bottom:
                dest.y = h - lines.Count * Font.BiggestChar.y;
                break;
            case VAlignment.Fill:
                dest.y = 0;
                break;
            default:
                throw new HException("HText/SetVertices: alignment {0} was not catered for.", VAlignment);
        }

        foreach (var line in lines)
        {
            switch (HAlignment)
            {
                case HAlignment.LeftJustified:
                    dest.x = 0;
                    break;
                case HAlignment.Centred:
                    dest.x = (w - line.Sum(c => c.Length)) / 2;
                    break;
                case HAlignment.RightJustified:
                    dest.x = w - line.Sum(c => c.Length);
                    break;
                case HAlignment.FullyJustified:
                    dest.x = 0;
                    break;
                default:
                    throw new HException("HText/SetVertices: alignment {0} was not catered for.", HAlignment);
            }

            foreach (var com in line)
                foreach (char c in com.ToString())
                {
                    var source = Font.CharPositionsNormalised[System.Drawing.FontStyle.Regular][c];
                    dest.w = Font.CharPositions[System.Drawing.FontStyle.Regular][c].w;
                    dest.h = Font.CharPositions[System.Drawing.FontStyle.Regular][c].h;

                    vertices.Add(HF.Geom.QuadToTris(
                        new Vertex(dest.TopLeft, com.Colour, source.TopLeft),
                        new Vertex(dest.TopRight, com.Colour, source.TopRight),
                        new Vertex(dest.BottomLeft, com.Colour, source.BottomLeft),
                        new Vertex(dest.BottomRight, com.Colour, source.BottomRight)
                        ));

                    dest.x += dest.w;
                }
            dest.y += Font.BiggestChar.y;
        }

        _vertices = vertices.ToArray();
    }

    public virtual void Render(ref Matrix4 projection, ref Matrix4 modelView)
    {
        if (w == 0 || h == 0)
            return;

        if (HV.LastBoundTexture != Texture.ID)
        {
            OpenGL.BindTexture(TextureTarget.Texture2D, Texture.ID);
            HV.LastBoundTexture = Texture.ID;
        }

        if (HV.LastBoundVertexBuffer != VertexBuffer)
            Bind();

        if (_verticesChanged)
        {
            SetVertices();
            _verticesChanged = false;

            OpenGL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * Vertex.STRIDE, _vertices, BufferUsageHint.StreamDraw);
        }

        var mv = Matrix4.Translate(ref modelView, x, y, 0);

        Shader.Render(ref projection, ref mv, _vertices.Length, PrimitiveType.Triangles);
    }
}
公共类HText:IRectGraphic
{
//...
私有列表拆分文本列表(字符串文本)
{
//将文本转换为IComponents列表
var allComponents=新列表();
text.Replace(“\r\n”,“\n”);
文本。替换(“\r”和“\n”);
而(!text.IsNullOrEmpty())
{
如果(文本[0]='')
{
添加(新空格(字体、字体样式));
text=text.Remove(0,1);
}
else if(文本[0]='\n')
{
添加(新换行符());
text=text.Remove(0,1);
}
否则,这是一个词
{
var words=text.Split(新[]{'\n',''});
添加(新单词(单词[0],字体,字体样式,颜色));
text=text.Remove(0,单词[0]。长度);
}
}
变量行=新列表();
//将IComponents拆分为行
var currentLine=新列表();
bool oneletterwidthanwholeline=假;
而(!allComponents.IsEmpty())
{
var分量=所有分量[0];
开关(组件类型)
{
案例LineComponentType。Word:
变量词=(词)成分;
if(currentLine.Sum(c=>c.Length)+单词长度c.Length)+组件长度>w)
{
行。添加(当前行);
currentLine=新列表();
}
currentLine.Add(组件);
移除所有组件(0);
打破
案例LineComponentType.NewLine:
行。添加(当前行);
currentLine=新列表();
移除所有组件(0);
打破
违约:
抛出新的HException(“Htext/splittexttoline:未处理的组件类型{0}”,component.type);
}
}
如果(currentLine.Count>0)
行。添加(当前行);
//尺寸警告
if(lines.Count*Font.BiggestChar.y>h)
HConsole.Log(“HText/SetVertices:lines总高度({0})大于文本框高度({1})”,lines.Count*Font.BiggestChar.y,h);
如果(一个字母宽或整行)
Log(“HText/SetVertices:单个字母超出文本框宽度({0})”,w);
回流线;
}
私有void SetVertices()
{
var顶点=新列表();
变量行=拆分文本行(文本);
var dest=new Rect();
开关(有效)
{
案例有效期。顶部:
目的地y=0;
打破
案例验证中心:
dest.y=(h-lines.Count*Font.BiggestChar.y)/2;
打破
箱子有效期。底部:
dest.y=h-lines.Count*Font.BiggestChar.y;
打破
案例有效期。填写:
目的地y=0;
打破
违约:
抛出新的HException(“HText/SetVertices:alignment{0}不适合。”,VAlignment);
}
foreach(行中的var行)
{
开关(HAlignment)
{
case HAlignment.LeftJustified:
目标x=0;
打破
案例HAlignment.中心:
dest.x=(w-line.Sum(c=>c.Length))/2;
打破
case HAlignment.RightJustified:
dest.x=w-行和(c=>c.Length);
打破
案例哈尔
internal enum LineComponentType { Word, Space, NewLine }

internal interface ILineComponent
{
    float Length { get; }
    LineComponentType Type { get; }
    Colour Colour { get; }
}

internal class NewLine : ILineComponent
{
    public float Length => 0;
    public LineComponentType Type => LineComponentType.NewLine;
    public Colour Colour => Colour.Transparent;
}

internal class Space : ILineComponent
{
    public Space(HFont font, FontStyle fontStyle)
    {
        Length = font.MeasureString(' ', fontStyle).x;
    }

    public float Length { get; }

    public LineComponentType Type => LineComponentType.Space;
    public Colour Colour => Colour.Transparent;
    public override string ToString() => " ";
}

internal class Word : ILineComponent
{
    private readonly HFont _font;

    public Word(string text, HFont font, FontStyle style, Colour colour)
    {
        Text = text;
        _font = font;
        Style = style;
        Colour = colour;

        Length = font.MeasureString(text, style).x;
    }

    public float Length { get; }

    public LineComponentType Type => LineComponentType.Word;

    public string Text { get; }

    public FontStyle Style { get; }

    public Colour Colour { get; }

    public float MeasureCharAtIndex(int index) => _font.MeasureString(Text[index], Style).x;

    public Word SubWord(int startIndex) => new Word(Text.Substring(startIndex), _font, Style, Colour);

    public Word SubWord(int startIndex, int length) => new Word(Text.Substring(startIndex, length), _font, Style, Colour);

    public override string ToString() => Text;
}
public class HText : IRectGraphic
{
    //...

    private List<List<ILineComponent>> SplitTextToLines(string text)
    {
        //Convert text into a list of IComponents
        var allComponents = new List<ILineComponent>();

        text.Replace("\r\n", "\n");
        text.Replace("\r", "\n");

        while (!text.IsNullOrEmpty())
        {
            if (text[0] == ' ')
            {
                allComponents.Add(new Space(Font, FontStyle));
                text = text.Remove(0, 1);
            }
            else if (text[0] == '\n')
            {
                allComponents.Add(new NewLine());
                text = text.Remove(0, 1);
            }
            else //it's a word
            {
                var words = text.Split(new[] { '\n', ' ' });
                allComponents.Add(new Word(words[0], Font, FontStyle, Colour));
                text = text.Remove(0, words[0].Length);
            }
        }

        var lines = new List<List<ILineComponent>>();

        //Split IComponents into lines
        var currentLine = new List<ILineComponent>();

        bool oneLetterWiderThanWholeLine = false;

        while (!allComponents.IsEmpty())
        {
            var component = allComponents[0];

            switch (component.Type)
            {
                case LineComponentType.Word:
                    var word = (Word)component;

                    if (currentLine.Sum(c => c.Length) + word.Length <= w) //if it fits, add it to the current line
                    {
                        //if we started a new line with a space then a word, remove the space
                        if (lines.Count != 0 && currentLine.Count == 1 && currentLine[0].Type == LineComponentType.Space)
                            currentLine.RemoveAt(0);

                        currentLine.Add(component);
                        allComponents.RemoveAt(0);
                    }
                    else if (currentLine.Count == 0) //if doesn't fit, but it is the first word in a line, we will split and wrap it
                    {
                        int splitAt = 0;
                        float width = 0;

                        float nextCharLength;
                        while (width + (nextCharLength = word.MeasureCharAtIndex(splitAt)) <= w)
                        {
                            width += nextCharLength;
                            splitAt++;
                        }

                        if (splitAt == 0) //if one letter is too wide to fit, display it anyway
                        {
                            splitAt = 1;
                            oneLetterWiderThanWholeLine = true;
                        }

                        var word1 = word.SubWord(0, splitAt);
                        var word2 = word.SubWord(splitAt);

                        currentLine.Add(word1);
                        lines.Add(currentLine);
                        currentLine = new List<ILineComponent>();
                        allComponents.RemoveAt(0);
                        allComponents.Insert(0, word2);
                    }
                    else //push the word to the next line
                    {
                        lines.Add(currentLine);
                        currentLine = new List<ILineComponent>();
                        //we don't add the next word to the next line yet - it might not fit
                    }
                    break;

                case LineComponentType.Space:
                    if (currentLine.Sum(c => c.Length) + component.Length > w)
                    {
                        lines.Add(currentLine);
                        currentLine = new List<ILineComponent>();
                    }
                    currentLine.Add(component);
                    allComponents.RemoveAt(0);
                    break;

                case LineComponentType.NewLine:
                    lines.Add(currentLine);
                    currentLine = new List<ILineComponent>();
                    allComponents.RemoveAt(0);
                    break;

                default:
                    throw new HException("Htext/SplitTextToLines: Unhandled component type {0}", component.Type);
            }
        }

        if (currentLine.Count > 0)
            lines.Add(currentLine);

        //Size warnings
        if (lines.Count * Font.BiggestChar.y > h)
            HConsole.Log("HText/SetVertices: lines total height ({0}) is bigger than text box height ({1})", lines.Count * Font.BiggestChar.y, h);

        if (oneLetterWiderThanWholeLine)
            HConsole.Log("HText/SetVertices: a single letter is beyond the text box width ({0})", w);

        return lines;
    }

    private void SetVertices()
    {
        var vertices = new List<Vertex>();

        var lines = SplitTextToLines(Text);

        var dest = new Rect();

        switch (VAlignment)
        {
            case VAlignment.Top:
                dest.y = 0;
                break;
            case VAlignment.Centre:
                dest.y = (h - lines.Count * Font.BiggestChar.y) / 2;
                break;
            case VAlignment.Bottom:
                dest.y = h - lines.Count * Font.BiggestChar.y;
                break;
            case VAlignment.Fill:
                dest.y = 0;
                break;
            default:
                throw new HException("HText/SetVertices: alignment {0} was not catered for.", VAlignment);
        }

        foreach (var line in lines)
        {
            switch (HAlignment)
            {
                case HAlignment.LeftJustified:
                    dest.x = 0;
                    break;
                case HAlignment.Centred:
                    dest.x = (w - line.Sum(c => c.Length)) / 2;
                    break;
                case HAlignment.RightJustified:
                    dest.x = w - line.Sum(c => c.Length);
                    break;
                case HAlignment.FullyJustified:
                    dest.x = 0;
                    break;
                default:
                    throw new HException("HText/SetVertices: alignment {0} was not catered for.", HAlignment);
            }

            foreach (var com in line)
                foreach (char c in com.ToString())
                {
                    var source = Font.CharPositionsNormalised[System.Drawing.FontStyle.Regular][c];
                    dest.w = Font.CharPositions[System.Drawing.FontStyle.Regular][c].w;
                    dest.h = Font.CharPositions[System.Drawing.FontStyle.Regular][c].h;

                    vertices.Add(HF.Geom.QuadToTris(
                        new Vertex(dest.TopLeft, com.Colour, source.TopLeft),
                        new Vertex(dest.TopRight, com.Colour, source.TopRight),
                        new Vertex(dest.BottomLeft, com.Colour, source.BottomLeft),
                        new Vertex(dest.BottomRight, com.Colour, source.BottomRight)
                        ));

                    dest.x += dest.w;
                }
            dest.y += Font.BiggestChar.y;
        }

        _vertices = vertices.ToArray();
    }

    public virtual void Render(ref Matrix4 projection, ref Matrix4 modelView)
    {
        if (w == 0 || h == 0)
            return;

        if (HV.LastBoundTexture != Texture.ID)
        {
            OpenGL.BindTexture(TextureTarget.Texture2D, Texture.ID);
            HV.LastBoundTexture = Texture.ID;
        }

        if (HV.LastBoundVertexBuffer != VertexBuffer)
            Bind();

        if (_verticesChanged)
        {
            SetVertices();
            _verticesChanged = false;

            OpenGL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * Vertex.STRIDE, _vertices, BufferUsageHint.StreamDraw);
        }

        var mv = Matrix4.Translate(ref modelView, x, y, 0);

        Shader.Render(ref projection, ref mv, _vertices.Length, PrimitiveType.Triangles);
    }
}