重构—改善既有代码的设计008:重新组织数据(Organizing Data)
一:SELF ENCAPSULATE FIELD
二:REPLACE DATA VALUE WITH OBJECT
三:CHANGE VALUE TO REFERENCE
四:CHANGE REFERENCE TO VALUE
五:REPLACE ARRAY WITH OBJECT
六:DUPLICATE OBSERVED DATA
七:CHANGE UNIDIRECTIONAL ASSOCIATION TO BIDIRECTIONAL
八:CHANGE BIDIRECTIONAL ASSOCIATION TO UNIDIRECTIONAL
九:REPLACE MAGIC NUMBER WITH SYMBOLIC CONSTANT
十:ENCAPSULATE FIELD
十一:ENCAPSULATE COLLECTION
十二:REPLACE RECORD WITH DATA CLASS
十三:REPLACE TYPE CODE WITH CLASS
十四:REPLACE TYPE CODE WITH SUBCLASSES
十五:REPLACE TYPE CODE WITH STATE/STRATEGY
十六:REPLACE SUBCLASS WITH FIELDS
一:SELF ENCAPSULATE FIELD
1:你直接访问一个值域,但与值域之间的耦合关系逐渐变得笨拙
为这个值域建立取值/设值函数,并且只以这些函数来访问值域
2:动机
直接访问变量的好处是代码比较容易读懂。
间接访问的好处是子类得以通过覆写一个函数而改变获取数据的路径(好处体现在继承的覆盖处)。
3:做法
.为待封装值域建立取值/设置函数
.找出该值域的所有引用点,将它们全部替换为对于取值/设置函数的引用
.将该值域声明为PRIVATE
.复查,确保找出所有引用点
.编译,测试
4:范例
class IntRange
{
private int _low, _high;
public IntRange(int low, int high)
{
_low = low;
_high = high;
}
public Boolean includes(int arg)
{
return arg >= _low && arg <= _high;
}
public voud grow(int factor)
{
_high = _high * factor;
}
}
改为:在后面的继承中,就能体现出优势来
class IntRange
{
private int _low, _high;
public IntRange(int low, int high)
{
_low = low;
_high = high;
}
public int getLow()
{
return _low;
}
public void setLow(int arg)
{
_low = arg;
}
public int getHigh()
{
return _high;
}
public void setHigh(int arg)
{
_high = arg;
}
public Boolean includes(int arg)
{
return arg >= getLow() && arg <= getHigh();
}
public voud grow(int factor)
{
setHigh(getHigh() * factor);
}
}
二:REPLACE DATA VALUE WITH OBJECT
1:你有一笔数据项,需要额外的数据和行为
将这笔数据项变成一个对象
2:动机
开发初期,往往决定你简单的数据项表示简单的行为。但是随着开发的进行,你可能会发现,这些简单的数据项不再那么简单了。
3:做法
.为待替换述职新建一个类,在其中声明一个FINAL值域,其类型和源类中的待替换数值类型一样,然后在新类中加入这个值域的取值函数,再加上一个接受此值域为参数的构造函数
.编译
.将源类中的待替换数值值域的类型改为上述新建类
.修改源类中此一值域的取值函数,令它调用新建类的取值函数
.如果源类构造函数中提及这个待替换值域,我们就修改构造函数,领它改用新类的构造函数来对值域进行赋值动作
.修改源类中待替换值域的设置函数,令它为新类创建实体
.编译,测试
4:范例
private static int numberOfOrderFor(ArrayList orders, string customer)
{
int result = 0;
foreach (object o in orders)
{
Order each = (Order)o;
if (each.getCustomer().Equals(customer))
{
result++;
}
}
return result;
}
public class Order
{
private string _customer;
public Order(string customer)
{
_customer = customer;
}
public string getCustomer()
{
return _customer;
}
public void setCustomer(string arg)
{
_customer = arg;
}
}
改为:
private static int numberOfOrderFor(ArrayList orders, string customerName)
{
int result = 0;
foreach (object o in orders)
{
Order each = (Order)o;
if (each.getCustomer().Equals(customerName))
{
result++;
}
}
return result;
}
public class Order
{
private Customer _customer;
public Order(string customerName)
{
_customer = new Customer(customerName);
}
public string getCustomer()
{
return _customer.getName();
}
public void setCustomer(string customerName)
{
_customer = new Customer(customerName);
}
}
public class Customer
{
private string _name;
public Customer(string name)
{
_name = name;
}
public string getName()
{
return _name;
}
}
三:CHANGE VALUE TO REFERENCE
1:如果你有一个类,衍生出许多相等实体,你希望它们替换为单一对象
将这个VALUE OBJECT实值对象变成一个REFERENCE OBJECT引用对象
2:动机
有时,会从一个简单的VALUE OBJECT开始,在其中保存少了不可修改的数据
而后希望给这个对象加入一些可修改的数据,并确保对任何一个对象的修改都能影响所有引用此对象的地方,这是就需将对象编程REFERENCE OBJECT
3:做法
4:范例
private static int numberOfOrderFor(ArrayList orders, string customerName)
{
int result = 0;
foreach (object o in orders)
{
Order each = (Order)o;
if (each.getCustomer().Equals(customerName))
{
result++;
}
}
return result;
}
public class Order
{
private Customer _customer;
public Order(string customerName)
{
_customer = new Customer(customerName);
}
public string getCustomer()
{
return _customer.getName();
}
public void setCustomer(string customerName)
{
_customer = new Customer(customerName);
}
}
public class Customer
{
private string _name;
public Customer(string name)
{
_name = name;
}
public string getName()
{
return _name;
}
}
目前为止CUSTOMER对象还是VALUE OBJECT,每个ORDER对象还是拥有各自的CUSTOMER对象
改为,一旦客户拥有不同的订单,即订单共享同一个CUSTOMER对象,而不必各自拥有
private static int numberOfOrderFor(ArrayList orders, string customerName)
{
int result = 0;
foreach (object o in orders)
{
Order each = (Order)o;
if (each.getCustomer().Equals(customerName))
{
result++;
}
}
return result;
}
public class Order
{
private Customer _customer;
public Order(string customerName)
{
_customer = Customer.create(customerName);
}
public string getCustomer()
{
return _customer.getName();
}
public void setCustomer(string customerName)
{
_customer = new Customer(customerName);
}
}
public class Customer
{
private string _name;
private Customer(string name)
{
_name = name;
}
public static Customer create(string name)
{
return new Customer(name);
}
public string getName()
{
return _name;
}
}
一般情况下,顾客对象都是创建好的,从数据库或文件中来
这样的话,由于CUSTOMER对象预创建好,所以每次用时,直接取,共享
private static int numberOfOrderFor(ArrayList orders, string customerName)
{
int result = 0;
foreach (object o in orders)
{
Order each = (Order)o;
if (each.getCustomer().Equals(customerName))
{
result++;
}
}
return result;
}
public class Order
{
private Customer _customer;
public Order(string customerName)
{
_customer = Customer.getNamed(customerName);
}
public string getCustomer()
{
return _customer.getName();
}
public void setCustomer(string customerName)
{
_customer = new Customer(customerName);
}
}
public class Customer
{
private string _name;
private static ArrayList _instance = new ArrayList();
private Customer(string name)
{
_name = name;
}
public static Customer getNamed(string name)
{
return (Customer)_instance[name];
}
private static localCustomer()
{
_instance.Add(new Customer("1"));
_instance.Add(new Customer("2"));
_instance.Add(new Customer("3"));
}
public string getName()
{
return _name;
}
}
四:CHANGE REFERENCE TO VALUE
1:如果有一个REFERENCE OBJECT引用对象,很小且不可变,而且不易管理
将它变成一个VALUE OBJECT实值对象
2:动机
3:做法
4:范例
五:REPLACE ARRAY WITH OBJECT
1:你有一个数组,其中的元素各自代表不同的东西
以对象替换数组,对于数据中的各个元素,以一个值域表示
string[] row = new string[3];
row[0] = "Lvierpool";
row[1] = "15";
Performance row = new Performance();
row.setName("Liverpool");
row.setWins("15");
2:动机
数组是一种常见的用于组织数据的结构体。只用于某种顺序容纳一组相似对象。
如果一个数组容纳了数组不同对象,这会给ARRAY用户带来麻烦,因为很难记住每个数组表示的意义。
对象就不同了,我们可以运用值域名称和函数名称来传达这样的信息,而无需死机,也无需依赖注释。
3:做法
.新建一个类,表示数组所示信息,并在该类中以一个PUBLIC值域保存原先的数组
.修改数组的所有用户,让它们改用新建的类实体
.编译,测试
.逐一为数组元素添加取值/设值函数,根据元素的用途为这些访问函数命名。修改客户代码,让它们通过访问函数取用数组内的元素,每次修改后,编译并测试
.当所有对数组的直接访问都被取代为对访问函数的调用后,将类之中保存该数组的值域声明为PRIVATE
.编译
.对于数组内的每一个元素,在新类中创建一个类型相当的值域,修改该元素的访问函数,领它改用上述的新建值域
.修改每一个元素,编译并测试
.数组的所有元素都在对应的类内有了相应值域之后,删除该数组
4:范例
string[] row = new string[3];
row[0] = "Lvierpool";
row[1] = "15";
string name = row[0];
int wins = Integer.parseInt(row[1]);
改后:
public class Performance
{
private string _name;
private int _win;
public void setName(string arg)
{
_name = arg;
}
public string getName()
{
return _name;
}
public void setWin(int arg)
{
_win = arg;
}
public int getWin()
{
return _win;
}
}
Performance row = new Performance();
row.setName("Liverpool");
row.setWin(15);
string name = row.getName();
int wins = row.getWin();
六:DUPLICATE OBSERVED DATA
七:CHANGE UNIDIRECTIONAL ASSOCIATION TO BIDIRECTIONAL
1:两个类都需要使用对方特性,当其间只有一条单向连接
添加一个反向指针,并使修改函数能够同时更新两条连接
八:CHANGE BIDIRECTIONAL ASSOCIATION TO UNIDIRECTIONAL
1:两个类之间有双向关联,但其中一个类如今不再需要另一个类的特性
去除不必要的关联
九:REPLACE MAGIC NUMBER WITH SYMBOLIC CONSTANT
1:如果有一个字面数值带有特别含义
创建一个常量,根据其含义为它命名,并将上述的字面数值替换为这个常量
double potentialEnergy(double mass, double height)
{
return mass * 9.81 * height;
}
private const double GRAVITATIONAL_CONSTANT = 9.81;
double potentialEnergy(double mass, double height)
{
return mass * GRAVITATIONAL_CONSTANT * height;
}
2:动机
许多语言都允许你声明常量,常量不会造成任何性能开销,确可以大大提高代码的可读性
3:做法
.声明一个常量,令其值为原本的魔法数值
.找出这个魔法数的所有引用点
.检查是否可以使用这个新声明的常量来替换该魔法数。如果可以,便以此常量替换之
.编译
.所有魔法数都被替换完毕后,编译并测试,此时整个程序应该运转如常,就像没有做任何修改一样
十:ENCAPSULATE FIELD
1:如果你的类中存在一个PUBLIC值域
将它声明为PRIVATE,并提供相应的访问函数
public string _name;
修改为:
private string _name;
public string getName()
{
return _name;
}
public void setName(string arg)
{
_name = arg;
}
十一:ENCAPSULATE COLLECTION
十二:REPLACE RECORD WITH DATA CLASS
十三:REPLACE TYPE CODE WITH CLASS
1:类之中有一个数值型别码,当它并不影响类的行为
以一个新的类替换该数值型别码
2:范例
public class Person
{
public static int O = 0;
public static int A = 1;
public static int B = 2;
public static int AB = 3;
private int _bloodGroup;
public Person(int bloodGroup)
{
_bloodGroup = bloodGroup;
}
public void setBloodGroup(int arg)
{
_bloodGroup = arg;
}
public int getBloodGroup()
{
return _bloodGroup;
}
}
改为:
public class BloodGroup
{
public static BloodGroup O = new BloodGroup(0);
public static BloodGroup A = new BloodGroup(1);
public static BloodGroup B = new BloodGroup(2);
public static BloodGroup AB = new BloodGroup(3);
private int _code;
private BloodGroup(int code)
{
_code = code;
}
public int getCode()
{
return _code;
}
}
class Person
{
private BloodGroup _bloodGroup;
public Person(BloodGroup bloodGroup)
{
_bloodGroup = bloodGroup;
}
public BloodGroup getBloodGroup()
{
return _bloodGroup;
}
public void setBloodGroup(BloodGroup arg)
{
_bloodGroup = arg;
}
}
static void Main(string[] args)
{
Person thePerson = new Person(BloodGroup.A);
thePerson.getBloodGroup().getCode();
thePerson.setBloodGroup(BloodGroup.AB);
}
十四:REPLACE TYPE CODE WITH SUBCLASSES
十五:REPLACE TYPE CODE WITH STATE/STRATEGY
十六:REPLACE SUBCLASS WITH FIELDS