第十章 类
10.1 类的组织
从上往下,从一组变量列表开始:公共静态常量、私有静态变量,私有实体变量
封装
我们喜欢保持变量和工具函数的私有性,有时需要用到受保护变量或工具函数,好让测试可以访问到
我们首先会想办法使之保有隐私,放松封装总是下策
10.2 类应该短小
70个方法的类确实有些太长了,5个方法的类虽然方法数少,但是拥有太多的权责
类的名称应当描述其权责
10.2.1 单一权责原则
类或者模块应该有且只有一条加以改变的理由
鉴别权责(修改的理由)常常帮助我们再代码中认识到并创建出更好的抽象
而这个创建出的类极有可能在其他地方回得到复用
系统应该由许多短小的类而不是少量巨大的类组成,每个小类封装一个权责,只有一个修改的原因,并与少数其他类一起协同达成期望的系统行为
10.2.2 内聚
方法操作的变量越多,就越黏聚到类上。如果一个类中的每个变量都被每个方法所使用的,则该类具有最大的内聚性
创建极大化内聚类是既不可取也不可能的
我们希望内聚性保持在较高的位置,内聚性高,意味着类中的方法和变量互相依赖,互相结合成一个逻辑整体
保持函数和参数列表短小的策略,可能会导致一组方法所用的实体变量数量增加。出现这种情况往往以为着至少有一个类要从大类中挣扎出来,我们应该尝试将这些变量和方法拆分到更多的类中,让新的类更为内聚
10.2.3 保持内聚性就会得到许多短小的类
当我们看到一个有许多变量的大函数,我们想把该函数中某一部分拆解成单独的函数,不过,要拆出来的代码使用了函数中声明的4个变量,那是否要把这4个变量都当做参数传递到新函数中去呢?肯定是没必要的!只需要将4个变量提升为类的实体变量,就无需传递任何参数了。但是这也意味着类丧失了内聚性,因为堆积了越来越多只为允许少量函数共享而存在的实体变量。如果有些函数想要共享某些变量,可以让这几个函数拥有一个自己的类!换句话说,就是创建一个类去包含这几个函数。
作者举了一个例子:输出素数
原本全部的代码都写在了PrintPrimes的main函数中,然后经过重构优化,拆分出了PrimePrinter、RowColumnPagePrinter、PrimeGenerator三个类
10.3 为了修改而组织
作者举了一个例子:
public class Sql { public Sql(String table, Column[] columns) public String create() public String insert(Object[] fields) public String selectAll() private String selectWithCriteria(String criteria) private String valuesList(Object[] fields, final Column[] columns) }
当新增一种语句类型时,就要修改Sql类。改动单个语句类型时,也要进行修改。说明Sql违背了单一职责原则。并且还出现了与类一小部分有关的私有方法,比如selectWithCriteria()
改进后:
abstract public class Sql { public Sql(String table, Column[] columns) abstract public String generate(); } public class CreateSql extends Sql { public CreateSql(String table, Column[] columns) @Override public String generate() } public class InsertSql extends Sql { public InsertSql(String table, Column[] columns) @Override public String generate() private String valuesList(Object[] fields, final Column[] columns) } public class SelectSql extends Sql { public SelectSql(String table, Column[] columns) @Override public String generate() } ......
如此一来,每个类中的代码都记为简单,函数对于其它函数造成毁坏的风险也变得几乎近无
类与类之间相互隔离了,当需要增加语句时,现存的类无需修改,只需要继承Sql类即可
重构后的Sql遵守单一职责原则,同时也遵守开闭原则
隔离修改
具体类包含细节,抽象类只呈现概念。依赖于具体的细节,细节改变时,就会有风险,我们可以借助接口和抽象类来隔离这些细节带来的影响
现在我们实现股票计算相关的代码,构建一个Portfolio类来代表投资组合的价值,如果直接依赖于外部TokyoStockExchanged的API,则测试用例就会受到价值查询连带的影响
与其设计直接依赖于TokyoStockExchange的Portfolio类,不如直接创建StockExchange接口,让TokyoStockExchange来实现这个接口:
public interface StockChange { Money currentPrice(String symbol); } public Portfolio { private StockExchange exchange; public Portfolio(StockExchange exchange) { this.exchange = exchange; } }
通过降低连接度,我们就遵循了另一条设计原则,依赖倒置原则,类应该依赖于抽象而不是依赖于细节
总结
这节其实更多的说了一些设计模式相关的东西,所说的所讲的代码都在阐述那几个原则,有时候代码需要多看,就慢慢的会明白其中的含义了。接口设计能力是很重要的一项能力,我们要明白什么时候该去考虑抽象,什么时候该去考虑细节。