一、很多人在初次接触设计模式的时候,最大的疑惑莫过于碰到这样的句子:(父类Animal和子类Dog)Animal a2 = new Dog(); 这是什么玩意儿?究竟有什么用途,我直接用子类对象不就完了? 二、这种用法叫做“父类引用指向子类对象”(跟绕口令似的),或者叫“父类指针指向子类对象”,指的是定义一个父类的引用,而它实际指向的是子类创建的对象。功能上相当于子类的“向上转型”,或者“上溯对象”。 Animal a2 = new Animal(); Dog dog = new Dog(); a2 = dog; 将子类对象赋给父类的引用就是向上转型,Animal a2 = new Dog();中的new Dog()就是实例化一个没有名字的对象,然后转型给父类的引用a2,仅此而已。 三、一般来说,一个对象实例创建完就定死了,比如dog,如果程序里的变量写dog的话,万一以后发生变化就要改代码了。那么事先就不写死dog,而是写父类Animal,那么以后用到Animal的地方,用dog实例,cat实例都可以取代,不用改实例名了。说玄乎一点,也就体现了面向对象“多态”的特性。 下面看一个很简单的例子,就更加明白了: class Animal { private String type = "Animal"; public virtual void showLegs() { Console.WriteLine("This is an {0} , Animal always has legs",type); } } class Dog : Animal { private String type = "Dog"; public override void showLegs() { Console.WriteLine("This is a {0} , Dog has four legs", type); } } class Glede : Animal { private String type = "Glede"; public override void showLegs() { Console.WriteLine("This is a {0} , Glede has two legs",type); } } class test { static void Main(string[] args) { Animal a1 = new Animal(); Animal a2 = new Dog(); Animal a3 = new Glede(); a1.showLegs(); a2.showLegs(); a3.showLegs(); Console.ReadLine(); } } 结果: This is an Animal , Animal always has legs This is a Dog , Dog has four legs This is a Glede , Glede has two legs 真正明白了这些,也就该接触设计模式领域了。 (1)·因为a2,a3都是子类的对象,所以调用起来,也都是调用的子类的方法(当然前提是它们都override了父类方法,诸位可以试着把两个子类中的override替换成new,结果就完全不一样,具体查看override与new的区别),有人把这叫做“关注对象原则”。 (2)·向上转型是一定没有错的,就好比你说“狗是动物”是成立的,反之,向下转型就要注意了,你不能说“动物是狗”。 四、父类引用指向子类对象的理解: (1)例如父类Animal,子类Cat,Dog。其中Animal可以是类也可以是接口,Cat和Dog是继承或实现Animal的子类。 Animal animal = new Cat(); 即声明的是父类,实际指向的是子类的一个对象。那这么使用的优点是什么,为什么要这么用? (2)可以用这几个关键词来概括:多态、动态链接,向上转型 也有人说这是面向接口编程,可以降低程序的耦合性,即调用者不必关心调用的是哪个对象,只需要针对接口编程就可以了,被调用者对于调用者是完全透明的。让你更关注父类能做什么,而不去关心子类是具体怎么做的,你可以随时替换一个子类,也就是随时替换一个具体实现,而不用修改其他. 以后结合设计模式(如工厂模式,代理模式)和反射机制可能有更深理解。 下面介绍java的多态性和其中的动态链接,向上转型: (3)面向对象的三个特征:封装、继承和多态; 封装隐藏了类的内部实现机制,可以在不影响使用者的前提下修改类的内部结构,同时保护了数据; 继承是为了重用父类代码,子类继承父类就拥有了父类的成员。 方法的重写、重载与动态连接构成多态性。Java之所以引入多态的概念,原因之一是它在类的继承问题上和C++不同,后者允许多继承,这确实给其带来的非常强大的功能,但是复杂的继承关系也给C++开发者带来了更大的麻烦,为了规避风险,Java只允许单继承,派生类与基类间有IS-A的关系(即“猫”is a “动物”)。这样做虽然保证了继承关系的简单明了,但是势必在功能上有很大的限制,所以,Java引入了多态性的概念以弥补这点的不足,此外,抽象类和接口也是解决单继承规定限制的重要手段。同时,多态也是面向对象编程的精髓所在。 (4)理解多态,首先要知道“向上转型”。 我定义了一个子类Cat,它继承了Animal类,那么后者就是前者是父类。我可以通过 那么这样做有什么意义呢?因为子类是对父类的一个改进和扩充,所以一般子类在功能上较父类更强大,属性较父类更独特, 定义一个父类类型的引用指向一个子类的对象既可以使用子类强大的功能,又可以抽取父类的共性。 所以,父类类型的引用可以调用父类中定义的所有属性和方法,而对于子类中定义而父类中没有的方法,父类引用是无法调用的; 当父类中的一个方法只有在父类中定义而在子类中没有重写的情况下,才可以被父类类型的引用调用; 对于父类中定义的方法,如果子类中重写了该方法,那么父类类型的引用将会调用子类中的这个方法,这就是动态连接。 下面看一下典型的多态例子: 1. class Father{ 2. public void func1(){ 3. func2(); 4. } 5. //这是父类中的func2()方法,因为下面的子类中重写(又称:覆写)了该方法 6. //所以在父类类型的引用中调用时,这个方法将不再有效 7. //取而代之的是将调用子类中重写的func2()方法 8. public void func2(){ 9. System.out.println("AAA"); 10. } 11. } 12. 13. class Child extends Father{ 14. //func1(int i)是对func1()方法的一个重载,主要不是重写! 15. //由于在父类中没有定义这个方法,所以它不能被父类类型的引用调用 16. //所以在下面的main方法中child.func1(68)是不对的 17. public void func1(int i){ 18. System.out.println("BBB"); 19. } 20. //func2()重写了父类Father中的func2()方法 21. //如果父类类型的引用中调用了func2()方法,那么必然是子类中重写的这个方法 22. public void func2(){ 23. System.out.println("CCC"); 24. } 25. } 26. 27. public class PolymorphismTest { 28. public static void main(String[] args) { 29. Father child = new Child(); 30. child.func1();//打印结果将会是什么? 31. child.func1(68); 32. } 33. } 上面的程序是个很典型的多态的例子。子类Child继承了父类Father,并重载了父类的func1()方法,重写了父类的func2()方法。重载后的func1(int i)和func1()不再是同一个方法,由于父类中没有func1(int i),那么,父类类型的引用child就不能调用func1(int i)方法。而子类重写了func2()方法,那么父类类型的引用child在调用该方法时将会调用子类中重写的func2()。 五、 对于多态,可以总结以下几点: (1)、使用父类类型的引用指向子类的对象; |
|