第壹章第11节 C#和TS语言对比-继承

35 阅读4分钟

C#中的继承很简单,但延伸到多态时,学习曲线会比较陡,而多态又是C#中非常重要的特性。TS的继承,底层通过原型链来实现,但都已经封装好,我们直接操作类,和C#差不多。而TS的多态,是鸭子类型兼容,很不习惯,好在前端开发应该极少会涉及,了解即可。

一、C#中的继承

1.1 基本使用

//1、派生类(子类)通过运算符:继承基类(父类)========================================
//基类(父类)
public class Animal
{
    public void Eat()
    {
        Console.WriteLine("Animal is eating.");
    }
}

//派生类(子类)
public class Dog : Animal
{
    public void Bark()
    {
        Console.WriteLine("Dog is barking.");
    }
}

//下例省略程序入口方法
Dog dog = new Dog();
dog.Eat(); // Eat()方法属于基类,但派生类继承了



//2、访问修饰符对继承的影响==========================================================
//private修饰的成员,不能被继承,只能在当前类访问
//protect修饰的成员,可以被继承,只能在当前类及其派生类访问
//internal修饰的成员,可以被继承,在当前程序集内公开
//public修饰的成员,可以被继承,完全公开



//3、有几个成员是不能继承的,除此之外的成员都可以继承=================================
//①静态构造函数(主要用于初始化静态数据)
//②实例构造函数
//③终结器(从来没用过)


//4、单继承=========================================================================
//C#是单继承,只能有一个父类,但可以实现多个接口


//5、隐式继承Object=================================================================
//Object是所有类的基类,所有类都可以调用Object上的成员
//如果定义类时没有显式定义基类,则它隐式继承Object,即class MyClass:Object{}

1.2 调用基类构造函数

//构造函数不能继承,但可以访问
//如果基类使用了构造函数进行初始化,则派生类需要调用基类构造函数并传入参数
//除此之外,派生类的方法中,可以通过base关键词访问基类成员
//this代表当前类的实例,base代表基类的实例
//基类------------------------------
public class Animal
{
    public string Name{get;set}

    public Animal(string name)
    {
        Name = name; 
    }

    public void Eat()
    {
        Console.WriteLine("Animal is eating.");
    }
  
}
//派生类------------------------------
public class Dog : Animal
{
    public int Age{get;set}
  
    public Dog(int Age, string name) : base(name) //调用基类构造函数,并传入name
    {
        Age = age; //Age在Dog中,Name在Animal中
    }

    public void DogEat()
    {
        base.Eat(); //访问基类的实例方法
    }
}

1.3 隐藏方法和虚方法

  • 隐藏方法:当在派生类中定义一个与基类中的方法具有相同名称和签名的方法时,派生类方法会隐藏基类方法
  • 虚方法:基类中声明的方法,可以在派生类中被重写。使用 virtual 声明的方法默认是虚方法,允许重写
  • 虚方法的具体应用,在《多态》章节说
//基类---------------------------------------------
public class Animal
{
    public void MakeSound()
    {
        Console.WriteLine("Animal makes a sound.");
    }
    //虚方法,使用virtual关键词
    public virtual void Sleep()
    {
        Console.WriteLine("Animal sleeps.");
    }
}
//派生类----------------------------------------------
public class Dog : Animal
{
    //new关键词,隐藏基类中的方法
    public new void MakeSound()
    {
        Console.WriteLine("Dog barks.");
    }

    //基类使用virtual,派生类使用override,重写基类中的虚方法
    //只有virtual方法,才可以被override
    //virtual和override,也可以用于修饰属性、事件、索引器等成员
    public override void Sleep()
    {
        Console.WriteLine("Dog sleeps.");
    }
}
//目前,看不出隐藏方法和虚方法的区别,以及有啥用
//只有到下节多态时,才能领会两者区别,以及虚方法的强大
Animal animal = new Animal();
animal.MakeSound(); // 输出: Animal makes a sound.
animal.Sleep();     // 输出: Animal sleeps.

Dog dog = new Dog();
dog.MakeSound();    // 输出: Dog barks. (隐藏的方法)
dog.Sleep();        // 输出: Dog sleeps. (虚方法重写)

1.4 抽象、接口、隐藏和虚方法傻傻分不清楚

如果刚学习C#,这几个概念是傻傻分不清的,也不容易讲得清,我从实际经验出发简单概括一下:

  • 隐藏方法,知道一下就可以了,极少使用,也尽量避免使用
  • 虚方法将在依赖注入IOC中大量使用,准确说它属于多态特性,一定要掌握
  • 抽象和接口都用于约束行为,但抽象用于约束其派生类,是在继承链条上的;而接口用于功能组合,没有继承要求,可以多实现,任何类都可以实现任何接口。实际开发中,接口大量用到,多态中也会大量使用到它,而抽象类较少用,你甚至可以完全不用

二、TS中的继承

2.1 基本使用(和C#基本一样)

//使用extends关键词继承==============================================================
class Animal {
    move(distance: number = 0) {
        console.log(`moved ${distance} meters.`);
    }
}
class Dog extends Animal {
    bark() {
        console.log('Woof! Woof!');
    }
}

const dog = new Dog();
dog.move(10); //继承的方法
dog.bark();   //自己的方法


//其它知识点=========================================================================
/*
  1)private,不能被继承,只能在当前类访问;
  2)protect,可以被继承,只能在当前类及其派生类访问
  3)public,可以被继承,完全公共
  4)单继承,只能有一个父类,但可以实现多个接口
  5)构造函数不能被继承
  6)隐式派生自Object
  7)接口是TS新增的特性,JS中只有类、没有接口
*/

2.2 调用基类构造函数(和C#基本一样)

class Animal {
    constructor(public name: string) {}

    move(distance: number = 0) {
        console.log(`${this.name} moved ${distance} meters.`);
    }
}

class Dog extends Animal {
    constructor(public age:number, name: string) { //使用了初始化的简写方式
        super(name); //使用super关键词调用基类构造函数,并传入参数name
                     //比C#的:base()舒服很多
    }
}

const dog = new Dog('Buddy');
dog.move(5); 

2.3 方法重写

//不同于C#中的虚方法,在TS中,派生类可以通过定义相同的方法直接重写基类方法
//基类不要使用virtual,派生类的override可写可不写,但建议显式申明。在UTS中,必须显式使用。
class Animal {
    speak() {
        console.log('Animal makes a sound.');
    }
}

class Dog extends Animal {
    //重写父类方法speak,方法名称、参数和返回值必须一致
    //override可写可不写,但UTS中,overried必须写
    override speak() { 
        console.log('Dog barks.');
    }
}

const dog = new Dog();
dog.speak(); // 输出: Dog barks.