最近在看完刘铁猛老师讲解的类,抽象类,接口后,有一种豁然开朗的感觉,现将内容记录下来,以便后续的学习巩固。
先提一下程序设计的六大模式之一的开闭原则(open closed principle):用抽象构建架构,用实现扩展原则;(总纲)
设计模式文章的连接如下:(https://blog.csdn.net/luguojiu/article/details/102551573)
在后面的代码重构中会体现这一原则。
一天,小菜与大鸟在一起。
大鸟:小菜,现在有一个项目要交给你。项目要求写一个小汽车,这个汽车具有Run,以及Stop方法,
小菜:很简单呀,看我的。
几分钟后,小菜写出了代码,如下所示:
namespace Example_03
{
class Program
{
static void Main(string[] args)
{
}
}
class Car
{
public void Run()
{
Console.WriteLine("Car is running");
}
public void Stop()
{
Console.WriteLine("Stopped !");
}
}
}
大鸟:嗯,不错,是这样的。那现在项目中要增加卡车了,和小汽车具有相同的方法。
小菜:简单,我粘贴复制,修改一下就可以了。
很快,代码如下
namespace Example_03
{
class Program
{
static void Main(string[] args)
{
}
}
class Car
{
public void Run()
{
Console.WriteLine("Car is running");
}
public void Stop()
{
Console.WriteLine("Stopped !");
}
}
class Truck
{
public void Run()
{
Console.WriteLine("Truck is running");
}
public void Stop()
{
Console.WriteLine("Stopped !");
}
}
}
大鸟:你这样写是没问题的,但是,在代码编写的过程中,尽量不要出现代码的粘贴复制现象。同时,你看看,Car类和Truck类中的方法都是一样的,这样写是不是重复了,还有就是,如果我需要添加更多的车的时候,是不是还需要添加更多的相同代码,这样是比较麻烦的。当多个类中相同代码较多的时候,可以考虑将相同代码放在父类当中。
小菜:明白了。
几分钟后:
namespace Example_03
{
class Program
{
static void Main(string[] args)
{
//客户端代码(体现了多态)
Vechile vh1 = new Car();
vh1.Run("Car");
vh1.Stop();
Vechile vh2 = new Truck();
vh2.Run("Truck");
vh2.Stop();
Console.ReadLine();
}
}
class Vechile
{
public void Run(string type)
{
if (type=="Car")
{
Console.WriteLine("Car is running");
}
else if (type == "Truck")
{
Console.WriteLine("Truck is running");
}
}
public void Stop()
{
Console.WriteLine("Stopped !");
}
}
class Car:Vechile
{
}
class Truck:Vechile
{
}
运行结果:
大鸟:不错。我看到你在基类中改变了Run的方法,以前是不带参数的。
小菜:没办法啊,Car 和Truck的Run方法中的方法体是有区别的。
大鸟:嗯,你这样处理也是可以的。那如果我让你添加一个RaceCar呢?
小菜:我只需要在基类中的Run方法里添加一个else if 的分支,先后写一个RaceCar子类就行了。看我的,
一分钟后,小菜的代码如下:
namespace Example_03
{
class Program
{
static void Main(string[] args)
{
//客户端代码(体现了多态)
Vechile vh1 = new Car();
vh1.Run("Car");
vh1.Stop();
Vechile vh2 = new Truck();
vh2.Run("Truck");
vh2.Stop();
Console.ReadLine();
}
}
class Vechile
{
public void Run(string type)
{
if (type=="Car")
{
Console.WriteLine("Car is running");
}
else if (type == "Truck")
{
Console.WriteLine("Truck is running");
}
else if (type=="RaceCar")
{
Console.WriteLine("RaceCar is running");
}
}
public void Stop()
{
Console.WriteLine("Stopped !");
}
}
class Car:Vechile
{
}
class Truck:Vechile
{
}
class RaceCar : Vechile
{
}
}
大鸟:嗯,这样写是可以的。但是,小菜啊,你知不知道你违反了一个程序设计中的一个很重要的原则呀。我先不告诉你,先来说一下存在的问题。按照你的这种写法,如果后续需要添加别的车的时候,你是不是需要频繁的去修改基类Vechile中的代码?
小菜:这个确实是的呀,但是那也没办法啊,有需求了就要改的。如果我想增加属性的话,我不也得去修改这个类啊?
大鸟:没错,如果你需要增加类的属性,确实是需要修改这个类,这个是必然的,同时,如果你需要给类添加新的功能,比如说,车还有加油的功能,那你需要在Vechile中添加Fill函数,这也是需要修改类的,这个也是必然,也是允许的。也就是说,你需要增加类的新功能的时候,修改类是正确的,除此之外,最好不要总去动类中的代码,比如说,增加一种类型的车,你就去修改Run中的方法,这个是不好的。
小菜:我似乎有些明白了。
大鸟:这么说吧,在一个团队了,你负责设计了这个类,然后团队中的另外一个成员需要继承你的这个基类,那么他还得跑到你那边去修改基类的代码,这显然不是好的体验。好的体验应该是,我直接继承,其他的我无需关心。
小菜:懂了。
大鸟:很好,这就是我今天要和你说的设计模式中的开闭原则。你刚刚的代码就是违反了这个设计原则啊。
小菜:那我该如何进行修改呢?
大鸟:首先啊,在设计Run方法时,我们可以考虑将基类的Run方法与子类中的方法具有相同的签名,。。。。
小菜:对,对,然后我将方法设计成虚方法,在子类中进行重写不就可以了嘛;
大鸟:是滴
几分钟后:
小菜改完了代码,如下:
namespace Example_03
{
class Program
{
static void Main(string[] args)
{
//客户端代码(体现了多态)
Vechile vh1 = new Car();
vh1.Run();
vh1.Stop();
Vechile vh2 = new Truck();
vh2.Run();
vh2.Stop();
Console.ReadLine();
}
}
class Vechile
{
public virtual void Run()
{
Console.WriteLine("Vechile is running");
}
public void Stop()
{
Console.WriteLine("Stopped !");
}
}
class Car:Vechile
{
public override void Run()
{
Console.WriteLine("Car is running");
}
}
class Truck:Vechile
{
public override void Run()
{
Console.WriteLine("Truck is running");
}
}
}
运行结果:
大鸟:很好,运行结果与前面一样。在基类中设计虚方法,在子类中进行重写(override)。小菜,你发现没,基类中的虚方法,在实际运行中,一直不会执行到的。
小菜:确实是这样的哈,
大鸟:那这样的话,我们还要他的函数体干啥啊,不如就不要函数实现了啊
小菜:不要函数体了,也就是说函数没有实现,那这个函数就太抽象了。
大鸟:对啊,是很抽象的。这就成了抽象方法了,类也就变成抽象类了。。
小菜:我明白了,我这就去重构代码:
几分钟后:
namespace Example_03
{
class Program
{
static void Main(string[] args)
{
//客户端代码(体现了多态)
Vechile vh1 = new Car();
vh1.Run();
vh1.Stop();
Vechile vh2 = new Truck();
vh2.Run();
vh2.Stop();
Console.ReadLine();
}
}
abstract class Vechile
{
public abstract void Run();
public void Stop()
{
Console.WriteLine("Stopped !");
}
}
class Car : Vechile
{
public override void Run()
{
Console.WriteLine("Car is running");
}
}
class Truck:Vechile
{
public override void Run()
{
Console.WriteLine("Truck is running");
}
}
}
大鸟:嗯,不错,是这样的。从上面 可以看出,只要类包含了抽象方法,那么这个类就是抽象类。当然了,抽象类中还可以包含非抽象方法的。
小菜:嗯,嗯。那如果我把所有的方法都写成抽象函数呢?
大鸟:你可以试一试啊。
小菜:好的。
几分钟后;
namespace Example_03
{
class Program
{
static void Main(string[] args)
{
//客户端代码(体现了多态)
Vechile vh1 = new Car();
vh1.Run();
vh1.Stop();
Vechile vh2 = new Truck();
vh2.Run();
vh2.Stop();
Console.ReadLine();
}
}
abstract class Vechile
{
public abstract void Run();
public abstract void Stop();
}
class Car : Vechile
{
public override void Run()
{
Console.WriteLine("Car is running");
}
public override void Stop()
{
Console.WriteLine(" Car Stopped !");
}
}
class Truck:Vechile
{
public override void Run()
{
Console.WriteLine("Truck is running");
}
public override void Stop()
{
Console.WriteLine(" Truck Stopped !");
}
}
}
大鸟:对的,是这样的。在之类中,基类中的全部的抽象函数在子类中必须全部实现,一个都不能少。
小菜:那如果我不小心少实了怎么办?
大鸟:这样的话,继承的这个类依然是抽象类。也就是说,一**个类没有完全实现其基类中的抽象方法的话,这个类也是抽象类。**下面,我在你的代码基础上进行修改一下,你看看。
几分钟后,大鸟的代码写好了,看下面:
namespace Example_03
{
class Program
{
static void Main(string[] args)
{
//客户端代码(体现了多态)
Vechile vh1 = new Car();
vh1.Run();
vh1.Stop();
Vechile vh2 = new Truck();
vh2.Run();
vh2.Stop();
Console.ReadLine();
}
}
abstract class VechileBase
{
public abstract void Run();
}
abstract class Vechile:VechileBase
{
public abstract void Stop();
}
class Car : Vechile
{
public override void Run()
{
Console.WriteLine("Car is running");
}
public override void Stop()
{
Console.WriteLine(" Car Stopped !");
}
}
class Truck:Vechile
{
public override void Run()
{
Console.WriteLine("Truck is running");
}
public override void Stop()
{
Console.WriteLine(" Truck Stopped !");
}
}
}
大鸟:看到了吧,VechileBase是Vechile的 基类,Vechile没有实现VechileBase中的Run方法,因此,Vechile依然是抽象类。但是,最终这些抽象方法都是需要在之类中实现的。
小菜:明白了。
大鸟:小菜啊,你看看,在前面你把Vechile这个抽象类中的方法都设计成了抽象方法,这样固然是可以的,但是在实际应用中,我们是不这样做的。这个类的方法全部是抽象的,因此这个类的抽象程度是很高的,这样的话,我们一般用接口。下面我来重构一下代码:
namespace Example_03
{
class Program
{
static void Main(string[] args)
{
//客户端代码(体现了多态)
IVechile vh1 = new Car();
vh1.Run();
vh1.Stop();
IVechile vh2 = new Truck();
vh2.Run();
vh2.Stop();
Console.ReadLine();
}
}
//abstract class VechileBase
//{
// public abstract void Run();
//}
interface IVechile
{
void Stop();
void Run();
}
class Car : IVechile
{
public void Run()
{
Console.WriteLine("Car is running");
}
public void Stop()
{
Console.WriteLine(" Car Stopped !");
}
}
class Truck : IVechile
{
public void Run()
{
Console.WriteLine("Truck is running");
}
public void Stop()
{
Console.WriteLine(" Truck Stopped !");
}
}
}
大鸟:在接口里,方法也是没有任何实现的。只是定义,没有任何实现。接口只管有什么(what),具体的怎么做(how),接口是不管的。接口中只能包含方法,属性,索引器等抽象的东西。
小菜:嗯,嗯,终于明白啦。
大鸟:整个的实现过程就是这样的,小菜啊,你一定要理解这些代码在设计之初到最终的重构过程。只有真正理解了,才能在以后的设计过程过程中灵活的应用。