抽象类:
提到接口前一定要先知道什么抽象类(abstract),一个类里面一旦有了 abstract 成员,类就变成了抽象类,就必须标 abstract。abstract 成员即暂未实现的成员,因为它必须在子类中被实现,所以抽象类不能是 private 的。抽象类不能用于实例化对象,而只能用于被继承,因此抽象类一般有两个作用:
// 特别抽象
abstract class VehicleBase
{
public abstract void Stop();
public abstract void Fill();
public abstract void Run();
}
// 抽象
abstract class Vehicle:VehicleBase
{
public override void Stop()
{
Console.WriteLine("Stopped!");
}
public override void Fill()
{
Console.WriteLine("Pay and fill...");
}
}
// 具体
class Car : Vehicle
{
public override void Run()
{
Console.WriteLine("Car is running...");
}
}
抽象类为其子类定义了必须要实现的成员,通过子类对成员的重写又使各成员间具体实现方法上可以保留自身的差异。
接口:
接口(interface)是对抽象类的进一步抽象,把类内所有的成员都“掏空”了,只留下了成员的声明,通过接口我们可以定义类的原型,而不用去考虑具体的实现。与抽象类不同之处在于,C#中子类只能继承一个父类,但却可以实现多个接口。
interface VehicleBase
{
void Stop();
void Fill();
void Run();
}
abstract class Vehicle : VehicleBase
{
public void Stop()
{
Console.WriteLine("Stopped!");
}
public void Fill()
{
Console.WriteLine("Pay and fill...");
}
// Run 暂未实现,所以依然是 abstract 的
public abstract void Run();
}
class Car : Vehicle
{
public override void Run()
{
Console.WriteLine("Car is running...");
}
}
接口的作用除了能够规范化定义一些具有相同成员的类之外,更重要的是接口提供了一种契约(contract)关系,凭借这种关系我们可以使1、不同类之间找到相同的可操作性,2、在类与类之间产生依赖时形成松耦合。
未使用接口时:
class Program
{
static void Main(string[] args)
{
int[] nums1 = new int[] { 1, 2, 3, 4, 5 };
ArrayList nums2 = new ArrayList { 1, 2, 3, 4, 5 };
Console.WriteLine(Sum(nums1));
Console.WriteLine(Avg(nums1));
Console.WriteLine(Sum(nums2));
Console.WriteLine(Avg(nums2));
}
static int Sum(int[] nums)
{
int sum = 0;
foreach (var n in nums)
{
sum += n;
}
return sum;
}
static double Avg(int[] nums)
{
int sum = 0;
double count = 0;
foreach (var n in nums)
{
sum += n;
count++;
}
return sum / count;
}
static int Sum(ArrayList nums)
{
int sum = 0;
foreach (var n in nums)
{
sum += (int)n;
}
return sum;
}
static double Avg(ArrayList nums)
{
int sum = 0;
double count = 0;
foreach (var n in nums)
{
sum += (int)n;
count++;
}
return sum / count;
}
}
供方是 nums1 和 nums2,需方是 Sum 和 Avg 这两函数。
需方需要传进来的参数可以迭代就行,别的不关心也用不到。
整型数组的基类是 Array,Array 和 ArrayList 都实现了 IEnumerable。
static int Sum(IEnumerable nums)
{
int sum = 0;
foreach (var n in nums)
{
sum += (int)n;
}
return sum;
}
static double Avg(IEnumerable nums)
{
int sum = 0;
double count = 0;
foreach (var n in nums)
{
sum += (int)n;
count++;
}
return sum / count;
}
通过调用接口来定义输入类型,减少了不必要的重载,由于输入类型array与arraylist都实现了IEnumerable接口且在Sum与Avg内部直关系传入参数的可迭代性。这里是有接口作为传入参数的输入类型,实质是在不同传入类型中找到了相同共性,简化了代码增强了可读性。
现实世界中有分工、合作,面向对象是对现实世界的抽象,它也有分工、合作。
类与类、对象与对象间的分工、合作。
在面向对象中,合作有个专业术语“依赖”,依赖的同时就出现了耦合。依赖越直接,耦合就越紧。
Car 与 Engine 紧耦合的示例:
class Program
{
static void Main(string[] args)
{
var engine = new Engine();
var car = new Car(engine);
car.Run(3);
Console.WriteLine(car.Speed);
}
}
class Engine
{
public int RPM { get; private set; }
public void Work(int gas)
{
this.RPM = 1000 * gas;
}
}
class Car
{
// Car 里面有个 Engine 类型的字段,它两就是紧耦合了
// Car 依赖于 Engine
private Engine _engine;
public int Speed { get; private set; }
public Car(Engine engine)
{
_engine = engine;
}
public void Run(int gas)
{
_engine.Work(gas);
this.Speed = _engine.RPM / 100;
}
}
紧耦合的问题:
接口:
接口解耦示例
以老式手机举例,对用户来说他只关心手机可以接(打)电话和收(发)短信。
对于手机厂商,接口约束了他只要造的是手机,就必须可靠实现上面的四个功能。
用户如果丢了个手机,他只要再买个手机,不必关心是那个牌子的,肯定也包含这四个功能,上手就可以用。用术语来说就是“人和手机是解耦的”。
class Program
{
static void Main(string[] args)
{
//var user = new PhoneUser(new NokiaPhone());
var user = new PhoneUser(new EricssonPhone());
user.UsePhone();
Console.ReadKey();
}
}
class PhoneUser
{
private IPhone _phone;
public PhoneUser(IPhone phone)
{
_phone = phone;
}
public void UsePhone()
{
_phone.Dail();
_phone.PickUp();
_phone.Receive();
_phone.Send();
}
}
interface IPhone
{
void Dail();
void PickUp();
void Send();
void Receive();
}
class NokiaPhone : IPhone
{
public void Dail()
{
Console.WriteLine("Nokia calling ...");
}
public void PickUp()
{
Console.WriteLine("Hello! This is Tim!");
}
public void Send()
{
Console.WriteLine("Nokia message ring ...");
}
public void Receive()
{
Console.WriteLine("Hello!");
}
}
class EricssonPhone : IPhone
{
public void Dail()
{
Console.WriteLine("Ericsson calling ...");
}
public void PickUp()
{
Console.WriteLine("Hello! This is Tim!");
}
public void Send()
{
Console.WriteLine("Ericsson ring ...");
}
public void Receive()
{
Console.WriteLine("Good evening!");
}
}
没有用接口时,如果一个类坏了,你需要 Open 它再去修改,修改时可能产生难以预料的副作用。引入接口后,耦合度大幅降低,换手机只需要换个类名,就可以了。
等学了反射后,连这里的一行代码都不需要改,只要在配置文件中修改一个名字即可。
在代码中只要有可以替换的地方,就一定有接口的存在;接口就是为了解耦(松耦合)而生。
松耦合最大的好处就是让功能的提供方变得可替换,从而降低紧耦合时“功能的提供方不可替换”带来的高风险和高成本。
• 高风险:功能提供方一旦出问题,依赖于它的功能都挂
• 高成本:如果功能提供方的程序员崩了,会导致功能使用方的整个团队工作受阻