代码的演进以及常见的设计错误

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;

        }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值