您的位置:首页 > 编程语言

改善代码设计 —— 组织好你的数据(Composing Data)

2011-07-24 18:15 246 查看

1. Self Encapsulate Field (自封装值域)

解释:


大部分类 (class) 中都会有一些值域 (field),随之还会有一些方法使用到了这些值域. "如果调用这些值域"这个问题分为两种观点: 1. 应该直接调用它们 2. 应该通过访问函数调用它们.

我觉得大部分情况下直接调用比较方便,过多的访问函数还会造成类中的函数过多,当然将来如果我觉得直接调用带来了一些问题,写一个一个的访问函数也并不是很困难.

下面的例子主要说明如何给值域写一个访问函数,并通过访问函数调用值域的值.


冲动前:

0
private
string
_userName,_password;
1
2
public
bool
IsValid()
3
{
4
bool
isValid = !(String.IsNullOrEmpty(_userName) &&
5
String.IsNullOrEmpty(_password));
6
return
isValid;
7
}

冲动后:

00
private
string
_userName,_password;
01
02
public
bool
IsValid()
03
{
04
bool
isValid = !(String.IsNullOrEmpty(GetUserName()) &&
05
String.IsNullOrEmpty(GetPassword()));
06
return
isValid;
07
}
08
private
string
GetUserName()
09
{
10
return
_userName;
11
}
12
private
string
GetPassword()
13
{
14
return
_password;
15
}

2. Replace Data Value with Object (以物件取代数据值)

解释:


如果你发现代码中有很多字段或者值域似乎都在描述某一样东西,可以考虑将这些字段或者值域封装到一个类中,用这个物件代替原先代码中繁杂的字段和值域.


冲动前:

0
class
Order
1
{
2
public
string
CustomerName {
get
;
set
;}
3
public
string
CustomerAddress {
get
;
set
;}
4
public
int
CreditLevel {
get
;
set
;}
5
public
int
CustomerTel {
get
;
set
;}
6
//...
7
}

冲动后:

00
class
Order
01
{
02
public
Customer Customer {
get
;
set
;}
03
}
04
05
class
Customer
06
{
07
public
string
CustomerName {
get
;
set
;}
08
public
string
CustomerAddress {
get
;
set
;}
09
public
int
CreditLevel {
get
;
set
;}
10
public
int
CustomerTel {
get
;
set
;}
11
//...
12
}

3. Change Value to Reference (将值对象改为引用对象)

解释:


一个类中有时包含值对象作为它的字段或者属性,如订单 Order 类中包含了一个 客户 customerName 的字段. 如果同一个客户有好几份订单,那么每一份订单就都会保存一次客户的姓名. 如果这个客户的姓名改变了,那么就需要更改每份个订单中的 customerName.
如果将 customerName 提取出去,提炼一个 Customer 的类,订单使用的是 Customer 实例的引用,则只需更改实例中客户姓名的属性,因为所有的订单都是引用的这个客户实例,所以它们不需要作其它更改.


冲动前:

0
class
Order
1
{
2
private
string
_customerName;
3
4
public
Order(
string
customerName)
5
{
6
this
._customerName = customerName;
7
}
8
}

冲动后:

00
class
Order
01
{
02
private
Customer _customer;
03
04
public
Order(Customer customer)
05
{
06
this
._customer = customer;
07
}
08
}
09
10
class
Customer
11
{
12
public
string
CustomerName {
get
;
set
;}
13
}

4. Change Reference to Value (将引用对象改为值对象)

解释:


如果给一个类传递引用类型变得不是你想要的效果,比如我曾经做过一个类似于 MSN 的软件,其中将人的头像,昵称,个性签名显示在一个自定义控件中,然后在使用了 ListBox.Add(myControl),结果我惊奇的发现最后好友列表里居然都是同一个 (最后一个) 人. 原来 ListBox.Add() 传递进去的都是引用的同一个 myControl 实例,尽管代码貌似在动态的生成每一个人,但其实都只修改了一个 myControl,而 ListBox 仅仅了引用这一个对象. 后来我在代码中动态的生成不同对象的实例才解决了这个问题.

除了这样,还可以在你的类中定义一个值对象,每一次生成一个类的实例时,类中值对象的控制权总是属于自己.


5. Replace Array with Object (用物件取代数组)

解释:


一个类中可能会包含很多字段,你不能因为这些字段都是 string 类型,就通通把它们放到一个 string[] 里,在类的方法体中通过数组索引来获取你需要的值,这样很难让别人记得你 string[0] 表示的是姓名,string[1] 表示的是他的住址...

下面的将展示前后代码可读性的差距.


冲动前:

0
string
[] _person =
new
string
[5];
1
2
public
string
GetName()
3
{
4
return
_person[0];
5
}
6
public
string
GetAdd()
7
{
8
return
_person[1];
9
}

冲动后:

0
private
Person _person;
1
2
public
string
GetName()
3
{
4
return
_person.Name;
5
}
6
public
string
GetAdd()
7
{
8
return
_person.Address;
9
}

6. Duplicate Observed Data (复制被监视数据)

解释:


一个良好的系统应该事先业务逻辑 (Business Logic) 与用户界面 (User Interface) 分离,如果没有这样做,常见的比如在 Program.cs 堆叠了大量的代码,业务逻辑与用户界面非常紧密的耦合在了一起. 实现业务逻辑与用户界面分离,最重要的实现是数据的同步机制,Duplicate Observed Data 重构手段用来完成这项工作. 由于例子较长,可以独立成篇,在以后介绍观察者模式 (Observer Pattern) 的时候会搬出来讨论.



7. Change Unidirectional Association to Bidirectional (将单向关联改为双向)

解释:


如果两个 Class 都需要使用对方的特性,但两者之间只有一条单向关联 (如只有调用方才能调用被调用的特性),如果你想让被调用的那一方知道有哪些物件调用了自身,这时就需要使用 Change Unidirectional Association to Bidirectional.

通常的做法是在被调用方的代码里定义一个被调用方类型的集合 (Collection),如 ArrayList<T>,List<T>(推荐使用 List<T>),在[调用方] 调用 [被调用方] 的时候,在 [被调用方] 里的集合中也添加一下自己的引用.


8. Change Bidirectional Association to Unidirectional (将双向关联改为单向)

解释:


使用了Change Unidirectional Association to Bidirectional 之后,如果需求变了,发现双向关联是没有必要的,这时就需要将双向关联改为单向,也就是 Change Bidirectional Association to Unidirectional,步骤与 Change Unidirectional Association to Bidirectional 相反.


9. Replace Magic Number with Symbolic Constant (用符号常量取代魔法数)

解释:


新手在写程序的时候,往往不注重命名 (name),他们对程序的要求也就是能正确运行即可. 在 IDE 中随便拖几个 Button,也不重新命名,于是在代码中出现了类似 Button1,Button2,Button3...之类的,你无法光看代码就能想象得出 Button1 对应 UI 中的哪一个按钮. 同样的坏习惯出现在代码的字段,临时变量的命名,到处是 a,aa,aaa...

使用 Replace Magic Number with Symbolic Constant 吧! 如果你的 Button 是用于"发送",那么你可以给它一个名字 ——btnSend;如果你的一个 Int32型 变量用于表示一个人的年龄,请给它一个名字 ——age.


10. Encapsulate Field (封装值域)

解释:


如果你的 class 中存在一个 public 字段,请将它设置为属性.

面向对象的第一条原则就是封装 (encapsulation),或者称之为数据隐藏 (Data Hiding),如果一个字段想被外界访问到,你应该将它设为属性,在 C# 中设置属性比在 Java 中要方便许多,因为 C# 编译器帮你做了一些额外工作.


冲动前:

0
public
string
_name;

冲动后:

0
public
string
Name {
get
;
set
;}

11. Encapsulate Collection (封装集合)

解释:


如果你的 class 中有一个方法需要返回多个值,在类中有一个集合 (Collection) 暂时保持这些返回值,有些情况下应当避免让 [调用端] 直接访问这个集合. 如果 [调用端] 修改了你集合的某一项,而 [被调用端] 不知道自己的集合被修改了,而另外的一些方法仍然在调用被修改后的集合,这样可能会造成无法预料的后果.

Encapsulate Collection 建议你在 [被调用端] 将这个集合封装 (至少你将它的访问权限设置为 private) 起来,有需要的话,并提供修改这个集合的函数.


12. Replace Record with Data Class (用数据取代记录)

解释:


与第二条 Replace Data Value with Object 和第五条 Replace Array with Object 做法相似,目的是提取类中的数据到一个描述记录 (Record) 的类中,方便以后可以对这个类进行扩展.


13. Replace Subclass with Fields (用值域取代子类)

解释:


子类建立在父类之上再增加新的功能或者重载父类可能的变化行为,有一种变化行为 (variant behavior) 成为常量函数 (constant method),他们会返回一个硬编码 (hard-coded) 值,这个值一般有这样的用途: 让不同的父类中的同一个访问函数返回不同的值,你可以在父类中将访问函数声明为抽象函数,并在不同的不同的子类中让它返回不同的值. 但如果子类中只有返回常量的函数,没有其它的作用,往往子类并没有太大的存在价值.

你可以在父类中设计一些与子类返回值相应的值域,再对父类做一些其它修改,从而可以消除这些子类,好处是避免不必要的子类带来的复杂性,这就是 Replace Subclass with Fields.


冲动前:

00
class
Person
01
{
02
protected
abstract
bool
isMale();
03
protected
abstract
char
Code();
04
//...
05
}
06
class
Male : Person
07
{
08
protected
override
bool
isMale()
09
{
10
return
true
;
11
}
12
protected
override
char
Code()
13
{
14
return
'M'
;
15
}
16
}
17
class
Female : Person
18
{
19
protected
override
bool
isMale()
20
{
21
return
false
;
22
}
23
protected
override
char
Code()
24
{
25
return
'F'
;
26
}
27
}

冲动后:

00
class
Person
01
{
02
public
bool
IsMale {
get
;
set
;}
03
public
char
Code {
get
;
set
;}
04
05
public
Person(
bool
isMale,
char
code)
06
{
07
this
.IsMale = IsMale;
08
this
.Code = code;
09
}
10
public
Person Male()
11
{
12
return
new
Person(
true
,
'M'
);
13
}
14
public
Person Female()
15
{
16
return
new
Person(
false
,
'F'
);
17
}
18
//...
19
}
调用的时候这样: Person Create_Chen = Person.Male();
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐