1. 将对象的ID列设置为readonly,同时为它提供一个get访问器。
注:虽然我给出的C#代码,但是下面所有的知识点在C++,java中同样适用。
首先,让我们先来看一下这个Commodity类。
public class Commodity { string customerID; public string CustomerID { get { return customerID; } set { customerID = value; } }
string name; public string Name { get { return name; } set { name = value; } } } |
这是一个商品类,其中包含有一个commodityID字段,用于标示对象,还有一个name字段,标示商品的名称,同时为这个name字段添加了一个get/set访问器和赋值器。这个类还有一些类似的字段,我没有写出来。
看似一段没有问题的代码实际上漏洞百出,你肯定也在很多书中看到过这样的代码。如果我是一个面试官,有人这样给我写代码,我肯定不会用他。首先,那个commodityID列有一个set赋值器,这意味着一旦创建了一个商品对象之后,就可以修改对象的commodityID值,代码如下:
Commodity commodity = new Commodity("book10010", "C++编程思想", 100); commodity.customerID = "book00000"; |
这通常会引起灾难,并且带来难以察觉的错误。commodityID就相当于我们的身份证,一个人从出生开始,他的身份证号码就会伴随一生,不可能更改,我们可以通过一个身份证来确定一个人。同样,一个对象一旦创建,那么他的ID就永远不可能被修改,我们会通过这个ID来进行一些查询等操作。
因此我们应该把上面的设计修改成如下:
public class Commodity { readonly string customerID; public string CustomerID { get { return customerID; } }
string name; public string Name { get { return name; } get { return name; } } } |
通过将customerID列设置为readonly,并且只为它提供了一个get访问器,用户只可以访问它,而不能够修改它。同样在该类内部也不能修改它,如果修改它将会得到一个编译期间错误,customerID一旦在构造函数中初始化之后,就不能再被修改。
如果一个对象永远不可能被修改,会带来很多好处,你可以任意的使用它,而不用担心在使用的过程中,被他人偷偷的修改。如果你知道多线程,你就会知道这样有多重要。
2.不要胡乱的添加get访问器以及set赋值器
你肯定见过这样的设计,一个数据类(譬如上面的Commodity类),含有一些私有字段,而后又为这些私有字段添加了get访问器以及set访问器,用户不仅可以访问他们还可以修改他们。这是一种非常糟糕的设计。
遗憾的是,很多书中都充斥着这样的代码,包括我们的教材书。你绝对不应该被这些代码所误导,在一个真正的面向对象设计中,是否封装数据结构需要经过慎重的考虑。
在我写代码的时候,从来不向他们添加set修改器,只有再真正需要修改他们的时候我才为他们添加set修改器。同时我也建议这样做。
因此我们应该将name私有字段的set修改器去掉,将来你发现需要修改一个商品的名称时,你在为他们添加一个set修改器也不迟。最终代码如下:
public class Commodity { string customerID; public string CustomerID { get { return customerID; } }
string name; public string Name { set { name = value; } } } |
3.用构造函数链消除构造函数中的代码。
现在来看一下Commodity的构造函数,构造函数负责初始化对象的数据。
public Commodity(string customerID, string customerName, double price, int quantity) { this.customerID = customerID; this.name = customerName; this.price = price; this.quantity = quantity; } public Commodity(Commodity commodity) { this.customerID = commodity.customerID; this.name = commodity.name; ; this.price = commodity.price ; this.quantity = commodity.quantity; }
}
|
这段代码最大的诟病就是冗余——重复代码太多了。第一个构造函数和第二构造函数的初始化代码竟然完全一样。一旦将来发现quantity的命名并不合适,我们可能会将他命名为inventory(库存量),这样就需要同时在两个地方去修改。在一个大型系统中,如果这种重复代码散落在各个角落,那么一旦发生变化维护起来就非常困难,因为你必须找出所有重复的代码,然后去一一修改他们。
你可能听过千年虫的问题,事实上千年虫的问题很简单:在2000年以前许多公司都用两位数来表示年份,譬如用99表示1999年。显然如果用这种方法表示2000年以后的日期就不够用了。人们开始在系统中一处处的查找并修改他们,然而这种问题出现在数以万计的地方,各大公司投入大量的人力和财力。全世界因为这个问题损失了数百亿美元。
当你写代码的时候,如果发现重复代码,那么请将他们提炼到一处,这会给你带来很大的好处,一旦发生变化,我们只需要在一个地方修改,同时你的代码页将更加整洁,你是非常棒的程序员。
因此,我们将上面的代码用构造函数链来消除重复代码,在C++和Java中也可以采用类似的手法,只不过他们的语法有些不同而已。代码如下:
public Commodity(string customerID, string customerName, double price, int quantity) { this.customerID = customerID; this.name = customerName; this.price = price; this.quantity = quantity; } public Commodity(Commodity commodity) :this(commodity.customerID,commodity.name,commodity.price,commodity.quantity) {
}
|
如果你不懂的什么是构造函数链,请查阅相关书籍,msdn文档。再给大家举一个具有重复代码的例子:
public void AddItem(Item newItem) { Item result = items.Find( delegate(Item current) { return current.CommodityID == commodityID; }); if (item== null) items.Add(newItem); else item.Quantity += newItem.Quantity; } public void RemoveByCommodityID(string commodityID) { Item result = items.Find( delegate(Item current) { return current.CommodityID == commodityID; }); if (result != null) items.Remove(result); else throw new InvalidOperationException(……… )
|
第一个方法的职责是想购物车中添加一个条目(Item),如果购物车没有指定商品则添加,否则,累加购买数量.第二个方法的职责是根据指定商品ID删除一个条目,如果购物车中存在该商品则删除,否则抛出一个异常。
你能发现重复代码么?两个方法都有如下的代码:
Item result = items.Find( delegate(Item current) { return current.CommodityID == commodityID; });
|
应该将重复代码提炼到一个新建的方法中,然后让原方法去调用这个新建的方法。在这里,我们将重复代码提炼到一个名为FindByCommodityID方法中,该方法的职责是根据指定商品ID查找Item。如下:
public void AddItem(Item newItem) { Item item = FindByCommodityID(newItem.CommodityID); if (item== null) items.Add(newItem); else item.Quantity += newItem.Quantity; } public void RemoveByCommodityID(string commodityID) { Item result = FindByCommodityID(commodityID); if (result != null) items.Remove(result); else throw new InvalidOperationException(………); } public Item FindByCommodityID(string commodityID) { Item result = items.Find( delegate(Item current) { return current.CommodityID == commodityID; }); return result; } |