又一次深入的学习设计模式,发现了很多以前感觉不是问题的问题,这才发现原来自己不是真的理解了。通过这次的深入学习,才开始慢慢感受到OO的魅力所在。
从C#学习到设计模式,再到机房收费系统个人版和合作版,再到我们做的项目,我们真正的朝着面向对象编程了吗?我的项目中,先不说泛型、委托、集合的利用率,就是基本的继承、多态用的少之又少。
下面将为大家解说“OO引领编程”之——继承和多态
继承篇
一、简介
俗话说:龙生龙凤生凤,老鼠的儿子会打洞。可以理解为,继承就是小辈拥有父辈流传下来的东西。
在编程的世界里也一样,后一代继承前一代的非私有功能并有自己新的发展。如果没有发展,小辈和父辈就是一样的,没有什么新的发展,社会岂不是要停滞不前。即子类继承父类的所有特性,同时还定义新的特性。
二、实例
1、基本实例
<span style="font-size:18px;">using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace JiCheng { class Program { static void Main(string[] args) { QueryStudentOnLineInfo studentA=new QueryStudentOnLineInfo(); //调用父类的查询学生上机信息 studentA.QueryStudentOnLine(); //调用子类的查询学生余额 studentA.QueryStudentBalance(); } } //父类拥有查询学生上机信息 class QueryStudentInfo{ public void QueryStudentOnLine(){ Console.WriteLine("查询学生是否正在上机"); } } //子类即拥有父类的方法,也拥有自己的查询学生余额方法 class QueryStudentOnLineInfo: QueryStudentInfo{ public void QueryStudentBalance(){ Console.WriteLine("查询学生余额"); } } } </span>
注意:
继承中子类中隐藏了实现父类的部分,如
//子类即拥有父类的方法,也拥有自己的查询学生余额方法 class QueryStudentOnLineInfo: QueryStudentInfo{ public void QueryStudentOnLine(){ Base.QueryStudentOnLine(); } public void QueryStudentBalance(){ Console.WriteLine("查询学生余额"); } }
2、Base关键字的引入
Base关键字用于从派生类中访问基类成员,并且可以使用Base关键字调用基类的构造函数。下面看它的使用方法。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace JiChengBase { class Program { static void Main(string[] args) { QueryStudentOnLineInfo studentA = new QueryStudentOnLineInfo(7); } } //父类定义构造函数 class QueryStudentInfo { protected int id; //定义卡号 public QueryStudentInfo(int id) { this.id = id; Console.WriteLine("我的卡号为{0}",id); } } //调用基类的构造函数 class QueryStudentOnLineInfo : QueryStudentInfo { public QueryStudentOnLineInfo(int id):base(id){} } }
三、特点
(1)子类拥有父类非private得属性和功能;
(2)子类具有自己的属性和功能,即子类可以扩展父类没有的属性和功能;
(3)子类还可以以自己的方式实现父类的功能(即方法重写)
四、优缺点
优点:继承机制允许和鼓励类的重用,派生类既具有自己新定义的属性和行为,又具有继承下来的属性和行为。
缺点:
破坏封装,子类和父类间紧密耦合
增加系统复杂度
五、小结
继承的应用很普遍也很常用,但使用时一定要判断是否二者符合继承关系,即is-a关系。继承是一种强关系,复杂的继承会导致难以复用,所以要参考结合“合成聚合原则”使用。
多态篇
一、简介
同一操作作用于不同的对象,产生不同的结果,这就是多态。
二、实例
1、编译时多态
编译时多态就是我们所谓的重,包含构造函数重载和方法重载
2、运行时多态
(1)virtual-override实现多态
<span style="font-size:18px;">using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace JiCheng { class Program { static void Main(string[] args) { QueryStudentInfo studentA = new QueryStudentOnLineInfo(); //调用父类的查询学生上机信息 studentA.QueryStudentOnLine(); } } //父类拥有查询学生上机信息 public class QueryStudentInfo{ public virtual void QueryStudentOnLine(){ Console.WriteLine("查询学生是否正在上机"); } } //子类重写父类学生是否正在上机的信息 public class QueryStudentOnLineInfo: QueryStudentInfo{ public override void QueryStudentOnLine() { Console.WriteLine("查询学生是否已购卡"); } } } </span>
(2)abstract-override实现多态
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace JiCheng { class Program { static void Main(string[] args) { QueryStudentInfo studentA = new QueryStudentOnLineInfo(); //调用父类的查询学生上机信息 studentA.QueryStudentOnLine(); } } //父类拥有查询学生上机信息 public abstract class QueryStudentInfo{ public abstract void QueryStudentOnLine(); } //子类重写父类学生是否正在上机的信息 public class QueryStudentOnLineInfo: QueryStudentInfo{ public override void QueryStudentOnLine() { Console.WriteLine("查询学生是否已购卡"); } } }
区别
1、抽象方法只有声明没有实现代码,需要在子类中实现;虚方法有声明和实现代码,并且可以在子类中重写,也可以不重写使用父类的默认实现。
2、抽象类不能被实例化(不可以new),只能实例化实现了全部抽象方法的派生类;而包含虚方法的类可以实例化。
联系
抽象方法是虚拟方法两个相像的一点是都用override重写。
三、特点
1.子类以父类身份出现
2.子类工作时以父类身份出现
3.子类以父类身份出现时,子类特有属性不可使用
四、具备条件
1.继承关系的存在
2.方法的重写
3.父类的引用指向子类对象
五、优缺点
优点:提高了代码的扩展性。
不足:前期建立父类的引用虽然可以接收后期所有该类的子类对象。但是只能使用父类中的功能,不能使用子类中的特有功能,
六、小结
多态是面向对象的核心。继承是子类使用了父类的方法;多态是父类使用了子类的方法。继承实现了功能传递和延展;多态实现了单一行为的多种表示。充分利用好这两大关系,会使我们的编程更具弹性。
注意:
使用父类类型的引用指向子类的对象,该引用只能调用父类中定义的方法和变量;
变量不能被重写(覆盖),”重写“的概念只针对方法,如果在子类中”重写“了父类中的变量,那么在编译时会报错。
重写、重载、多态篇
一、重写和重载
1、重写(覆写):
(1)重写方法的参数列表和返回值必须与被重写方法一致
(2)重写方法的访问修饰符一定要大于被重写方法的访问修饰符(public>protected>default>private)。
(3)重写的方法所抛出的异常必须和被重写方法的所抛出的异常一致,或者是其子类;
(4)被重写的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行重写。
(5)静态方法不能被重写为非静态的方法(会编译出错)。
2、重载
(1)相同的方法名,不同的参数形式。不同的参数形式可以是不同的参数类型,不同的参数个数,不同的参数顺序(参数类型必须不一样);
(2)不能通过访问权限、返回类型、抛出的异常进行重载;
(3)方法的异常类型和数目不会对重载造成影响;
常见实例:构造函数
二、重载和多态
1、重载,是指允许存在多个同名方法,而这些方法的参数不同。重载的实现是:编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。
2、多态:是指子类重新定义父类的虚方法(virtual,abstract)。当子类重新定义了父类的虚方法后,父类根据赋给它的不同的子类,动态调用属于子类的该方法,这样的方法调用在编译期间是无法确定的。
区别在于编译器何时去寻找所要调用的具体方法,对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”;而对于多态,只有等到方法调用的那一刻,编译器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。
new和override篇
一、new
当派生类的方法使用new修饰时,子类对象的对象转换为父类对象后,调用的是父类中的QueryStudentOnLine()方法。可以理解为,使用new关键字后,使得子类中的QueryStudentOnLine()方法和父类中的QueryStudentOnLine()方法成为毫不相关的两个方法,只是它们的名字碰巧相同而已。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; ) namespace duotai { class Program { static void Main(string[] args) { //实例化父类对象 QueryStudentInfo studentA = new QueryStudentInfo(); //父类对象指向子类引用 QueryStudentInfo studentB = new QueryStudentOnLineInfo(); //实例化子类对象 QueryStudentOnLineInfo studentC = new QueryStudentOnLineInfo(); studentA.QueryStudentOnLine(); studentB.QueryStudentOnLine(); studentC.QueryStudentOnLine(); } } //父类拥有查询学生上机信息 public class QueryStudentInfo { public virtual void QueryStudentOnLine() { Console.WriteLine("查询学生是否正在上机"); } } //子类重写父类学生是否正在上机的信息 public class QueryStudentOnLineInfo : QueryStudentInfo { public new void QueryStudentOnLine() { Console.WriteLine("查询学生是否已购卡"); } } }
不能说通过使用new来实现多态,只能说在某些特定的时候碰巧实现了多态的效果。
二、override
如果子类中重写了父类中的一个方法,那么在调用这个方法的时候,将会调用子类中的这个方法;(动态连接、动态调用)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace duotai { class Program { static void Main(string[] args) { //实例化父类对象 QueryStudentInfo studentA = new QueryStudentInfo(); //父类对象指向子类引用 QueryStudentInfo studentB = new QueryStudentOnLineInfo(); //实例化子类对象 QueryStudentOnLineInfo studentC = new QueryStudentOnLineInfo(); studentA.QueryStudentOnLine(); studentB.QueryStudentOnLine(); studentC.QueryStudentOnLine(); } } //父类拥有查询学生上机信息 public class QueryStudentInfo { public virtual void QueryStudentOnLine() { Console.WriteLine("查询学生是否正在上机"); } } //子类重写父类学生是否正在上机的信息 public class QueryStudentOnLineInfo : QueryStudentInfo { public override void QueryStudentOnLine() { Console.WriteLine("查询学生是否已购卡"); } } }
小结:
使用new关键字,即使父类引用指向子类对象,最终调用的是父类方法(特殊情况下会用到)
而使用override,父类引用指向子类对象时,动态调用子类方法
接口、抽象类篇
一、比较
1、抽象类是对类的抽象;接口是对行为的抽象
2、行为跨越不同对象可使用接口;对于相似类对象使用抽象类。
3、抽象类是从子类中泛化出父类,然后子类继承父类;接口并不知道子类的存在。
二、实际运用
1、如果要设计大的功能单元,则使用抽象类.如果要在组件的所有实现间提供通用的已实现功能,则使用接口。
2、抽象类主要用于关系密切的对象;而接口适合为不相关的类提供通用功能。
父类引用指向子类
一、几种情况
namespace duotai{ class Program { public class Animal { public virtual void Eat() { Console.WriteLine("动物吃东西"); } } public class Dog : Animal { public override void Eat() { Console.WriteLine("狗吃东西"); } } static void Main(string[] args) { Animal A = new Animal(); Animal B = new Dog(); Dog C = new Dog(); A.Eat(); B.Eat(); C.Eat(); } } }
1、当父类引用B指向其子类的对象的时候( Animal B = new Dog()),通过B无法访问专属于子类对象的成员。
因为B是父类引用,所以编译器只知道B拥有的信息,Animal以外的信息,编译器不知道,而子类的对象成员是在Dog里,也就是说在父类以外,所以B无法访问子类的对象成员
2、假如子类中有对父类方法的重写,那么根据多态机制,通过A访问这个方法的时候实际访问的是子类中重写的方法。
编译器:父类对象只知道自己是父类类型,不知道具体指向什么对象
运行期(调用):父类引用指向的是子类对象,所以调用子类方法
3、如果子类重写的方法中访问了专属于子类的成员变量,这时候通过父类引用A还可以调用那个被重写的方法吗?
可以,要分清编译期和运行期,编译期是编译器检查语法和类型,运行期是解析器解析伪代码为机器指令而执行,编译期编译器会检查B的访问范围,也就是B的访问不超过父类的信息就不会出错,运行期解析器会解析方法的代码指令,因为B指向子类对象,所以会解析子类重写的方法代码指令,而子类对象的内存空间是包含子类的成员变量的空间的,所以也不存在子类成员变量没有分配内存的问题,所以可以调用。
二、优点
定义一个父类类型的引用指向一个子类的对象既可以使用子类强大的功能,又可以抽取父类的共性。所以,父类类型的引用可以调用父类中定义的所有属性和方法,而对于子类中定义而父类中没有的方法,它是无可奈何的;
注意:
父类中的一个方法只有在在父类中定义而在子类中没有重写的情况下,才可以被父类类型的引用调用;
总结:
面向对象博大精深,需要我们一步步去深入,去了解。多用多思考会发现我们了解到的也许只是冰山一角。博文只是代表了个人的一些理解,如有不足,请指出,互相交流进步!
面向对象(一)—继承与多态