您的位置:首页 > 其它

坏味道之3 过大类 & Extract class & Move Method & Encapsulate Field & Self Encapsulate Field

2011-12-22 16:55 363 查看
对于类,如果想要用单一类做太多的事情,就会出现太多的instance变量

通常如果class内的数个变量有着相同的前缀或字尾,这就意味有机会把它们提炼到某个组件内。

Extract class

某个class做了应该由两个classes做的事。

建立一个新class,将相关的值域和函数从旧class搬移到新class。

如果某些数据和某些函数总是一起出现,如果某些数据经常同时变化甚至彼此相依,这就表示你应该将它们分离出去。

例子:
class Person...

public String getName() {

return _name;

}

public String getTelephoneNumber() {

return ("(" + _officeAreaCode + ") " + _officeNumber);

}

String getOfficeAreaCode() {

return _officeAreaCode;

}

void setOfficeAreaCode(String arg) {

_officeAreaCode = arg;

}

String getOfficeNumber() {

return _officeNumber;

}

void setOfficeNumber(String arg) {

_officeNumber = arg;

}

private String _name;

private String _officeAreaCode;

private String _officeNumber;
在这里我们可以将与电话号码相关的行为分离到一个独立的类中。
首先:定义一个表示电话号码的类:
class TelephoneNumber{ }
然后从Person建立到TelephoneNumber的连接:
class Person
private TelephoenNumber officeTelephone = new TelephoneNumber();
然后,运用Move field方法移动一个值域。

下面先说Move Field方法
如果某个值域被它所在地类之外的另一个类更多的用到。
做法:1:如果field的属性是public的,应该首先使用Encapsulate Field方法封装:
所谓的封装值域,意思就是将值域声明为private方法,并提供相应的访问函数也就是getter/setter方法。
2:在目标类中建立了相同的值域
3:在原对象中引用目标对象,然后删除原值域
4:将

将所有「对source field的引用」替换为「对target适当函数的调用」。
Ø如果是「读取」该变量,就把「对source field的引用」替换为「对target取值函数(getter)的调用」;如果是「赋值」该变量,就把对source field的引用」替换成「对设值函数(setter)的调用」。
Ø如果source field不是private,就必须在source class的所有subclasses中查找source field的引用点,并进行相应替换。
例子:
源代码
class Account...

private AccountType _type;

private double _interestRate;

double interestForAmount_days (double amount, int days) {

return _interestRate * amount * days / 365;

}
然后最后改成:

class Account...

private AccountType _type;

private double _interestRate;

double interestForAmount_days (double amount, int days) {

return getInterestRate() * amount * days / 365;

}

private void setInterestRate (double arg) {

_interestRate = arg;

}

private double getInterestRate () {

return _interestRate;

}

这样,在搬移field之后,我就只需要修改访问函数(accessors)就行了 :

double interestForAmountAndDays (double amount, int days) {

return getInterestRate() * amount * days / 365;

}

private void setInterestRate (double arg) {

_type.setInterestRate(arg);

}

private double getInterestRate () {

return _type.getInterestRate();

}


自封装值域:

你直接访问一个值域(field),但与值域之间的耦合关系逐渐变得笨拙。

为这个值域建立取值/设值函数(getting and setting methods ),并且只以这些函数来访问值域。

private int _low, _high;

boolean includes (int arg) {

return arg >= _low && arg <= _high;

}

private int _low, _high;

boolean includes (int arg) {

return arg >= getLow() && arg <= getHigh();

}

int getLow() {return _low;}

int getHigh() {return _high;}


自封装,也就是自己调用自己的值域的时候也是通过方法而不是直接使用值域,这个在子类时体现出价值来。

原来的代码:
class IntRange {

private int _low, _high;

boolean includes (int arg) {

return arg >= _low && arg <= _high;

}

void grow(int factor) {

_high = _high * factor;

}

IntRange (int low, int high) {

_low = low;

_high = high;

}
为了封装_low和_high这两个值域,我先定义「取值/设值函数」(如果此前没有定义的话),并使用它们:

class IntRange {

boolean includes (int arg) {

return arg >= getLow() && arg <= getHigh();

}

void grow(int factor) {

setHigh (getHigh() * factor);

}

private int _low, _high;

int getLow() {

return _low;

}

int getHigh() {

return _high;

}

void setLow(int arg) {

_low = arg;

}

void setHigh(int arg) {

_high = arg;

}


使用本项重构时,你必须小心对待「在构造函数中使用设值函数」的情况。一般说来,设值函数被认为应该在「对象创建后」才使用,所以初始化过程中的行为有可能与设值函数的行为不同。在这种情况下,我也许在构造函数中直接访问值域,要不就是建立另一个独立的初始化函数:

IntRange (int low, int high) {

initialize (low, high);

}

private void initialize (int low, int high) {

_low = low;

_high = high;

}


一旦你拥有一个subclass,上述所有动作的价值就体现出来了。如下所示:

class CappedRange extends IntRange {

CappedRange (int low, int high, int cap) {

super (low, high);

_cap = cap;

}

private int _cap;

int getCap() {

return _cap;

}

int getHigh() {

return Math.min(super.getHigh(), getCap());

}

}

现在,我可以在CappedRange class中覆写getHigh(),从而加入对cap的考虑,而不必修改IntRange class的任何行为。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐