对为什么使用访问器(getter),以及什么是继承的一点看法

我们知道,一个高内聚、低耦合的模块是一个可复用性,可维护性都比较高的模块。

使用访问器有助于使一个类降低耦合性。

我们来看一个例子:

 
   
1 class  Line
2 ExpandedBlockStart.gifContractedBlock.gif {
3public:
4    Point start;
5    Point end;
6    double length;
7}
;
8

这个类看起来比较合理,因为一个线段具有起点、终点和长度(即使长度为0)等属性。

然而,线段的长度却是由起点和终点的位置决定的。

所以一个看起来比较好的实现方式可能是这样的:

1 class  Line
2 ExpandedBlockStart.gifContractedBlock.gif {
3public:
4    Point start;
5    Point end;
6ExpandedSubBlockStart.gifContractedSubBlock.gif    double getLength() return start.distanceTo(end); }
7}

8

 

然而,在以后的开发过程中,你可能由于性能的考虑而修改这个类,比如说这样:

 
   
ContractedBlock.gif ExpandedBlockStart.gif
 1class Line
 2ExpandedBlockStart.gifContractedBlock.gif{
 3public:
 4ExpandedSubBlockStart.gifContractedSubBlock.gif    Line* setStart(Point value) { start = p; changed = truereturn this; }
 5ExpandedSubBlockStart.gifContractedSubBlock.gif    Line* setEnd(Point value) { end  = p; changed = truereturn this; }
 6ExpandedSubBlockStart.gifContractedSubBlock.gif    Point getStart() return start; }
 7ExpandedSubBlockStart.gifContractedSubBlock.gif    Point getEnd() return end; }
 8 
 9    double getLength()
10ExpandedSubBlockStart.gifContractedSubBlock.gif    {
11        if(changed)
12ExpandedSubBlockStart.gifContractedSubBlock.gif        {
13            length = start.distanceTo(end);
14            changed = false;
15        }

16        return length;
17    }

18private:
19    bool changed;
20    double length;
21    Point start;
22    Point end;
23}
;
24
从上一个类变化到这个类会引起非常大的变化。

但是,如果在一开始,我们就使用访问器的话,这个变化就被限制在了这个类内部,而不会扩散到整个程序。

这说明了,在面向对象的语言中,我们应该尽可能的使用访问器而不是字段(field)。

 

为什么访问器这么有效?

我们来看看上一个例子中,究竟发生了什么变化,使得这种变化影响了整个程序。

这个变化就是,我们原本使用一个字段来获得数据,但是后来,我们使用了一个方法来获得数据。

这种本质上的改变,使得我们必须得重新检查我们的程序。

但是访问器使得我们通过一个统一的接口去访问类中的数据,并且,我们不知道这个数据是通过存储还是计算来实现的。

也就是说,访问器掩盖了数据的存储形式和计算过程,使得这个类的内聚性提高了,从而降低了与系统的其他部分的耦合。

 

 

最近翻了翻书,发现各种书里面,关于继承的说法都很复杂。

在我看来,父类就是对子类的抽象,子类是具体的事物,父类是抽象的事物。

父类与子类的关系是抽象和具体的关系,是一般和特殊的关系。

例如:

 
   
ContractedBlock.gif ExpandedBlockStart.gif
 1class Line
 2ExpandedBlockStart.gifContractedBlock.gif{
 3public:
 4ExpandedSubBlockStart.gifContractedSubBlock.gif    Point getStart() {}
 5ExpandedSubBlockStart.gifContractedSubBlock.gif    Line* setStart(Point value) {}
 6ExpandedSubBlockStart.gifContractedSubBlock.gif    Point getEnd() {}
 7ExpandedSubBlockStart.gifContractedSubBlock.gif    Line* setEnd(Point value) {}
 8 
 9ExpandedSubBlockStart.gifContractedSubBlock.gif    double getLength() {}
10    virtual void Draw() = 0;
11protect:
12    
13}
;
14 
15class Line2D : Line
16ExpandedBlockStart.gifContractedBlock.gif{
17public:
18ExpandedSubBlockStart.gifContractedSubBlock.gif    void Draw() //Draw a 2D Line }
19}
;
20 
21class Line3D : Line
22ExpandedSubBlockStart.gifContractedSubBlock.gif{
23public:
24ExpandedSubBlockStart.gifContractedSubBlock.gif    void Draw() //Draw a 3D Line }
25}
;
Line是一个一般的线段,描述了一般的线段都具有的行为和属性。

Line2D是一个2D的线段,它实现了一个画一个2D线段的方法。

Line3D是一个3D的线段,它实现了一个画一个3D线段的方法(很炫是么,呵呵)。

可以看出,Line是对所有的线段的一个抽象,Line2D是一个具体的2D线段,Line3D是个具体的3D线段。

Line是一个一般的线段,而Line2D、Line3D是特殊的线段。

 

继承没什么神秘的,就是建立了这种一般到特殊的关系。

我们用is-a原则判断继承关系的时候,实际上就是判断子类是不是一个特殊的父类,或者说子类是不是父类的一个具体实现。

 

下面我们再说Liskov替换原则

Liskov替换原则:子类对象能够替换父类的对象而被使用。

这个原则是什么意思呢?

子类对象能够替换父类对象,意味着,子类对象实现的各种方法的意义与父类相同,他们的意图一致。

显而易见,因为子类是特殊,父类是一般,所以父类的含义应该比子类更广。

这也是为什么父类又被称为超类。

也就是说,子类的含义是被其父类所包含的,子类的能力不能超出其父类的能力的含义所包含的范围。

并且,子类的的含义应该与其父类相同。

比如说上面的例子。

Line要求Draw方法画一条线段。那么Line2D中的Draw方法,就不能画一个圆,只能也是画一个线段。

但是Line对于Draw方法画一条什么样的线段并没有规定,所以Line2D中的Draw方法可以画任意一种线段。

这个理解起来比较复杂,我觉得还是没办法用清晰地语言去描述。

但是,只要牢牢抓住一点就可以了:子类是父类的特殊形式。子类必须在父类所涵盖的范围内。

转载于:https://www.cnblogs.com/HCOONa/archive/2009/11/01/1593827.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值