寻找将这两种方法重构为单一方法的方法

Looking way to refactor these two methods into single method

大家好我正在尝试生成包含两个不同 table 的 word 文档,为此我有两种类似的方法,我正在传递 word 文档引用和数据对象以及 table 到类似的方法..

现在我希望以通用方式制作单一方法,以便在不同的地方我可以通过向其传递参数来使用单一方法

方法一:

    private static List<OpenXmlElement> RenderExhaustEquipmentTableDataAndNotes(MainDocumentPart mainDocumentPart, List<ProjectObject<ExhaustEquipment>> exhaustEquipment,Table table)
    {
        HtmlConverter noteConverter = new HtmlConverter(mainDocumentPart);
        var equipmentExhaustTypes = new Dictionary<string, List<ProjectObject<ExhaustEquipment>>>();

        foreach (var item in exhaustEquipment)
        {
            string exhaustEquipmentName = item.TargetObject.Name;
            if (!equipmentExhaustTypes.ContainsKey(exhaustEquipmentName))
            {
                equipmentExhaustTypes.Add(exhaustEquipmentName, new List<ProjectObject<ExhaustEquipment>>());
            }
            equipmentExhaustTypes[exhaustEquipmentName].Add(item);
        }

        List<OpenXmlElement> notes = new List<OpenXmlElement>();
        int noteIndex = 1;
        foreach (var exhaustEquipmentItem in equipmentExhaustTypes)
        {
            List<string> noteIndices = new List<string>();
            for (int exhaustEquipmentConditionIndex = 0; exhaustEquipmentConditionIndex < exhaustEquipmentItem.Value.Count; exhaustEquipmentConditionIndex++)
            {
                var condition = exhaustEquipmentItem.Value[exhaustEquipmentConditionIndex];
                var row = new TableRow();
                Run superscriptRun = new Run(new RunProperties(new VerticalTextAlignment { Val = VerticalPositionValues.Superscript }));

                if (exhaustEquipmentConditionIndex == 0)
                {              
                    row.Append(RenderOpenXmlElementContentCell(new Paragraph(
                        new List<Run> {
                            new Run(new RunProperties(), new Text(exhaustEquipmentItem.Key) { Space = SpaceProcessingModeValues.Preserve }),
                            superscriptRun
                        }), 1,
                        new OpenXmlElement[] {new VerticalMerge { Val = MergedCellValues.Restart },new TableCellMargin {
                                LeftMargin = new LeftMargin { Width = "120" },
                                TopMargin = new TopMargin { Width = "80" } }
                        }));
                }
                else
                {
                    row.Append(RenderTextContentCell(null, 1, null, null, new OpenXmlElement[] { new VerticalMerge { Val = MergedCellValues.Continue } }));
                }
                row.Append(RenderTextContentCell(condition.TargetObject.IsConstantVolume ? "Yes" : "No"));
                row.Append(RenderTextContentCell($"{condition.TargetObject.MinAirflow:R2}"));
                row.Append(RenderTextContentCell($"{condition.TargetObject.MaxAirflow:R2}"));

                if (condition.TargetObject.NotesHTML?.Count > 0)
                {
                    foreach (var note in condition.TargetObject.NotesHTML)
                    {
                        var compositeElements = noteConverter.Parse(note);
                        var htmlRuns = compositeElements.First().ChildElements.Where(c => c is Run).Cast<Run>().Select(n => n.CloneNode(true));
                        notes.Add(new Run(htmlRuns));
                        noteIndices.Add(noteIndex++.ToString(CultureInfo.InvariantCulture));
                    }
                }                   
                if (exhaustEquipmentConditionIndex == exhaustEquipmentItem.Value.Count - 1 && condition.TargetObject.NotesHTML?.Count > 0)
                {
                    superscriptRun.Append(new Text($"({String.Join(',', noteIndices)})") { Space = SpaceProcessingModeValues.Preserve });
                }
                table.Append(row);
            }
        }
        List<OpenXmlElement> notesSection = new List<OpenXmlElement>();
        List<OpenXmlElement> result = RenderNotesArray(table, notes, notesSection);
        return result;
    }

我在下面这样调用这个方法

 var table = new Table(RenderTableProperties());
 table.Append(new TableRow(
                    RenderTableHeaderCell("Name"),
                    RenderTableHeaderCell("Constant Volume"),
                    RenderTableHeaderCell("Minimum Airflow", units: "(cfm)"),
                    RenderTableHeaderCell("Wet Bulb Temperature", units: "(cfm)")
                    ));
body.Append(RenderExhaustEquipmentTableDataAndNotes(mainDocumentPart, designHubProject.ExhaustEquipment, table));

方法二:

    private static List<OpenXmlElement> RenderInfiltrationTableData(MainDocumentPart mainDocumentPart, List<ProjectObject<Infiltration>> infiltration,Table table)
    {
        HtmlConverter noteConverter = new HtmlConverter(mainDocumentPart);
        var nameByInflitrationObject = new Dictionary<string, List<ProjectObject<Infiltration>>>();

        foreach (var infiltrationData in infiltration)
        {
            string infiltrationName = infiltrationData.TargetObject.Name;
            if (!nameByInflitrationObject.ContainsKey(infiltrationName))
            {
                nameByInflitrationObject.Add(infiltrationName, new List<ProjectObject<Infiltration>>());
            }
            nameByInflitrationObject[infiltrationName].Add(infiltrationData);
        }

        List<OpenXmlElement> notes = new List<OpenXmlElement>();
        int noteIndex = 1;

        foreach (var inflitrationDataItem in nameByInflitrationObject)
        {
            List<string> noteIndices = new List<string>();
            for (int inflitrationNameIndex = 0; inflitrationNameIndex < inflitrationDataItem.Value.Count; inflitrationNameIndex++)
            {
                var dataItem = inflitrationDataItem.Value[inflitrationNameIndex];
                var row = new TableRow();
                Run superscriptRun = new Run(new RunProperties(new VerticalTextAlignment { Val = VerticalPositionValues.Superscript }));

                if (inflitrationNameIndex == 0)
                {
                    row.Append(RenderOpenXmlElementContentCell(new Paragraph(
                        new List<Run> {
                            new Run(new RunProperties(), new Text(inflitrationDataItem.Key) { Space = SpaceProcessingModeValues.Preserve }),superscriptRun
                        }), 1,
                        new OpenXmlElement[] {new VerticalMerge { Val = MergedCellValues.Restart },new TableCellMargin {
                                LeftMargin = new LeftMargin { Width = "120" },
                                TopMargin = new TopMargin { Width = "80" }}
                        }));
                }
                else
                {
                    row.Append(RenderTextContentCell(null, 1, null, null, new OpenXmlElement[] { new VerticalMerge { Val = MergedCellValues.Continue } }));
                }
                row.Append(RenderTextContentCell($"{dataItem.TargetObject.AirflowScalar.ToString("R2", CultureInfo.CurrentCulture)} cfm {EnumUtils.StringValueOfEnum(dataItem.TargetObject.InfiltrationCalculationType).ToLower(CultureInfo.CurrentCulture)}"));

                if (dataItem.TargetObject.NotesHTML?.Count > 0)
                {
                    foreach (var note in dataItem.TargetObject.NotesHTML)
                    {
                        var compositeElements = noteConverter.Parse(note);
                        var htmlRuns = compositeElements.First().ChildElements.Where(c => c is Run).Cast<Run>().Select(n => n.CloneNode(true));
                        notes.Add(new Run(htmlRuns));
                        noteIndices.Add(noteIndex++.ToString(CultureInfo.InvariantCulture));
                    }
                }

                if (inflitrationNameIndex == inflitrationDataItem.Value.Count - 1 && dataItem.TargetObject.NotesHTML?.Count > 0)
                {
                    superscriptRun.Append(new Text($"({String.Join(',', noteIndices)})") { Space = SpaceProcessingModeValues.Preserve });
                }
                table.Append(row);
            }
        }
        List<OpenXmlElement> notesSection = new List<OpenXmlElement>();
        List<OpenXmlElement> result = RenderNotesArray(table, notes, notesSection);
        return result;
    }

然后我在这里调用这个方法,如下所示

   var table = new Table(RenderTableProperties());
   table.Append(new TableRow(
                    RenderTableHeaderCell("Type"),
                    RenderTableHeaderCell("Air Flow")
                    ));
   body.Append(RenderInfiltrationTableData(mainDocumentPart, designHubProject.Infiltration, table));

我知道有很多行,但是有没有通用的方法可以使用这两种类似方法中的单一方法,我正在使用 .net core

任何人都可以提出任何想法或建议我如何将这两种方法重构为单一方法,将不胜感激。

非常感谢

在我们可以创建一个处理这两种类型的单一函数,实现消除无故重复的高度值得称赞的目标之前,我们应该清理代码以便更容易地看到两者之间的哪些部分(如果有的话)不同几乎相同的方法。还有很多东西需要清理,即使我们只有一个功能。

简而言之,您的函数太长,一个地方的代码太多,实际上代码太多。 在下文中,原始代码已被分解为多个具有特定用途的函数,并进行了重构以删除 DIY 废话,支持标准库函数并删除无意义的代码。

static IEnumerable<OpenXmlElement> RenderExhaustEquipmentTableDataAndNotes(MainDocumentPart mainDocumentPart, List<ProjectObject<ExhaustEquipment>> exhaustEquipment, Table table)
{
    var equipmentByType = exhaustEquipment.ToLookup(item => item.TargetObject.Name);

    List<OpenXmlElement> notes = new List<OpenXmlElement>();

    foreach (var items in equipmentByType)
    {
        Run superscriptRun = CreateSuperScriptRun();

        foreach (var item in items)
        {
            var row = new TableRow();

            if (item == items.First())
            {
                row.Append(CreateFirstRowStartingCell(items.Key, superscriptRun));
            }
            else
            {
                row.Append(RenderTextContentCell(null, 1, null, null, new[] {
                    new VerticalMerge { Val = MergedCellValues.Continue }
                }));
            }
            row.Append(RenderTextContentCell(item.TargetObject.IsConstantVolume ? "Yes" : "No"));
            row.Append(RenderTextContentCell($"{item.TargetObject.MinAirflow:R2}"));
            row.Append(RenderTextContentCell($"{item.TargetObject.MaxAirflow:R2}"));

            table.Append(row);

            var itemNotes = ParseNotes(mainDocumentPart, item.TargetObject.NotesHTML);

            if (item == items.Last() && itemNotes.Any())
            {
                UpdateSuperScript(superscriptRun, itemNotes);
            }

            notes.AddRange(itemNotes);
        }
    }
    List<OpenXmlElement> result = RenderNotesArray(table, notes, new List<OpenXmlElement>());
    return result;
}

private static Run CreateSuperScriptRun()
{
    return new Run(new RunProperties(new VerticalTextAlignment
    {
        Val = VerticalPositionValues.Superscript
    }));
}

private static void UpdateSuperScript(Run superscriptRun, IEnumerable<OpenXmlElement> notes)
{
    superscriptRun.Append(new Text($"({string.Join(",", Enumerable.Range(0, notes.Count()))})")
    {
        Space = SpaceProcessingModeValues.Preserve
    });
}

private static IEnumerable<OpenXmlElement> ParseNotes(MainDocumentPart mainDocumentPart, IEnumerable<OpenXmlElement> notes)
{
    return notes == null 
        ? Enumerable.Empty<OpenXmlElement>()
        : notes.Select(note => new HtmlConverter(mainDocumentPart).Parse(note))
               .Select(note => note.First().ChildElements
               .OfType<Run>()
               .Select(n => n.CloneNode(true))).Select(htmlRuns => new Run(htmlRuns))
               .ToList();
}


private OpenXmlElement CreateFirstRowStartingCell(string key, Run superscriptRun)
{
    return RenderOpenXmlElementContentCell(
        new Paragraph(new List<Run> {
        new Run(new RunProperties(), new Text(key) { Space = SpaceProcessingModeValues.Preserve }),
            superscriptRun
        }),
        1,
        new OpenXmlElement[] {
            new VerticalMerge { Val = MergedCellValues.Restart },
            new TableCellMargin { LeftMargin = new LeftMargin { Width = "120" }, TopMargin = new TopMargin { Width = "80" } }
        });
}

现在,让我们来处理第二个函数:

static IEnunumerable<OpenXmlElement> RenderInfiltrationTableData(MainDocumentPart mainDocumentPart, IEnunumerable<ProjectObject<Infiltration>> infiltration, Table table)
{
    var infiltrationsByType = infiltration.ToLookup(item => item.TargetObject.Name);

    List<OpenXmlElement> notes = new List<OpenXmlElement>();

    foreach (var inflitrations in infiltrationsByType)
    {
        Run superscriptRun = CreateSuperScriptRun();

        foreach (var item in inflitrations)
        {
            var row = new TableRow();

            if (item == inflitrations.First())
            {
                row.Append(CreateFirstRowStartingCell(inflitrations.Key, superscriptRun));
            }
            else
            {
                row.Append(RenderTextContentCell(null, 1, null, null, new[] {
                    new VerticalMerge { Val = MergedCellValues.Continue }
                }));
            }
            row.Append(RenderTextContentCell($"{item.TargetObject.AirflowScalar:R2} cfm {item.TargetObject.InfiltrationCalculationType}").ToLower());

            table.Append(row);

            var itemNotes = ParseNotes(mainDocumentPart, item.TargetObject.NotesHTML);

            if (item == inflitrations.Last() && itemNotes.Any())
            {
                UpdateSuperScript(superscriptRun, itemNotes);
            }

            notes.AddRange(itemNotes);
        }
    }
    IEnumerable<OpenXmlElement> result = RenderNotesArray(table, notes, new List<OpenXmlElement>());
    return result;
}

正如我们所见,只需将代码提取到简单的辅助函数中,即可大大减少重复。

这也使得更容易看出两个函数之间的差异。

这只是

的问题
row.Append(RenderTextContentCell(item.TargetObject.IsConstantVolume ? "Yes" : "No"));
row.Append(RenderTextContentCell($"{item.TargetObject.MinAirflow:R2}"));
row.Append(RenderTextContentCell($"{item.TargetObject.MaxAirflow:R2}"));

对比

row.Append(RenderTextContentCell($"{item.TargetObject.AirflowScalar:R2} cfm {item.TargetObject.InfiltrationCalculationType}").ToLower());

为了实现您希望的单个函数目标,我们可以创建一个通用函数,并要求调用者传入一个函数来处理这些差异。

static IEnumerable<OpenXmlElement> RenderTableDataAndNotes<T>(
    MainDocumentPart mainDocumentPart,
    IEnumerable<ProjectObject<T>> projects,
    Table table,
    Func<ProjectObject<T>, IEnumerable<OpenXmlElement>> createCells
) where T : ITargetObject
{
    var projectsByType = projects.ToLookup(item => item.TargetObject.Name);
    List<OpenXmlElement> notes = new List<OpenXmlElement>();

    foreach (var items in projectsByType)
    {
        Run superscriptRun = CreateSuperScriptRun();

        foreach (var item in items)
        {
            var row = new TableRow();

            if (item == items.First())
            {
                row.Append(CreateFirstRowStartingCell(items.Key, superscriptRun));
            }
            else
            {
                row.Append(RenderTextContentCell(null, 1, null, null, new[] {
                    new VerticalMerge { Val = MergedCellValues.Continue }
                }));
            }
            var itemCells = createCells(item);

            foreach (var cell in itemCells)
            {
                row.Append(cell);
            }

            table.Append(row);

            var itemNotes = ParseNotes(mainDocumentPart, item.TargetObject.NotesHTML);

            if (item == items.Last() && itemNotes.Any())
            {
                UpdateSuperScript(superscriptRun, itemNotes);
            }

            notes.AddRange(itemNotes);
        }
    }
    IEnumerable<OpenXmlElement> result = RenderNotesArray(table, notes, new List<OpenXmlElement>());
    return result;
}

现在,当我们称呼它为一些排气设备时,我们这样做如下:

 var rendered = RenderTableDataAndNotes(mainDocumentPart, exhaustProjects, table,
     exhaust => new[] {
         RenderTextContentCell(exhaust.TargetObject.IsConstantVolume ? "Yes" : "No"),
         RenderTextContentCell($"{exhaust.TargetObject.MinAirflow:R2}"),
         RenderTextContentCell($"{exhaust.TargetObject.MaxAirflow:R2}"),
  });

对于渗透项目,我们会做如下:

var rendered = RenderTableDataAndNotes(
    mainDocumentPart,
    infiltrationProjects,
    table,
    infiltration => new[] {
        RenderTextContentCell($"{item.TargetObject.AirflowScalar:R2} cfm {item.TargetObject.InfiltrationCalculationType}")
     .ToLower()
});

即使是现在,代码仍有很大的改进空间。目前,它要求各种项目类型实现一个通用的 ITargetObject 接口,声明用于按类型对项目进行分组的 Name 属性 接口。如果您通过将 Name 提升为 ProjectObject<T> 类型来重构代码以减少嵌套,那么我们可以删除约束以及 InfiltrationExhaustEquipment 实现 ITargetObject界面。

请注意,如果您无法更改类型,可以通过几种方式调整代码。

例如,您可以删除 T 上的类型约束并在外部构建查找并将其传递给函数:

static IEnumerable<OpenXmlElement> RenderTableDataAndNotes<T>(
    MainDocumentPart mainDocumentPart,
    ILookup<string, ProjectObject<T>> projectsByType,
    Table table,
    Func<ProjectObject<T>, IEnumerable<OpenXmlElement>> createCells
)

然后你会称它为

var infiltrationProjectsByType = infiltrationProjects.ToLookup(project => project.Name);

var rendered = RenderTableDataAndNotes(
    mainDocumentPart,
    infiltrationProjectsByType,
    table,
    infiltration => new[] {
        RenderTextContentCell($"{infiltration.TargetObject.AirflowScalar:R2} cfm {infiltration.TargetObject.InfiltrationCalculationType}").ToLower()
    }
);