多态概念比较抽象,我的经验是,不要去试图理解文字概念,我也没见过能用文字说清楚的,直接用代码干就是,熟悉几个套路用法,自然而然就明白了。
一、C#中的多态
1.1 多态基本使用
C#中,和多态相关的特性很多,比如之前讲到的方法重载、虚方法重写、隐藏方法、抽象方法实现、接口实现,都可以归到多态范畴。但实操来说,我觉得应该把范围缩窄,多态就是下面这种情况:基类变量可持有派生类的实例对象并调用其方法;接口变量也可以持有实现类的实例对象并调用其方法。
//基类Animal-----------------------------------------
public class Animal
{
public virtual void MakeSound()
{
Console.WriteLine("Animal makes a sound.");
}
}
//派生类Cat------------------------------------------
public class Cat : Animal
{
public override void MakeSound() //重写基类方法
{
Console.WriteLine("Meow!");
}
}
//派生类Dog------------------------------------------
public class Dog : Animal
{
public override void MakeSound() //重写基类方法
{
Console.WriteLine("Woof! Woof!");
}
}
//省略入口方法----------------------------------------
//一般使用
Animal animal = new Animal();
animal.MakeSound(); // 输出: Animal makes a sound.
cat cat = new Cat();
cat.MakeSound(); // 输出: Meow!
Dog dog = new Dog();
dog.MakeSound(); // 输出: Woof! Woof!
//***多态使用,将子类对象赋给父类变量,并调用方法
Animal a1 = new Cat();
a1.MakeSound(); // 输出: Meow!
Animal a2 = new Dog();
a2.MakeSound(); // 输出: Woof! Woof!
1.2 多态究竟有什么用
前面的案例,不能说明多态究竟有啥用!前面有说,接口和实现类之间,也有多态特性,“接口变量也可以持有实现类的实例对象并调用其方法”。所以,下面以接口为例,直接上.NET中的依赖注入IOC。
//定义依赖注入的服务类和实现类,这里服务类是接口=======================================
//定义一个接口ITestService.cs
interface ITestService
{
void SayHi();
}
//定义第一个实现接口的类TestService1.cs,待会作为依赖注入的实现类,
public class TestService1 : ITestService
{
public void SayHi()
{
Console.WriteLine("Hi,我是Service-1");
}
}
//定义第二个实现接口的类TestService2.cs,待会作为依赖注入的实现类
public class TestService2 : ITestService
{
public void SayHi()
{
Console.WriteLine("Hi,我是Service-2");
}
}
//AspNetCoe中注册服务和调用服务======================================================
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<ITestService,TestService>();
var app = builder.Build();
app.MapGet("/", (ITestService testService) =>{var names = testService.SayHi;});
app.Run();
//builder.Services是由框架为我们提供的IOC容器
//调用AddScoped方法,注册了一个服务,服务类是ITestService,实现类是TestService1
//代码1:
builder.Services.AddScoped<ITestService,TestService1>();
//"(ITestService testService)",这段代码向IOC容器要ITestService
//IOC容器会自动帮我们做下面的事情:ITestService testService = new TestService1()
//代码2:
app.MapGet("/", (ITestService testService) =>{var names = testService.SayHi;});
//再解释一下它的意义:
/*
代码2,是实际的业务代码。我们会在项目的很多地方用到TestService1或者TestService2
如果不用依赖注入:
-假设我们在项目的100个地方用到,就需要new TestService1(),100次
-突然项目变更,现在要说“Hi,我是Service-2”,你需要在100个地方,改为new TestService2()
如果使用依赖注入:
-我们只需要修改一行代码,builder.Services.AddScoped<ITestService,TestService2>();
-把实现类由TestService1修改为TestService2即可
//不多说了,再说就是废话了!!!
二、TS中的多态
1.1 TS的类型兼容概念
JS是动态类型,没有多态概念,因为它“全多态”。TS为JS带来了强类型,所以也需要多态来增加灵活性,在TS中,称之为类型兼容。由于TS只是为JS添加的类型,本质还是一样,所以它的多态和C#很不一样。TS用的是结构化类型系统,也叫鸭子类型,类型检查的是对象的形状是否相同或相似,不要求类型之间是否有继承或者实现关系。结构化类型是标明类型的超集,上面讲的C#的多态,在TS中一样适用。至于TS的依赖注入,前端几乎没用过,但nest.js使用了依赖注入,我只了解了一下,实际没用过nest.js做后端,就不展开了。由于TS的类型兼容太过灵活了,实际开发中,如果大量用到类型兼容,很容易被搞疯。如果要用,我的建议是把范围缩小到和C#一样,仅用在有继承和实现关系的类型上。
1.2 TS特有的类型兼容
下例中,只讲C#中没有的类型兼容,了解一下即可,反正我的项目是严格要求不要这么用。
//1、class的类型兼容==================================================================
//兼容和多态是一个概念,在TS中,多使用兼容
//如果对象具有相同的形状,则属于同一类型---------------
class Point {x:nmuber; y:number}
class Point2D {x:nmuber; y:number}
const p:Point = new Point2D() //C#中是不可能的
//对象具有的形状,有多有少----------------------------
class Point {x:nmuber; y:number}
class Point3D {x:nmuber; y:number; z:number}
const p:Point = new Point3D() //对象多的,可以赋值给对象少的,可以将Point3D视为子类
//const p:Point3D = new Point() //报错,反过来,对象少的不能赋值给对象多的
//2、接口的类型兼容===================================================================
//和类的兼容性类似
interface Point {x:nmuber; y:number}
interface Point2D {x:nmuber; y:number}
let p1:Point
let p2:Point2D = p1
interface Point2D {x:nmuber; y:number; z:nmuber}
let p3:Point3D
p2 = p3
class Point3D {x:nmuber; y:number; z:nmuber}
let p3:Point2D = new Point3D() //class和interface之间也可以兼容
//3、函数的类型兼容===================================================================
//函数类型兼容要同时满足参数个数、类型和返回值的兼容
//最让人发疯的就是这个了
//①参数个数兼容,参数少的可以赋值给参数多的-------------------
type F1 = (a:number)=>void
type F2 = (a:number, b:number)=>void
let f1:F1
let f2:F2 =f1
//记住forEach()方法即可
const arr = [1,2,3,4]
arr.forEach(()=>{})
arr.forEach((item)=>{})
//②参数类型兼容----------------------------------------------
//如果参数是值类型,则要求类型要相同
//如果参数是对象类型,则将对象的属性拆开,然后按值类型和参数个数来检测
//如果是有嵌套的复杂类型呢,按以上规则来拆,已疯!!!
//不举例了
//③参数返回值兼容-------------------------------------------
//如果返回值是值类型,则要求类型要相同
//如果返回值是对象类型,则兼容性同class/interface的兼容
//不举例了
//关于函数兼容总结:知道forEach方法是如何定义及使用的就可以了