(封装、继承、多态)是面向对象编程三大特性之一,继承的思想就是摈弃代码的冗余,实现更好的重用性。继承从字面上理解,无外乎让人想到某人继承某人的某些东西,一个给一个拿。这个语义在生活中,就像家族继承财产,爷爷将财产继承给儿女,儿女在将财产继承给子孙,有些东西可以继承有些的东西只继承给某人。映射到编程当中,其思想也大致如此。
继承允许我们根据一个类来定义另一个类,这使得创建和维护应用程序变得更容易。同时也有利于重用代码和节省开发时间。 当创建一个类时,程序员不需要完全重新编写新的数据成员和成员函数,只需要设计一个新的类,继承了已有的类的成员即可。这个已有的类被称为的基类,这个新的类被称为派生类。 继承的思想实现了 属于(IS-A) 关系。例如,哺乳动物 属于(IS-A) 动物,狗 属于(IS-A) 哺乳动物,因此狗 属于(IS-A) 动物。
一个类可以实现多个接口,和继承一个类。(C#不允许多继承)
C# 中创建派生类的语法如下:
<访问修饰符符> class <基类>
{
...
}
<访问修饰符符> class <派生类> : <基类>
{
...
}
假设,有一个基类 Shape,它的派生类是 Rectangle:
namespace Malema.net
{
public class Shape
{
public int Width { get; set; }
public int Height { get; set; }
///重写了 object的ToString方法
public override string ToString()
{
return $"Width: {Width}, Height:{Height}";
}
}
// 派生类
public class Rectangle : Shape
{
public int GetArea()
{
return (Width * Height);
}
}
class Program
{
static async Task Main(string[] args)
{
Rectangle rect = new Rectangle();
rect.Width = 5;
rect.Height = 6;
// 打印对象的面积
Console.WriteLine("总面积: {0}", rect.GetArea()); //输出总面积30
// 调用了 ToString方法
Console.WriteLine(rect); // Width: 5, Height:6
Console.ReadKey();
}
}
}
namespace Malema.net
{
public class Shape
{
public Shape(int width, int height)
{
this.Width = width;
this.Height = height;
}
public int Width { get; set; }
public int Height { get; set; }
public override string ToString()
{
return $"Width: {Width}, Height:{Height}";
}
//定义了一个可被重写的方法
public virtual string Display()
{
return "I am Shape";
}
}
// 派生类
public class Rectangle : Shape
{
public string Name { get; set; }
public Rectangle(int width, int height, string name) :
base(width, height)
{
this.Name = name;
}
public int GetArea()
{
return (Width * Height);
}
// 重写了基类的方法
public override string Display()
{
var temp = base.Display(); //复类里面也可以调用子类的方法。
return "I am Rectangle";
}
}
class Program
{
static async Task Main(string[] args)
{
Rectangle rect = new Rectangle(5, 6, "name1");
// 打印对象的面积
Console.WriteLine("总面积: {0}", rect.GetArea()); //
Console.WriteLine(rect); // Width: 5, Height:6
Console.WriteLine(rect.Display()); // I am Rectangle
Console.ReadKey();
}
}
}
C# 不能继承于多个类,但是可以实现多个接口。意味着,这个类实现了多种接口约定。 如一只鸭子。 可以飞也可以游. 也可以吃东西。
如下:
namespace Malema.net
{
public class Animal
{
public void Eat()
{
Console.WriteLine("我会吃");
}
}
public interface IFly
{
void Fly();
}
public interface ISwim
{
void Swim();
}
public class Duck : Animal, IFly, ISwim
{
public void Fly()
{
Console.WriteLine("我开始飞了");
}
public void Swim()
{
Console.WriteLine("我开始游了");
}
}
class Program
{
static async Task Main(string[] args)
{
var duck = new Duck();
duck.Eat();
LetUsEat(duck);
LetUsFly(duck);// 这些不是直接调用duck.Fly(). 因为如果这样调的话,IFly接口的作用就体现不出来了
LetUsSwim(duck);
}
public static void LetUsEat(Animal animal)
{
animal.Eat();
}
//有实现了IFly的我们都可以用这个方法来处理它了
// 实际代码中这个方法正常也会被放到其它类当中
public static void LetUsFly(IFly fly)
{
fly.Fly();
}
//有实现了ISwim的我们都可以用这个方法来处理它了。
public static void LetUsSwim(ISwim swim)
{
swim.Swim();
}
}
}
输出了
我会吃
我会吃
我开始飞了
我开始游了
为什么不弄一个大的接口含有两个方法呢。因为设计的时候偏向于越小越好。 因为并不是所有的动物都能飞的。(因为并不是所有的类都有办法满足一个大的接口).
比如上面的代码。吃这个东西本质上是属于嘴巴的行为。 而嘴巴可以是鸭子的一部分。 其它动物如果需要的话也可以放入这个嘴巴。这样这个Eat方法实际就是由Month来决定了,而不是各个类自己来决定。 如下代码
namespace Malema.net
{
//嘴巴
public class Month
{
public void Eat()
{
Console.WriteLine("我是嘴巴我开始吃了");
}
}
public class Duck
{
public Month Month
{
get;
set;
}
public void Eat()
{
Month.Eat();
}
}
class Program
{
static async Task Main(string[] args)
{
var duck = new Duck();
duck.Eat(); //我是嘴巴我开始吃了
}
}
}
继承的一个坏处继承层次多的时候。如果你想改动底层的一个方法的时候。你得考虑子类的依赖性。