企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
### Extract Superclass(提炼超类) 两个classes 有相似特性(similar features)。 为这两个classes 建立一个superclass ,将相同特性移至superclass 。 ![](https://box.kancloud.cn/2016-08-15_57b1b5e71c4c6.gif) **动机(Motivation)** 重复代码是系统中最主要的一种糟糕东西。如果你在不同的地方进行相同一件事 情,一旦需要修改那些动作时,你就得负担比你原本应该负担的更多事情。 重复代码的某种形式就是:两个classes 以相同的方式做类似的事情,或者以不同的方式做类似的事情。对象提供了一种简化这种情况的机制,那就是继承机制。但是,在建立这些具有共通性的classes 之前,你往往无法发现这样的共通性,因此你经常会在「具有共通性」的classes 存在之后,再幵始建立其间的继承结构。 另一种选择就是Extract Class。这两种方案之间的选择其实就是继承(Inheritance )和委托(delegation)之间的选择。如果两个classes 可以共享行为, 也可以共享接口,那么继承是比较简单的作法。如果你选错了,也总有 Replace Inheritance with Delegation 这瓶后悔药可吃。 **作法(Mechanics)** - 为原本的classes 新建一个空白的abstract superclass。 - 运用Pull Up Field, Pull Up Method, 和 Pull Up Constructor Body 逐一将subclass 的共同充素上移到superclass 。 - 先搬移值域,通常比较简单。 - 如果相应的subclass 函数有不同的签名式(signature),但用途相同,可以先使用Rename Method 将它们的签名式改为相同,然后 再使用 Pull Up Method。 - 如果相应的subclass 函数有相同的签名式,但函数本体不同,可以在superclass 中把它们的共同签名式声明为抽象函数。 - 如果相应的subclass 函数有不同的函数本体,但用途相同,可试着使用 Substitute Algorithm 把其中一个函数的函数本体拷贝到另一个函数中。如果运转正常,你就可以使用 Pull Up Method。 - 每次上移后,编译并测试。 - 检查留在subclass 中的函数,看它们是否还有共通成分。如果有,可以使用Extract Method 将共通部分再提炼出来,然后使用 Pull Up Method 将提炼出的函数上移到superclass 。如果各个subclass 中某个函数的整体流程很相似,你也许可以使用Form Template Method。 - 将所有共通元素都上移到superclass 之后,检查subclass 的所有用户。如果它们只使用共同接口,你就可以把它们所索求的对象型别改为superclass 。 **范例:(Example)** 下面例子中,我以Employee 表示「员工」,以Department 表示「部门」: ~~~ class Employee... public Employee (String name, String id, int annualCost) { _name = name; _id = id; _annualCost = annualCost; } public int getAnnualCost() { return _annualCost; } public String getId(){ return _id; } public String getName() { return _name; } private String _name; private int _annualCost; private String _id; public class Department... public Department (String name) { _name = name; } public int getTotalAnnualCost(){ Enumeration e = getStaff(); int result = 0; while (e.hasMoreElements()) { Employee each = (Employee) e.nextElement(); result += each.getAnnualCost(); } return result; } public int getHeadCount() { return _staff.size(); } public Enumeration getStaff() { return _staff.elements(); } public void addStaff(Employee arg) { _staff.addElement(arg); } public String getName() { return _name; } private String _name; private Vector _staff = new Vector(); ~~~ 这里有两处共同点。首先,员工和部门都有名称(names);其次,它们都有年度成本(annual costs),只不过计算方式略有不同。我要提炼出一个superclass ,用以包容这些共通特性。第一步是新建这个superclass ,并将现有的两个classes 定义为其subclasses: ~~~ abstract class Party {} class Employee extends Party... class Department extends Party... ~~~ 然后我开始把特性上移至superclass 。先实施Pull Up Field 通常会比较简单: ~~~ class Party... protected String _name; ~~~ 然后,我可以使用 Pull Up Method 把这个值域的取值函数(getter)也上移至superclass : ~~~ class Party { public String getName() { return _name; } ~~~ 我通常会把这个值域声明为private 。不过,在此之前,我需要先使用Pull Up Constructor Body,这样才能对_name 正确赋值: ~~~ class Party... protected Party (String name) { _name = name; } private String _name; class Employee... public Employee (String name, String id, int annualCost) { super (name); _id = id; _annualCost = annualCost; } class Department... public Department (String name) { super (name); } ~~~ Department.getTotalAnnualCost() 和 Employee.getAnnualCost() 两个函数的用途相同,因此它们应该有相同的名称。我先运用 Rename Method 把它们的名称改为相同: ~~~ class Department extends Party { public int getAnnualCost(){ Enumeration e = getStaff(); int result = 0; while (e.hasMoreElements()) { Employee each = (Employee) e.nextElement(); result += each.getAnnualCost(); } return result; } ~~~ 它们的函数本体仍然不同,因此我目前还无法使用 Pull Up Method。但是我 可以在superclass 中声明一个抽象函数: ~~~ abstract public int getAnnualCost() ~~~ 这一步修改完成后,我需要观察两个subclasses 的用户,看看是否可以改变它们转而使用新的superclass 。用户之一就是Department 自身,它保存了一个Employee 对象群集。Department .getAnnualCost() 只调用群集内的元素(对象)的getAnnualCost() 函数,而该函数此刻乃是在Party class 声明的: ~~~ class Department... public int getAnnualCost(){ Enumeration e = getStaff(); int result = 0; while (e.hasMoreElements()) { Party each = (Party) e.nextElement(); result += each.getAnnualCost(); } return result; } ~~~ 这一行为暗示一种新的可能性:我可以用Composite 模式[Gang of Four] 来对待Department 和Employee ,这样就可以让一个Department 对象包容另—个Department 对象。这是一项新功能,所以这项修改严格来说不属于重构范围。如果用户恰好需要Composite 模式,我可以修改_staff 值域名字,使其更好地表现这一模式。这一修改还会带来其他相应修改:修改addStaff() 函数名称,并将该函数的参数型别改为Party class 。最后还需要把headCount() 函数变成一个递归调用。我的作法是在Employee 中建立一个headCount() 函数,让它返回1;再使用Substitute Algorithm 修改Department 的headCount() 函数,让它总和(add)各部门的headCount() 调用结果。