重温面向对象核心 上
实例解读面向对象核心,所有例子基于 c#,涉及我们实务中最常关心的问题:
1、封装、继承、多态;
2、抽象类、接口;
3、委托、事件。
一、面向对象三大特性:封装、继承、多态
每个对象都包含它能进行操作的所有信息(不必依赖其他对象),这个特性称为封装。
封装降低了耦合,类内部的实现可以*的修改,使类具有清晰的对外接口。
对象的继承代表了一种“一般到特殊”的关系,例如 学生(student)是一种人类(person)。
继承定义了类如何相互关联,共享特性。
继承的工作方式是,定义父类和子类,或叫做基类与派生类,上面的例子中,可以让 student(子类)继承 person(父类)。
多态:表示不同的对象可以执行相同的动作,但要通过它们自己的实现代码执行。
这个文字理解比较抽象,具体说明见下面示例中讲解:
例子分为 父类,子类,调用 三部分。
先建立个mvc项目oopdemo,我们定义一个父类:抽象的图形类(shape), 子类 :矩形类 (rectangle),在 homecontroller 中 index 方法中调用。
父类:
定义一个属性name 和方法 getname, 将该成员声明为虚拟的(virtual, 有方法体),除了字段不能是虚拟的,属性、事件和索引器都可以是虚拟的,通常虚拟的是方法, 子类通过 override来覆写。
public class shape { public string name { get; set; } public shape(string name) { this.name = name; } public virtual string getname() { return "父类的图形名: "+ name; } }
子类继承父类:
public class rectangle:shape { public rectangle(string name):base(name) { } public override string getname() { return "子类的图形名: " + name; } public double length { get; set; } public double width { get; set; } public double getarea() { double area = length * width; return area; } }
子类中关于继承的说明:
1、子类拥有父类非private的属性和功能
子类拥有父类的 属性name,getname()方法。
* 构造方法有一些特殊,它不能被继承,只能被调用(使用 base)。
public rectangle(string name):base(name)
{ }
2、子类具有自己的属性和功能,即子类可以扩展父类没有的属性和功能。
矩形有自己的 长、宽属性,及计算面积的 getarea 方法。
public double length { get; set; } public double width { get; set; } public double getarea() { double area = length * width; return area; }
3、子类可以以自己的方式实现父类的功能(方法重写)
通过override重写
public override string getname() { return "子类的图形名: " + name; }
调用
在homecontroller的index方法中获取名字。
public iactionresult index() { shape rec1 = new rectangle("正方形"); viewbag.name = rec1.getname(); return view(); }
前端通过viewbag获取name
调用时关于多态的说明
1、子类以父类身份出现
注意是以 shape(父类) 而不是 rectangle(子类) 来声明的,然后用 rectangle(子类)来实例化。(对象的声明是父类,而不是子类,实例化的对象是子类)
shape rec1 = new rectangle("正方形");
2、子类在工作时以自己的方式来实现(覆写父类方法)
public override string getname() { return "子类的图形名: " + name; }
当方法被调用时,都只有位于对象继承链最末端的方法会被调用。也就是说,虚方法是按照运行时类型而非编译时类型进行动态绑定调用的。
3、子类以父类身份出现时,子类特有的属性和方法不可使用
这些都是不能用的:
public double length { get; set; } public double width { get; set; } public double getarea() { double area = length * width; return area; }
例如 rec1.getarea() ,这个是获取不到的。
二、抽象类与接口
抽象类
回顾下我们的例子。
shape实际上不会实例化,它只是抽象出一些共同的东西用来继承。
抽象类通常代表一个抽象概念,它提供一个继承的出发点,当设计一个新的抽象类时,一定是用来继承的,所以,在一个以继承关系形成的等级结构里面,树叶节点应当是具体类,而树枝节点应该是抽象类。也就是说,具体类不是用来继承的。
考虑把没有任何意义的父类改成抽象类,让抽象类拥有尽可能多的共同代码,拥有尽可能少的数据。
我们来修改一下例子。
将父类做如下方框处更改,其他的都不变。
我们将shape改成了抽象类, public abstract class shape {…}
将getname删除了方法体,改成了抽象方法 public abstract string getname();
说明:
1、抽象类不能实例化
2、抽象方法是必须被子类重写的方法(可以看成是没有实现体的虚方法)
3、如果类中包含抽象方法,那么类就必须被定义为抽象类,不论是否还包含其他一般方法。
接口
声明接口的语法与声明抽象类完全相同,但不允许提供接口中任何成员的执行方法。
实现接口的类就必须实现接口中所有的方法和属性。
我们来定义一个接口:
/// <summary>
/// 定义各种各样的面积算法
/// </summary>
public interface ical
{
string getareaalgorithm();
}
在rectangle中继承这个接口,并实现接口的方法
public class rectangle:shape, ical { // 此处省略其他代码xx行... // 实现接口 ical 中的方法 public string getareaalgorithm() { return "矩形的面积算法:长 × 宽"; } }
修改调用方法:
public iactionresult index() { shape rec1 = new rectangle("正方形"); viewbag.name = rec1.getname(); ical cal= new rectangle("正方形2"); viewbag.cal = cal.getareaalgorithm(); return view(); }
显示结果:
三、总结:
1、类是对对象的抽象;抽象类是对类的抽象;接口是对行为的抽象
2、如果行为跨越不同对象,可使用接口
3、从设计角度看,抽象类是从子类中发现公共的东西,泛化出父类,而接口根本不知道子类的存在,方法如何实现还不确认,预先定义。
抽象类往往都是通过重构得来的,抽象类是自底向上抽象出来的,而接口是自顶向下设计出来的。
祝学习进步 :)