【问题标题】:Looking way to refactor these two methods into single method寻找将这两种方法重构为单一方法的方法
【发布时间】:2020-09-25 05:22:17
【问题描述】:

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

现在我正在寻找以通用方式制作单一方法,以便在不同的地方我可以通过将参数传递给它来使用单一方法

方法一:

    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 核心

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

提前多谢

【问题讨论】:

  • 坦率地说,一个方法应该做一件事,而且只做一件事——你实际上可以考虑为这两个正在做的每个部分创建多个方法。
  • 这两种方法的区别在于designHubProject.Infiltration, designHubProject.ExhaustEquipment之类的dataObject、传递给这些方法的表配置和追加到行的数据
  • 制作一个泛型方法
  • @EnigmaState 我只是在猜测,但您要求的东西乍一看似乎违背了大多数编码实践。下面的答案更像是一个好的重构 - 提取方法,以便每个方法都做一件事,然后以某种方式将它们组合起来以实现你所追求的。

标签: c# .net generics refactoring c#-6.0


【解决方案1】:

在我们创建一个处理这两种类型的单一函数之前,我们应该清理代码,以便更容易地查看两个几乎相同的部分(如果有的话)是不同的方法。即使我们只有一个功能,也有很多需要清理的地方。

简而言之,你的函数太长了,在一个地方有太多的代码,实际上总共有太多的代码。 在下文中,原始代码已被分解为具有特定用途的多个函数,并进行了重构以删除 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&lt;T&gt; 类型来重构代码以减少嵌套,那么我们可以删除约束以及Infiltration 和ExhaustEquipment 实现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()
    }
);

【讨论】:

  • 感谢您的输入,不幸的是这两个对象没有实现接口,我现在无法更改结构。我正在寻找单一方法提取以及我们可以将 lambda 函数和其他参数传递给它的地方,以便我们可以对两种类型使用单一方法
  • @EnigmaState,我在底部添加了一个不需要实现接口或声明新成员的替代方案。
  • 感谢修改,对不起,我仍在寻找从上到下的单个函数,我们可以从父方法传递此行渲染
  • 您反对将函数分解为更小的函数?
  • 好吧,接受它,改进它,或者离开它。但是,我简化了您的代码,减少了变量的数量,最小化了可变状态,并删除了不需要的代码。如果您发现内联的所有内容更具可读性,您可以自己轻松地做到这一点,只需剪切和粘贴,但长函数更难阅读。我相信还有进一步改进的空间。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多