网络知识 娱乐 你不知道的方法对象-《代码不朽》

你不知道的方法对象-《代码不朽》

大家好,我是码农老吴,欢迎收看架构师书房。今天,我继续给大家解读《代码不朽》这本书。

上期,我们介绍了本书的第四个原则,保持代码单元的接口简单(Keep Unit Interfaces Small),或者直接一点说,方法的参数,最好不要超过4个。并且介绍了参数优化的一个方法,引入参数对象(Introduce Parameter Object)。今天我们看另外一个简化参数的重构技巧,使用方法对象替换方法(Replace Method with Method Object )

你不知道的方法对象-《代码不朽》

顾名思义,就是把原来的方法,替换为一个封装方法的对象。这种思路,是面向对象编程思想的一种突破。当一个方法的入参,提取为参数对象,没有意义,对简化方法入参没有帮助时,可以采用 “方法对象替换方法”(Replace Method with Method Object )的重构技巧。

我后面再展开聊,先看案例。

重构之前

Charts类:图表类

package eu.sig.training.ch05.chartlib.v1;nnimport java.awt.Graphics;nimport java.awt.Point;nnimport eu.sig.training.ch05.boardpanel.v2.Rectangle;nnpublic class Charts {n @SuppressWarnings("unused")n // tag::drawBarChart[]n public static void drawBarChart(Graphics g,n CategoryItemRendererState state,n Rectangle graphArea,n CategoryPlot plot,n CategoryAxis domainAxis,n ValueAxis rangeAxis,n CategoryDataset dataset) {n // ..n }n // end::drawBarChart[]nn // tag::drawBarChartDefault[]n public static void drawBarChart(Graphics g, CategoryDataset dataset) {n Charts.drawBarChart(g,n CategoryItemRendererState.DEFAULT,n new Rectangle(new Point(0, 0), 100, 100),n CategoryPlot.DEFAULT,n CategoryAxis.DEFAULT,n ValueAxis.DEFAULT,n dataset);n }n // end::drawBarChartDefault[]n}nnclass CategoryItemRendererState {n public static final CategoryItemRendererState DEFAULT = null;n}nnclass CategoryPlot {n public static final CategoryPlot DEFAULT = null;n}nnclass CategoryAxis {n public static final CategoryAxis DEFAULT = null;n}nnclass ValueAxis {n public static final ValueAxis DEFAULT = null;n}nnclass CategoryDataset {n public static final CategoryDataset DEFAULT = null;n}

头脑风暴

上面代码中的Charts类的drawBarChart()方法,是用来绘制图标的,比如柱状图,饼图等,它有7个参数,而且每个入参,基本上都是一个复杂的对象,绘制不同的图表,需要的参数都不一样。为了让这个方法的调用简单,可以考虑给每个参数,提供一个默认值。这次,我们使用“方法对象替换方法”(Replace Method with Method Object )的重构技巧来进行重构。这种技巧在Idea里面怎么操作,我们前面已经分享过,这里不再赘述。

重构之后

BarChart:

package eu.sig.training.ch05.chartlib.v2;nnimport java.awt.Graphics;nimport java.awt.Point;nnimport eu.sig.training.ch05.boardpanel.v2.Rectangle;nn@SuppressWarnings("unused")n// tag::BarChart[]npublic class BarChart {n private CategoryItemRendererState state = CategoryItemRendererState.DEFAULT;n private Rectangle graphArea = new Rectangle(new Point(0, 0), 100, 100);n private CategoryPlot plot = CategoryPlot.DEFAULT;n private CategoryAxis domainAxis = CategoryAxis.DEFAULT;n private ValueAxis rangeAxis = ValueAxis.DEFAULT;n private CategoryDataset dataset = CategoryDataset.DEFAULT;nn public BarChart draw(Graphics g) {n // ..n return this;n }nn public ValueAxis getRangeAxis() {n return rangeAxis;n }nn public BarChart setRangeAxis(ValueAxis rangeAxis) {n this.rangeAxis = rangeAxis;n return this;n }nn // More getters and setters.nn // end::BarChart[]nn public CategoryItemRendererState getState() {n return state;n }nn public BarChart setState(CategoryItemRendererState state) {n this.state = state;n return this;n }nn public Rectangle getGraphArea() {n return graphArea;n }nn public BarChart setGraphArea(Rectangle graphArea) {n this.graphArea = graphArea;n return this;n }nn public CategoryPlot getPlot() {n return plot;n }nn public BarChart setPlot(CategoryPlot plot) {n this.plot = plot;n return this;n }nn public CategoryAxis getDomainAxis() {n return domainAxis;n }nn public BarChart setDomainAxis(CategoryAxis domainAxis) {n this.domainAxis = domainAxis;n return this;n }nn public CategoryDataset getDataset() {n return dataset;n }nn public BarChart setDataset(CategoryDataset dataset) {n this.dataset = dataset;n return this;n }nn}nnclass CategoryItemRendererState {n public static final CategoryItemRendererState DEFAULT = null;n}nnclass CategoryPlot {n public static final CategoryPlot DEFAULT = null;n}nnclass CategoryAxis {n public static final CategoryAxis DEFAULT = null;n}nnclass ValueAxis {n public static final ValueAxis DEFAULT = null;n}nnclass CategoryDataset {n public static final CategoryDataset DEFAULT = null;n}nn@SuppressWarnings("serial")nclass BarChartTest extends java.awt.Frame {n ValueAxis myValueAxis = null;n CategoryDataset myDataset = null;n @SuppressWarnings("unused")n // tag::showMyBarChart[]n private void showMyBarChart() {n Graphics g = this.getGraphics();n BarChart b = new BarChart()n .setRangeAxis(myValueAxis)n .setDataset(myDataset)n .draw(g);n }n // end::showMyBarChart[]n}

点评

上面的代码,是把重构前Charts类的drawBarChart()方法,提取成了BarChart类,应该是画柱状图的类。原来的7个参数,除了第一个参数Graphics g,其余6个参数,都转变成了这个类的属性,并且每个属性,都提供了默认值。这样当用户调用draw()方法时,只需要传入唯一的一个参数Graphics g,其他6个参数,根据需要,调用对应的set方法进行初始化即可。

可以看出,“方法对象替换方法”(Replace Method with Method Object )的重构技巧,不仅可以简化方法的输入参数,还有一个很重要的作用,就是解决参数默认值的初始化问题。确实是一个非常实用的重构技巧。

但是,这个重构技巧,也有一个弊端,我在前面的分享里面讲过,这里再强调一下。

提取方法对象,是一种概念上的突破,一般我们的面向对象编程开发,都是找出项目中的一部分名词,抽象为类,和类自身有关的名词,抽象为类的属性,和类自身有关的动词,抽象为类的方法。

而提取方法对象,是在一个方法代码膨胀之后,将该方法抽象为一个类(要注意这个类的名字,最好不要直接使用动词),里面的部分局部变量,或者入参,可以抽象为属性,这样可以精简方法的参数个数。另外,这些属性还可以用默认值直接初始化,解决参数的默认值初始化问题。

本期我们就聊到这里,本书的第四个原则,保持代码单元的接口简单(Keep Unit Interfaces Small)我们就聊完了,下期,我们开始第五个原则,分离模块之间的关注点(Separate Concerns in Modules)。

极客架构师,专注架构师成长,我们下期见。