代码设计逻辑:
假设我们在玩一款游戏,玩家要操纵不同的军队来攻击敌人,还可以建造防御塔加强基地防御,军队有的不能移动,有的可以在陆地上跑,有的可以驾驶飞机,驾驶轮船等等。
可执行代码如下:
(例子仅供参考,具体设计要根据实际的业务逻辑来决定)
定义军队抽象类
/// <summary>
/// 军队抽象类
/// </summary>
public abstract class Force//默认不会移动
{
//军队都有名字,都会攻击敌人,侦察敌人,但不一定都会移动,例如防御塔
public string name { get; set; }//属性:姓名
public Force(string name)
{
this.name = name;
}
public abstract void Attack(Enemy ad);//军队都会攻击敌人,但攻击方式不同,所以定义抽象方法,让派生类自己实现
public void Scout(Enemy ad)//侦察敌人,假设每一种军队都用眼睛侦察,即派生类具有相同的行为,则无需让派生类单独实现,可以在抽象类中实现,减少代码冗余
{
Console.WriteLine($"{name}看到了{ad.name}");
}
}
然后定义移动接口
/// <summary>
/// 空中移动接口
/// </summary>
interface IAirMove
{
void AirMove();
}
/// <summary>
/// 陆地移动接口
/// </summary>
interface ILandMove
{
void LandMove();
}
/// <summary>
/// 海上移动接口
/// </summary>
interface ISeaMove
{
void SeaMove();
}
接着让派生类去继承军队抽象类,以及实现它们符合需求的接口
/// <summary>
/// 陆军类
/// </summary>
public class Army : Force, ILandMove
{
public Army(string name):base(name){}
public void LandMove()
{
Console.WriteLine($"{name}通过跑步在陆地上移动");
}
public override void Attack(Enemy ad)
{
Console.WriteLine($"{name}通过AK-47攻击{ad.name}");
}
}
/// <summary>
/// 空军类
/// </summary>
public class AirForce : Force, IAirMove
{
public AirForce(string name) : base(name) { }
public override void Attack(Enemy ad)
{
Console.WriteLine($"{name}通过飞机上的加特林攻击{ad.name}");
}
public void AirMove()
{
Console.WriteLine($"{name}驾驶直升机在天空中移动");
}
}
/// <summary>
/// 海军陆战队类
/// </summary>
public class SeaForce : Force, ISeaMove, ILandMove
{
public SeaForce(string name) : base(name) { }
public override void Attack(Enemy ad)
{
Console.WriteLine($"{name}通过两栖车上的导弹攻击{ad.name}");
}
public void LandMove()
{
Console.WriteLine($"{name}通过两栖车在陆地上移动");
}
public void SeaMove()
{
Console.WriteLine($"{name}通过两栖车在海洋中移动");
}
}
接着简单定义一下敌人类
public class Enemy
{
public string name;
public Enemy(string name)
{
this.name = name;
}
}
类已经定义完毕,该测试看看效果了
先定义一个封装操作的测试类,供调用者调用
class Test
{
//通过陆地移动军队
public static void MoveForceByLand(ILandMove lm)
{
lm.LandMove();
}
//通过天空移动军队
public static void MoveForceByAir(IAirMove am)
{
am.AirMove();
}
//通过海洋移动军队
public static void MoveForceBySea(ISeaMove sm)
{
sm.SeaMove();
}
//军队攻击敌人
public static void Combat(Force force, Enemy enemy)
{
force.Scout(enemy);
force.Attack(enemy);
}
}
调用者在Main函数中调用
static void Main(string[] args)
{
Army army1 = new Army("陆军1号");
Army army2 = new Army("陆军2号");
Army army3 = new Army("陆军3号");
AirForce air1 = new AirForce("空军1号");
AirForce air2 = new AirForce("空军2号");
SeaForce sea1 = new SeaForce("海军陆战队");
Enemy e1 = new Enemy("厄加特");
Test.MoveForceByLand(army1);
Test.MoveForceByLand(army2);
Test.MoveForceByLand(army3);
Test.MoveForceByAir(air1);
Test.MoveForceByAir(air2);
Test.MoveForceBySea(sea1);
Test.MoveForceByLand(sea1);
Test.Combat(army1, e1);
Test.Combat(air2, e1);
Test.Combat(sea1, e1);
Console.ReadKey();
}
运行结果显示:
代码分析:
从代码中,可以看出,用户(即调用者)在调用我们封装的代码时,只需知道对象能做什么,无需知道具体是怎么做的,在函数内部调用过程中,实现类隐式转换为接口或抽象类,即向上转型,无需转型操作符。实现类的实例总是包含接口或抽象类的全部成员,所以总是能成功转换接口或抽象类类型。通过向上转型实现多态,提高了程序的可扩展性。
对于接口和抽象类的区别,很多文章已经解释的非常清楚,作者这里叙述一下自己认为最重要的:
1.抽象类中的成员可以有实现,作用是减少代码冗余,提高代码的复用性,接口中不能有实现,所以抽象类是不完全抽象,接口是完全抽象。
2.派生类只能从一个抽象类中派生(C#和Java为单继承),但可以实现任意多个接口
至于其他的一些细节,相信聪明的你能够通过代码和以后的工作学习中慢慢体会的。
特别说明,设计模式的核心原则就是面向接口编程,面向抽象编程。理解抽象类和接口,对理解设计模式有极大的帮助。
一句话总结
掌握一个知识,就要能用一句话去概括。
接口和抽象类都是更高层次的抽象。抽象类是对事物本质的抽象,它约束了对象是什么?接口是对行为的抽象,它约束了对象能做什么?
使用时机:
当你关注对象的本质时,用抽象类,当你关注对象的行为时,用接口。(好吧,我承认不是一句了)