A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

我们都知道,类往往因为承担过多的责任而变得臃肿不堪。这种情况下,一般会使用"提炼类"这种手法将一部分责任分离出去。如果一个类变得"不负责任",一般会使用“内联类”这种手法将它融入另一个类。如果一个类使用了另一个类,一般会运用"隐藏委托关系"手法将这种关系隐藏起来通常是有帮助的。有时候隐藏委托关系会导致拥有者的接口经常性地变化,这时就可考虑使用"移除中间人"这种手法了。

Move Method

若发现程序中有个函数与其所驻类之外的另一个类进行更多交流。应该在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是将旧函数完全移除。

动机:(七年之痒)
"搬移函数"是重构理论中比较重要的特性之一。一般情况下,如果一个类有太多的行为,或者如果一个类与另一个类有太多合作而形成高度耦合,这时候就应该搬移函数。通过这种手段,可以使系统中的类更简单,这些类最终也将更干净利落地实现系统交付的任务。
在进行开发的过程中,我会时不时地浏览类的所有函数,从中寻找这样的函数:其使用另一个对象的次数要多于使用自己所驻对象的次数。一旦一些字段被移动,就应该进行这样的检查。一旦发现有可能搬移函数,就观察调用它的那一端、它调用的那一端,以及继承体系中的它的任何一个重定义的函数。然后根据“该函数与哪个读对象的交流比较多”来决定其移动路径。
事情往往不是那么容易做出决定。如果不能肯定是否应该移动一个函数,就应该继续观察其它函数。移动其它函数往往会让这项决定变得容易一些。有时候,即使移动了其它函数,还是很难对当前的函数做出决定。如果真的很难做出决定,那么或许"移动该函数与否"并不重要。那就,就让它呆在那儿,反正以后总是可以修改的。

范例
从一个表示"账户"的Account类开始:

class Account {
        private AccountType _type;
        private int _daysOverdrawn;

        double overdraftCharge() {
                if (_type.isPremium()) {
                        double result = 10;
                        if (_daysOverdrawn > 7)
                                result += (_daysOverdrawn - 7) * 0.85;
                        return result;
                } else {
                        return _daysOverdrawn * 1.75;
                }
        }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
现在假设有几种新的账户,每一种都有自己的“透支金额计费规则”,这样,我们就希望将overdraftCharge()函数搬移到AccountType类中。
首先,需要观察被overdraftCharge() 使用的每一项特征,考虑是否值得将它们与overdraftCharge()函数一起移动。在此例中,我们将_daysOverdrawn字段保留在Account类中,因为这个值会随着不同种类的账户而发生变化。然后,将overdraftCharge()函数复制到AccountType中,并做相应调整。


class AccountType {
        double overdraftCharge(int daysOverdrawn) {
                if (isPremium()) {
                        double result = 10;
                        if (daysOverdrawn > 7)
                                result += (daysOverdrawn - 7) * 0.85;
                        return result;
                } else {
                        return daysOverdrawn * 1.75;
                }
        }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
在例中,“调整”所表达的意思是:(1)对于使用AccountType特性的语句,去掉_type;(2)想办法得到仍然需要的Account类特性。当需要使用原类时,可有四种选择:(a)将这个特性同样移动到目标类,一般变为其成员变量;(b)建立或使用一个从目标类到原类的引用关系;(c)将原对象以参数传递给目标函数;(d)如果所需特性是变量,将它当作参数传递给目标函数。本例中是将_daysOverdrawn变量作为参数传递给目标函数。
调整目标函数后,编译,然后就可以将原函数的函数本体替换为一个简单的委托动作,然后编译并测试。

class Account {
        double overdraftCharge() {
                return _type.overdraftCharge(_daysOverdrawn);
}
1
2
3
4
Move Field

程序中某个字段被其所驻类之外的另一个类更多地用到。应该在目标类新建一个字段,修改原字段的所有用户,令它们改用新字段。

动机:
如果发现,一个字段在其所驻类之外的另一个类有更多函数使用了它,就应该考虑搬移这个字段。所谓“使用”可能是通过设值/取值函数间接进行的。可能也会涉及移动该字段的用户(函数),这取决于是否需要保持接口不变化。如果这些函数看上去很适合待在原地,就选择搬移字段。
在使用“提炼函数”的时候,也可能需要搬移字段。这时会选择先搬移字段,然后搬移函数。
范例:
从Account类开始:

class Account {
        private AccountType _type;
        private double _interestRate;

        double interestForAmount_days(double amount, int days) {
                return _interestRate * amount * days / 365;
        }
}
1
2
3
4
5
6
7
8
我想把表示利率的_interestRate搬移到AccountType类中。目前有一些函数引用了它,interestForAmount_days()就是其中之一。下一步需要在AccountType中建立_interestRate字段以及相应的访问函数:


class AccountType {
        private double _interestRate;

        void setInterestRate(double rate) {
                _interestRate = rate;
        }

        double getInterestRate() {
                return _interestRate;
        }
}
1
2
3
4
5
6
7
8
9
10
11
12
这时候就可以编译新的AccountType类了。
现在需要让Account类中访问_interestRate字段的函数转而使用AccoutType对象,然后删除Account类中的_interestRate字段。此时,必须删除原字段,才能保证其访问函数的确改变了操作对象,因为编译器会帮助我们的。


class Account {
        private AccountType _type;

        double interestForAmount_days(double amount, int days) {
                return _type.getInterestRate() * amount * days / 365;
        }
}
1
2
3
4
5
6
7
8
Extract Class

某个类做了应该由两个类做的事。应该建立一个新类,将相关的字段和函数从旧类搬移到新类。

动机:
我们或多或少听过这样的教诲:一个类应该是一个清楚的抽象,处理一些明确的责任。但是,在实际工作中,类会不断成长扩展。你会在这儿加一些功能,在那儿加入一些数据。 当给类添加新责任时,就会觉得不值得为这项责任分理出一个单独的类。于是,随着责任的不断增加,这个类会变得过分复杂。很快,类就会变成一团乱麻,继而你就越发不想管理。
这样的类往往含有大量函数和数据,往往太大而不易理解。此时,就需考虑哪些部分是可以分离出去的,并将它们分离到一个单独的类中。如果某些数据和某些函数总是一起出现,某些数据经常同时变化甚至彼此相依,这就说明应该将其分离处理。一个比较有用的测试就是问问自己:如果搬移了某些字段和函数,会发生什么事情?其它字段和函数是否会变得无意义?
还有一个值得注意的地方是类的子类化方式。如果发现子类化只影响类的部分特性,或如果发现某些特性需要以一种方式来子类化,而另一些特性则需要以另一种方式子类化,则意味着需要分解原来的类。
范例

// 提炼类
        class Person {
                private String _name;
                private String _officeAreaCode;
                private String _officeNumber;

                public String get_name() {
                        return _name;
                }

                public String getTelephoneNumber() {
                        return ("(" + _officeAreaCode + ")" + _officeNumber);
                }

                public String get_officeAreaCode() {
                        return _officeAreaCode;
                }

                public void set_officeAreaCode(String areaCode) {
                        _officeAreaCode = areaCode;
                }

                public String get_officeNumber() {
                        return _officeNumber;
                }

                public void set_officeNumber(String number) {
                        _officeNumber = number;
                }
        }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
在这个例子中,可以将与电话号码相关的行为分离到一个独立类中。首先需要定义一个TelephoneNumber类来表示“电话号码”这个概念:

class TelephoneNumber {
                private String _areaCode;
                private String _number;

                public String get_AreaCode() {
                        return _areaCode;
                }

                public String getTelephoneNumber() {
                        return ("(" + _areaCode + ")" + _number);
                }

                public void set_AreaCode(String areaCode) {
                        _areaCode = areaCode;
                }

                public String getNumber() {
                        return _number;
                }

                public void setNumber(String number) {
                        _number = number;
                }
        }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Person {
                //......
                private TelephoneNumber _officeTelephone = new TelephoneNumber();
                private String _name;

                public String get_name() {
                        return _name;
                }

                public String getTelephoneNumber() {
                        return _officeTelephone.getTelephoneNumber();
                }

                public TelephoneNumber getOfficeTelephone() {
                        return _officeTelephone;
                }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
下一步要决定是否对用户公开这个新类?可以将Person中与电话号码相关的函数委托至TelephoneNumber,从而完全隐藏这个新类;也可以直接将它对用户公开。也可以将它公开给部分用户(位于同一个包中的用户),而不公开给其它用户。
---------------------
【转载,仅作分享,侵删】
作者:No_Game_No_Life_
原文:https://blog.csdn.net/No_Game_No_Life_/article/details/86132549


1 个回复

倒序浏览
奈斯
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马