C# 这需要先决条件:Don';不要重复你自己或单一责任原则?

C# 这需要先决条件:Don';不要重复你自己或单一责任原则?,c#,oop,dry,single-responsibility-principle,C#,Oop,Dry,Single Responsibility Principle,在升级一些旧代码时,我发现这两个OO原则似乎相互冲突 考虑以下伪代码(这是我遇到的简化版本): 这段代码做两件事:它根据记录的状态统计记录的数量,它还根据记录的状态为每一行着色。这很混乱,但由于这两个操作(到目前为止)一直是同时调用的,因此没有造成任何问题,并且使得附加状态等维护要求易于添加 然而,单一责任原则告诉我,我应该将其分为两种不同的方法: (编辑)小调:我刚刚意识到我可能在这里误用了“单一责任原则”,据我所知,它指的是班级。“每个方法一次操作”设计模式的术语是什么 int number

在升级一些旧代码时,我发现这两个OO原则似乎相互冲突

考虑以下伪代码(这是我遇到的简化版本):

这段代码做两件事:它根据记录的状态统计记录的数量,它还根据记录的状态为每一行着色。这很混乱,但由于这两个操作(到目前为止)一直是同时调用的,因此没有造成任何问题,并且使得附加状态等维护要求易于添加

然而,单一责任原则告诉我,我应该将其分为两种不同的方法:

(编辑)小调:我刚刚意识到我可能在这里误用了“单一责任原则”,据我所知,它指的是班级。“每个方法一次操作”设计模式的术语是什么

int numberOfNewRecords;
int numberOfOldRecords;
int numberOfUndefinedRecords;

void Count()
{
    foreach(Row row in dataGridView1.Rows)
    {
        switch(row.Cells["Status"])
        {
            case: "Old"
                numberOfOldRecords++;
                break;
            case: "New"
                numberOfNewRecords++;
                break;
            default:
                numberOfUndefinedRecords++;
                break;
        }
    }
}

void Colour()
{
    foreach(Row row in dataGridView1.Rows)
    {
        switch(row.Cells["Status"])
        {
            case: "Old"
                row.BackColor = Color.Red;
                break;
            case: "New"
                row.BackColor = Color.Blue;
                break;
            default:
                row.BackColor = Color.White;
                break;
        }
    }
}
但这一点您自己不要重复:loop和switch语句在这两种方法中都是重复的,而且由于这段代码最有可能的升级路径是添加其他状态,这会使将来的升级更加困难,而不是更少

我很难找到最优雅的方法来重构它,所以我觉得最好问问社区,以防我遗漏了一些明显的东西。你将如何处理这种情况

(编辑)

我提出了一个可能的解决方案,但在我看来,这就像是一个简单问题的过度工程的例子(它并没有真正解决最初的单一责任问题)

struct状态
{
公共字符串名称,
公共整数计数,
公共颜色,
}
Dictionary StatiDictionary=新字典();
无效初始化()
{
添加(新状态(“new”,0,Color.Red));
添加(新状态(“旧”,0,颜色.Blue));
添加(新状态(“未定义”,0,颜色.白色));
}
void color和countallrows()
{
foreach(dataGridView1.Rows中的行)
{
CountRow(行,统计词典);
彩色行(行,统计词典);
}
}
void CountRow(行,字典统计字典)
{
StatiDictionary[行单元格[“状态”]].Count++;
}
void colorRow(行,字典统计字典)
{
row.backcolor=统计[行单元格[“状态”]]颜色;
}

出于实用的原因,有很多编程规则需要不时被违反。在您的情况下,我同意DRY和SRP似乎在竞争,因此我建议两个标准来决定获胜者:

  • 遵守每一条准则意味着什么
  • 对于应用程序来说,遵守每一条规则意味着什么
  • 在这种情况下,对我来说,两次枚举网格行的效率低下似乎是压倒一切的因素,在这种特殊情况下,DRY将胜出。在其他一些情况下,情况可能正好相反


    值得添加一条注释来解释您所做的决定以及原因,以便稍后查看代码的任何人都能清楚地看到。这正是注释应该用来做的事情,即为什么代码在做它正在做的事情,而不仅仅是它正在做的事情。

    设计模式为决策提供信息。他们不应该被宗教追随。它们帮助您将属性添加到代码中,使其更易于维护。遵循每一种设计模式都会导致严重的过度工程,这比忽略某一原则要糟糕得多

    从表面上看,将多个操作合并成一个迭代器似乎是合理的。这比两次迭代更有效,但它限制了您执行两个操作,而不是一个或另一个。因此,您可以使用可行实现的结果属性来判断哪一个是最好的。如果您认为单独应用这些操作很重要,那么您可以重复自己的操作

    有一种解决方案可以同时满足这两种要求,但问题是它的设计过于复杂。您希望您的代码必须同时尊重DRY和SRP以及第一个解决方案的效率优势,这些属性包括:

    • 允许您单独应用操作-SRP
    • 不在行上重复迭代两次-干燥/效率
    • 不重复同一开关语句两次-干燥/效率
    在类似的伪代码中,使用Java风格的方法而不是函数式方法,您可以通过以下解决方案满足这些标准:

    public abstract class RowOperation {
        public void apply(string status, Row row) {
            switch(status)
            {
                case: "Old"
                    this.OldCase(row);
                    break;
                case: "New"
                    this.NewCase(row);
                    break;
                default:
                    this.OtherCase(row);
                    break;
            }
        }
        abstract void OldCase(Row);
        abstract void NewCase(Row);
        abstract void OtherCase(Row);
    }
    
    public class ColorRow implements RowOperation {
        private static final ColorCells OP = new ColorCells();
    
        private ColorCells(){}
    
        // This operation isn't stateful so we use a singleton :D
        public static RowOperation getInstance() {
            return this.OP
        }
    
        public void OldCase(row) {
            row.BackColor = Color.Red;
        }
    
        public void NewCase(row) {
            row.BackColor = Color.Blue;
        }
    
        public void OtherCase(row) {
            row.BackColor = Color.White;
        }
    }
    
    public class CountRow implements CellOperation {
        public int oldRows = 0;
        public int newRows = 0;
        public int otherRows= 0;
    
        // This operation is stateful so we use the contructor
        public CountRow() {}
    
        public void OldCase(Row row) {
            oldRows++;
        }
    
        public void NewCase(Row row) {
            newRows++;
        }
    
        public void OtherCase(Row row) {
            otherRows++;
        }
    }
    
    // For each row in the grid we will call each operation
    // function with the row status and the row
    void UpdateRows(Grid grid, RowOperation[] operations)
    {
        foreach(Row row in grid.Rows)
        {
            string status = row.Cells["Status"]
    
            foreach(RowOperation op in operations)
            {
                op.apply(status, row)
            }
        }
    }
    
    然后,您可以在一次迭代中对行应用多个操作,根据需要添加新操作

    RowOperations[] ops = {
        ColorRow.getInstance(),
        new CountRows()    
    };
    
    UpdateRows(dataGrid1, ops);
    

    但正如您所注意到的,实现您所读到的每一种设计模式都会导致这种过度工程化的解决方案。我甚至跳过了一个级别的类层次结构,但仍然很糟糕。代码具有此处所述设计模式的所有优点,但问题是,在应用程序上下文中,您真的需要所有这些属性吗?答案可能是否定的。

    在你的“过度设计”代码中有很多好主意,但是如果每种情况的代码都很简单,你可以使用
    操作
    ,这样你就不需要写那么多额外的代码。我不喜欢你的代码是,如果你有多个操作,你每次都会重复调度。这就是复合操作的意义所在。例如,组合的
    OldCase
    方法将调用列表中每个项的
    OldCase
    方法。只要在单个位置使用与原始代码类似的代码,在循环中进行切换可能是可以接受的。然而,如果您可能会添加一些其他操作,比如按类型计算选定的行,那么使用Matt answer中的idea重构代码是有意义的。
    public abstract class RowOperation {
        public void apply(string status, Row row) {
            switch(status)
            {
                case: "Old"
                    this.OldCase(row);
                    break;
                case: "New"
                    this.NewCase(row);
                    break;
                default:
                    this.OtherCase(row);
                    break;
            }
        }
        abstract void OldCase(Row);
        abstract void NewCase(Row);
        abstract void OtherCase(Row);
    }
    
    public class ColorRow implements RowOperation {
        private static final ColorCells OP = new ColorCells();
    
        private ColorCells(){}
    
        // This operation isn't stateful so we use a singleton :D
        public static RowOperation getInstance() {
            return this.OP
        }
    
        public void OldCase(row) {
            row.BackColor = Color.Red;
        }
    
        public void NewCase(row) {
            row.BackColor = Color.Blue;
        }
    
        public void OtherCase(row) {
            row.BackColor = Color.White;
        }
    }
    
    public class CountRow implements CellOperation {
        public int oldRows = 0;
        public int newRows = 0;
        public int otherRows= 0;
    
        // This operation is stateful so we use the contructor
        public CountRow() {}
    
        public void OldCase(Row row) {
            oldRows++;
        }
    
        public void NewCase(Row row) {
            newRows++;
        }
    
        public void OtherCase(Row row) {
            otherRows++;
        }
    }
    
    // For each row in the grid we will call each operation
    // function with the row status and the row
    void UpdateRows(Grid grid, RowOperation[] operations)
    {
        foreach(Row row in grid.Rows)
        {
            string status = row.Cells["Status"]
    
            foreach(RowOperation op in operations)
            {
                op.apply(status, row)
            }
        }
    }
    
    RowOperations[] ops = {
        ColorRow.getInstance(),
        new CountRows()    
    };
    
    UpdateRows(dataGrid1, ops);