分解方法[通俗易懂]

分解方法[通俗易懂]分解方法概述“分解方法”的思想和前面讲到的“提取方法”、“提取方法对象”基本一致。它是将较大个体的方法不断的拆分,让每个“方法”做单一的事情,从而提高每个方法的可读性和可维护性。分解方法可以看做是

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

分解方法

 

概述

“分解方法”的思想和前面讲到的“提取方法”、“提取方法对象”基本一致。
它是将较大个体的方法不断的拆分,让每个“方法”做单一的事情,从而提高每个方法的可读性和可维护性。
分解方法可以看做是“提取方法”的递归版本,它是对方法反复提炼的一种重构策略。

分解方法

下图表示了这个重构策略,第1次提炼和第2次提炼都采用了“提取方法”这个策略。

image

何时分解方法?

“分解方法”最终可以让方法的可读性极大地增强,通常我们可以依据以下几点来辨别方法是否需要分解:

1. 每个方法应该只做一件事情(对事情的理解程度,决定了事情的粒度)

2. 方法应该尽量短小,方法最好不要超过20行(依不同情况,酌情考虑行数) 

3. 方法的缩进层次不宜太多,最好不要超过两级 

4. 方法需要太多的注释才能理解

示例

场景说明

假设在企业制作年度预算的场景中,用户需要按照如下Excel模板填写科目、部门、各月的预算数据,然后将Excel文件导入到“预算系统”。

image

为了表示用户填写的每一行预算数据,开发人员在系统中设计了两个class:BudgetItem(预算项)和BudgetItemDetail(预算项明细)。
上图红色方框标注的表示一个BudgetItem对象,每个蓝色方框则对应一个BudgetItemDetail对象。

image

BudgetItem.cs和BudgetItemDetail.cs
/// <summary> /// 预算项 /// </summary> public class BudgetItem { public string Dept { get; set; } public string Account { get; set; } public IList<BudgetItemDetail> BudgetItemDetails { get; set; } } /// <summary> /// 预算项明细 /// </summary> public class BudgetItemDetail { public string Month { get; set; } public decimal Amount { get; set; } }

重构前

在表示这段逻辑时,我们编写了一个BudgetItemImport类,用于读取Excel并返回IList<BudgetItem>集合

public class BudgetItemImport { private Regex _monthRegex = new Regex(@"\d{4}\\\d{2}"); public IList<BudgetItem> GetBudgetItems(string path) { // 读取Excel获取DataTable DataTable table = ExcelUtil.RenderFromExcel(path); // 获取表示月份的列名 IList<string> monthColumns = new List<string>(); for (var i = 0; i < table.Columns.Count; i++) { var columnName = table.Columns[i].ColumnName; if (_monthRegex.IsMatch(columnName)) { monthColumns.Add(columnName); } } // 遍历DataRow获取BudgetItems IList<BudgetItem> budgetItems = new List<BudgetItem>(); for (var i = 1; i < table.Rows.Count; i++) { // 获取DataRow DataRow dataRow = table.Rows[i]; // 创建BudgetItem对象,并设置部门和科目信息 BudgetItem budgetItem = new BudgetItem { Dept = dataRow[0].ToString(), Account = dataRow[1].ToString() }; // 创建BudgetItemDetail集合 IList<BudgetItemDetail> budgetItemDetails = new List<BudgetItemDetail>(); foreach (var column in monthColumns) { // 创建BudgetItemDetail对象,并设置预算月份和相应金额 BudgetItemDetail detail = new BudgetItemDetail { Month = column, Amount = Convert.ToDecimal(dataRow[column]) }; budgetItemDetails.Add(detail); } budgetItem.BudgetItemDetails = budgetItemDetails; budgetItems.Add(budgetItem); } return budgetItems; } } 

以上这段代码,如果没有这些注释,GetBudgetItems()方法是比较难以读懂的。
接下来,我们采用“分解方法”这个策略来对它重构。

第一次重构

我们粗略分析一下,可以得知GetBudgetItems()方法一共做了3件事情,下图阐述了它的逻辑。

image
秉承着“一个方法只做一件事情”的原则,我们将这3件事情拆分出来,使其变成3个方法。

public class BudgetItemImport { private Regex _monthRegex = new Regex(@"\d{4}\\\d{2}"); public IList<BudgetItem> GetBudgetItems(string path) { // 读取Excel获取DataTable DataTable table = ExcelUtil.RenderFromExcel(path); // 获取表示月份的列名 IList<string> monthColumns = GetMonthColumns(table.Columns); // 读取DataTable获取BudgetItem集合 return GetBudgetItemsFromDataTable(table, monthColumns); } // 获取表示月份的列名 private IList<string> GetMonthColumns(DataColumnCollection collection) { IList<string> monthColumns = new List<string>(); for (var i = 0; i < collection.Count; i++) { var columnName = collection[i].ColumnName; if (_monthRegex.IsMatch(columnName)) { monthColumns.Add(columnName); } } return monthColumns; } // 读取DataTable获取BudgetItem集合 private IList<BudgetItem> GetBudgetItemsFromDataTable(DataTable table, IList<string> monthColumns) { // 遍历DataRow获取BudgetItems IList<BudgetItem> budgetItems = new List<BudgetItem>(); for (var i = 1; i < table.Rows.Count; i++) { DataRow dataRow = table.Rows[i]; // 创建BudgetItem对象,并设置部门和科目信息 BudgetItem budgetItem = new BudgetItem { Dept = dataRow[0].ToString(), Account = dataRow[1].ToString() }; // 创建BudgetItemDetail集合,并设置每个BudgetItemDetail对象的月份和金额 IList<BudgetItemDetail> budgetItemDetails = monthColumns.Select(column => new BudgetItemDetail { Month = column, Amount = Convert.ToDecimal(dataRow[column]) }).ToList(); budgetItem.BudgetItemDetails = budgetItemDetails; budgetItems.Add(budgetItem); } return budgetItems; } } 

第二次重构

虽然GetBudgetItems()拆分成了3个方法,但新追加的GetBudgetItemsFromDataTable()方法还是不具备良好的可读性,这个方法我们仍然需要借助注释才能读懂。
我们再具体分析这个方法内部的逻辑,GetBudgetItemsFromDataTable()这个方法也做了3件事情,见下图:

image

按照这个更加明细的逻辑流程,我们将这3件事情再拆分出来。

public class BudgetItemImport { private Regex _monthRegex = new Regex(@"\d{4}\\\d{2}"); public IList<BudgetItem> GetBudgetItems(string path) { // 读取Excel获取DataTable DataTable table = ExcelUtil.RenderFromExcel(path); // 获取表示月份的列名 IList<string> monthColumns = GetMonthColumns(table.Columns); // 读取DataTable获取BudgetItem集合 return GetBudgetItemsFromDataTable(table, monthColumns); } // 获取表示月份的列名 private IList<string> GetMonthColumns(DataColumnCollection collection) { IList<string> monthColumns = new List<string>(); for (var i = 0; i < collection.Count; i++) { var columnName = collection[i].ColumnName; if (_monthRegex.IsMatch(columnName)) { monthColumns.Add(columnName); } } return monthColumns; } // 读取DataTable获取BudgetItem集合 private IList<BudgetItem> GetBudgetItemsFromDataTable(DataTable table, IList<string> monthColumns) { IList<BudgetItem> budgetItems = new List<BudgetItem>(); foreach (DataRow dataRow in table.Rows) { BudgetItem budgetItem = GetBudgetItemFromDataRow(dataRow, monthColumns); budgetItems.Add(budgetItem); } return budgetItems; } // 创建BudgetItem对象,并设置部门和科目信息 private BudgetItem GetBudgetItemFromDataRow(DataRow dataRow, IList<string> monthColumns) { BudgetItem budgetItem = new BudgetItem { Dept = dataRow[0].ToString(), Account = dataRow[1].ToString(), BudgetItemDetails = GetBudgetItemDetailsFromDataRow(dataRow, monthColumns) }; return budgetItem; } // 创建BudgetItemDetail集合,并设置每个BudgetItemDetail对象的月份和金额 private IList<BudgetItemDetail> GetBudgetItemDetailsFromDataRow(DataRow dataRow, IList<string> monthColumns) { return monthColumns.Select(column => new BudgetItemDetail { Month = column, Amount = Convert.ToDecimal(dataRow[column]), }).ToList(); } } 

经过这次重构后,BudgetItemImport类的可读性已经很好了。每个方法都只做一件事情,每个方法都很短小,都不超过20行,我们甚至不需要为这些方法写注释了。

小结

在经历过两次重构后,我们得到了结构良好的代码。回顾这个示例的重构过程,我们可以用下面一副图来表示。

image

写代码和写别的东西很像。在写文章时,你先想什么就写什么,然后再打磨它。初稿也许丑陋无序,你就雕章琢句,直至达到你心目中的样子。
我们并不能直接写出结构和可读性良好的方法,一开始我们的方法写得复杂且冗长,包含了各种循环、判断、缩进和注释。
然后我们打磨这些代码,通过分解方法逐一解决这些问题。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/166450.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • JAVA操作FTP(FTP工具类)

    JAVA操作FTP(FTP工具类)JAVAFTP操作导入commons-net<dependency><groupId>commons-net</groupId><artifactId>commons-net</artifactId><version>3.6</version></dependency>packagecom.my.ftp.test;importjava.io.File;imp

  • 卷积神经网络(CNN)与深度卷积神经网络(DCNN)

    卷积神经网络(CNN)与深度卷积神经网络(DCNN)作为小白,看到DCNN,一直想知道与CNN的区别,也没找到明确的说法,以下是自己的一点想法,欢迎指正!目录一、CNN与DCNN二、基于pytorch的实现1.LeNet-52.AlexNet一、CNN与DCNN卷积神经网络,如:LeNet深度卷积神经网络,如:AlexNetAlexNet与LeNet结构类似,但使用了更多的卷积层和更大的参数空间来拟合大规模数据集ImageNet。卷积神经网络就是含卷积层的网络。AlexNet是浅层神经网络和深度神经网络的分界..

  • java path环境变量_java配置环境变量

    java path环境变量_java配置环境变量前段时间因为windows10更新的缘故,系统越来越卡,任务管理器也闪退,试了各种方法都不管用,反而越改问题越多,乘着周末,昨天把系统重装了,现在记录一下配置java环境变量的过程。1.安装jdk,从官网下就行,我的是把原来的做了个备份,直接解压的。如下图2.记录下你要配置的jdk路径。右键此电脑,属性,点击高级系统设置,选择环境变量。3.现在就可以新建环境变量了。点击新建,变量名为JAVA_HO…

  • quartus ii 12.0安装教程_系统安装教程

    quartus ii 12.0安装教程_系统安装教程安装前先关闭杀毒软件和360卫士,注意安装路径不能有中文,安装包路径也不要有中文。1.鼠标右击【QuartusII12.0】压缩包选择【解压到QuartusII12.0】。2.双击打开解压后的【QuartusII12.0】文件夹。3.双击打开【Quartus】文件夹。4.鼠标右击【12.0_178_quartus_windows.exe】选择【以管理员身份运行】。5.点击【Install】。6.解压中。7.勾选【AllowA…

  • 阿里矢量图标_ui矢量图

    阿里矢量图标_ui矢量图阿里iconfunt官网对于图标的调用写的不够详细,许多初用者不会用,下面具体介绍下总结的两种方法:一、在线调用方式1、首先建立新浪微博账号,用微博号登录iconfunt官网;2、所需要图标加入

  • vue子组件传值给父组件_子组件调用父组件中的方法

    vue子组件传值给父组件_子组件调用父组件中的方法参考视频:https://www.bilibili.com/video/av32790541/?spm_id_from=trigger_reload原理:在父组件引用子组件时,通过事件绑定机制把一个方法aaaa的引用传给子组件,这个方法中可以有各种参数,子组件在触发自己的函数或者某些数据发生变化时,触发:事件绑定机制绑定的函数,通过参数的方式将要传的值传过来,父组件中处理,也就接到了子…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号