重温面向对象核心 上

实例解读面向对象核心,所有例子基于 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、从设计角度看,抽象类是从子类中发现公共的东西,泛化出父类,而接口根本不知道子类的存在,方法如何实现还不确认,预先定义。

抽象类往往都是通过重构得来的,抽象类是自底向上抽象出来的,而接口是自顶向下设计出来的。

祝学习进步 :)

(0)

相关推荐