重新学.Net[八]——CLR的面向对象实现

懒了好几天,尽玩去了。恩,赶在年前,把想写的再写一点^_^。

.Net目的之一是建立一个面向对象的编程平台。CLR当然需要支持面向对象的各个特征,确切一些,CLR基本可以视为一个完全面向对象的平台(回忆一下之前说的CTS)。CLR的面向对象有一些自己的特点和实现手段,下面会分点写一点,很零星不系统,算是一个笔记吧。

封装:
CLR对封装的支持没有什么特殊的。仍然是public,private,protected(在这里使用C#的语法而不是CLR的语法,方便大家理解,下同)几种主要权限,比较特殊的有Internal,将权限扩大到了程序集级别,我很少有用到,感觉破坏美好的感觉^_^。比较值得一说的是利用接口来巧妙实现类似于友元的功能,有兴趣的可以查一下C#实现记事本模式的方法。

抽象:
在CLR中(下面还是用C#语法),支持虚基类(abstract class,无法实例化,仅能用于做基类)和接口(interface)。在设计模式中有这样一个基本原则:面向接口编程。我个人觉得这里说的接口影射到C#中,不仅仅指interface,而应该指所有的抽象手段,包括abstract class 和interface。而在很多情况下,我们应该优先使用虚基类而不是接口。因为abstract class具有更好的可修改性。比如你提供了一个称为superman的abstract class和interface。当初它只有fly一个方法。后来我想为他添加一个walk的方法(超人也是人嘛,也不能天天飞啊)。这时候如果你用abstract class实现,只需要添加一个含默认实现的walk即可,而如果是interface实现的,不仅要改接口,还需要修改所有实现了该接口的类型(当然有时候这样还更好,很安全)。一个很有名的经验性原则是Is-a和Can-do原则。如果子类是基类的一部分用abstract class,如果基类只是子类拥有的一项功能,请用interface。相比之下,interface的设计要求更高一些,你一定要确定它不会被更改,否则会很痛苦。
回到设计模式中的面向接口编程原则,确实是金玉良言,但也不可强求。有时候确实不需要抽象了,就不必抽象了。在实践中还有一个很有效的原则必须遵守就是尽可能用最抽象的接口进行通信。考虑下面两种函数实现:
public IList getList();
public ArrayList getList();
如果,前面一个函数能满足你的需求,就千万别用第二个。更高的抽象意味着更低的通信成本和更高的变化支持。比如有一天我讨厌用默认的ArrayList实现我的功能了,我自己实现IList。那么所有用第二个函数的函数都需要修改,这是一个很恐怖的事情,绝对应该尽量避免。

多态:
在CLR层面,对多态的支持十分的全面。可以支持不同的参数各数,不同参数类型,不同返回值的函数多态。但是,在语言成面,比如C#。还只是能支持不同参数类型或数量的重载。
在实际应用中,我们应该尽量用多态了代替使用默认参数,同时把函数的具体实现放在有最长参数列的函数中,其他函数调用该函数即可。比如:
public void TheMethod(int a,int b,string s){//实现}
public void TheMethod(int a,int b){TheMethod(a,b,"");}
这也可以被用于检查你是否正确使用多态。比如你有一个TheMethod(int a)根本无法很好的调用TheMethod(int a,int b,string s)来作为其实现,你就应该考虑一下你这个函数的命名了,应该把它剔除出你的多态体系,另立门派。

继承:
继承是面向对象中最有效也是最麻烦的手段之一。用好继承实在是不容易的一件事情。CLR不支持多继承,你可以通过继承接口来实现多继承的需求。这降低了继承的复杂性。设计模式中另一个很重要的原则是优先使用组合而不是继承,同样一条不容置疑的金玉良言。一定要学会正确使用继承,这十分重要。
继承中很重要的一项内容就是函数的重写。CLR有其自己的一套虚函数实现机制,它利用元数据对虚函数进行动态的解析,不论你子类是否构造完成,它都具有调用到目前最深的继承体系中类的函数的功能。如果你熟悉C++,请一定要分清楚,这与C++中有很大的不同。(C++是用虚表来实现,动态构造。而CLR中用元数据标识,动态解析)想说清楚其中的不同不是一件很容易的事情。你可以尝试实现写下如下的代码:
    public abstract class Base
    {
        public Base()
        {
            Method();
        }

        public abstract void Method();
    }

    public class Derived : Base
    {
        private int value;
        public Derived()
        {
            value = 1;
        }

        public override void Method()
        {
            if (value == 1)
            {
                Console.WriteLine("OK");
            }
            else
            {
                Console.WriteLine("Wrong");
            }
        }
    }
然后实例化一个Derived对象,看看是否能够通过编译,运行结果是否与预期相符。还可以移植到C++中实现一下,也许你会有出乎意料的发现,对此机制有更好的了解。
了解这个情况,有助于你在构造函数中正确使用虚函数,也许下次你不会写出类似于上面那段代码的代码了。
与函数的重载需要一起说明的是函数的隐藏。如果基类中没指名函数是虚函数,子类中出现同名函数就会将基类中的同名函数隐藏。你可以用new关键字(C#中)放置在函数前,明确地告诉编译器,没错,我就是想隐藏它。要特别注意的就是类似于下面的代码也会被视为隐藏:
virtual void TheMethod() //基类中
void TheMethod() //子类中
你需要在子类函数前添加overide关键字告诉编译器我是想重写而不是隐藏。

其他:
在OO编程中还有很多需要注意的东西。比如默认构造函数,传值和传址,静态类等等。上面的只是一个简单的摘要,要了解更多细节需要多看书,多实践,多思考,希望大家共同提高共同进步。

posted on 2007-02-15 12:44  duguguiyu  阅读(891)  评论(3编辑  收藏  举报

导航