xingd.net

.net related techonology

导航

"C#类中虚方法相互调用的潜在重载错误"相关思考

Posted on 2005-02-25 15:07  xingd  阅读(1806)  评论(2编辑  收藏  举报
原文见http://www.cnblogs.com/birdshome/archive/2005/02/25/108866.html

birdshome在原文中引用了我给他的一个测试代码,但那个测试代码原意只是为了说明在Derived类的Foo中调用base.Foo时,因为多态的关系,Derived的Bar会被调用,而Base类本身的接口设计是有问题的。

“其实这确实是面向对象中的一个设计矛盾,等于平白的无故的把一些语义规则强加给了程序员,而且还是相当隐讳的。”
在OOD和OOP中,最重要的是Thinking in Object,语言所提供的面向对象的功能,更多的是表达出来的一种语义,表达的是对象的一种特性。C#中的virtual,不仅仅表示某一个方法是一个虚函数,更重要的是,它为对象的方法添加了多态的能力,意味着类的设计者希望这个类能够被派生,并且允许在派生类中对此方法进行重载。而此,语义的规则,反映的是程序员所设计的对象的特征,而不是实现相关的。OOD的第一步是抽象,如果没有抽象出类所代表的对象的特性,那么在实现过程中,就会因为对语言功能的错误使用而产生具有错误语义的抽象。

回过头来看Base类的接口设计,原来的代码如下:
public class Base
{
    
public virtual void Foo()
    
{
        Console.WriteLine(
"Base::Foo");
        
this.Bar();
    }

    
public virtual void Bar()
    
{
        Console.WriteLine(
"Base::Bar");
    }

}
;

从面向对象语义和对象接口设计的角度来看,Base类的接口设计是有很多问题的。

首先,Foo做为一个public的接口,却调用了另一个public Bar方法,从接口设计上来说,是功能重叠,不满足面向对象设计中的最小化接口要求。
其次,Foo是virtual方法,这意味着Foo的实现会被派生类重载,而在Foo的实现中,调用了方法Bar。派生类是没有责任在Foo的重载中调用Bar方法的,这意味着基类的逻辑不具有传递性。
第三,Foo本身是virtual方法,而其调用的Bar也是virtual方法,这导致派生类具有过强的重载能力,这种能力常常会造成逻辑上的冲突。

因此,Base类的接口需要重新设计,可以参考Template Method模式,重构如下:
public class Base
{
    
public void Foo()
    
{
        
this.DoFoo();
        
this.DoBar();
    }


    
public void Bar()
    
{
        
this.DoBar;
    }


    
protected virtual void DoFoo()
    
{
        
    }


    
protected virtual void DoBar()
    
{
    
    }

}
;

当然,真正适合的接口设计要参照应用的场景,是不能简单的通过一个示例代码来说明的。

而原文的问题,是因未做if (obj != null)的测试而引起的,因为Bar本身也是一个public方法,能够被其他方法调用。从Derived类的接口设计上来看,没有Foo一定在Bar调用前的语义,而此必要的判断是必需的。