接口(interface)
抽象类中的抽象方法只规定了不能是 private 的,而接口中的"抽象方法"只能是 public 的。
这样的成员访问级别就决定了接口的本质:接口是服务消费者和服务提供者之间的契约。
既然是契约,那就必须是透明的,对双方都是可见的。
除了 public,abstract 的抽象方法还可以是 protected 和 internal,它们都不是给功能调用者准备的,各自有特定的可见目标。
接口即契约(contract)
契约使自由合作成为可能,所谓自由合作就是一份合同摆在这里,它即约束服务的使用者也约束服务的提供者。如果该契约的使用者和提供者有多个,它们之间还能自由组合
示例推导
重载
同一个方法名被用于定义多个方法,但这些方法具有不同的参数列表(参数的数量、类型或参数的修饰符如ref、out、params等)。
当调用一个方法时,编译器会根据提供的参数类型和数量来确定应该调用哪个版本的方法。
csharp
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(Sum(nums2));
}
// 计算和:该Sum()只能计算int类型的数组和
// 不能计算nums2的object类型,需要用重载,两个Sum()
static int Sum(int[] nums)
{
int sum = 0;
foreach (var item in nums)
{
sum += item;
}
return sum;
}
static int Sum(ArrayList nums)
{
int sum = 0;
foreach (var item in nums)
{
sum += (int)item; // 强制转换为 int 类型
}
return sum;
}
}
接口版
int 整型数组的基类是 Array,F12可知其实现了接口 IEnumerable
ArrayList 也实现了接口 IEnumerable ,都遵守契约 IEnumerable
两个方法只要求能被迭代即可,就可以把具体类型换成接口 IEnumerable
csharp
// 使用接口,只写一次方法即可
static int Sum(IEnumerable nums)
{
int sum = 0;
foreach (var item in nums)
{
sum += (int)item; // 强制转换为 int 类型
}
return sum;
}
依赖和耦合
现实中有分工、合作,面向对象是对现实的抽象,也有分工、合作。
在面向对象中,合作的专业术语叫"依赖",依赖的同时就有了耦合,依赖越直接,耦合就越紧。
高内聚低耦合
内聚性:又称块内联系。指模块的功能强度的度量,即一个模块内元素彼此之间结合的紧密程度的度量。若一个程序之间各元素之间(程序段之间)联系紧密,则内聚性就高(高内聚)。
耦合性:又称块间联系。指软件系统各模块之间相互紧密联系程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性就越差。相反其耦合性就越弱(低耦合)。
Car 和 Engine 的紧耦合示例
csharp
class Car与Engine紧耦合
{
static void Main(string[] args)
{
var engine = new Engine();
Car car = new Car(engine);
car.Run(3);
Console.WriteLine(car.Speed);
}
class Engine
{
public int PRM { get; set; } // 设置属性"引擎转数"
public void Work(int gas) // gas:汽油
{
this.PRM = 1000 * gas;
}
}
class Car
{
// Car 类里有 Engine 类型的字段,它俩就是紧耦合了
// Car 依赖于 Engine
private Engine _engine;
public int Speed { get; set; }
// // 当创建一个新的 Car 对象时,必须为这个构造函数提供一个 Engine 对象。
public Car(Engine engine)
{
_engine = engine;
}
public void Run(int gas)
{
_engine.Work(gas);
this.Speed = _engine.PRM / 100; // 速度
}
}
}
紧耦合的问题:
- 基础类一旦出问题,上层类写得再好也没辙
- 程序调试时很难定位问题源头
- 基础类修改时,会影响写上层类的其他程序员的工作
所以程序开发中要尽量避免紧耦合,解决方法就是接口。
接口:
- 约束调用者只能调用接口中包含的方法
- 让调用者放心去调,不必关心方法怎么实现的、谁提供的
接口解耦示例
以老式手机举例,对用户来说他只关心手机可以接、打电话和收、发短信。
对于手机厂商,接口约束了他只要造的是手机,就必须可靠实现上面的四个功能。
用户如果丢了个手机,他只要再买个手机,不必关心是那个牌子的,肯定也包含这四个功能,上手就可以用。用术语来说就是"人和手机是解耦的"。
csharp
class Program
{
static void Main(string[] args)
{
}
class PhoneUser
{
private Iphone _iphone;
public PhoneUser(Iphone iphone)
{
_iphone = iphone;
}
public void UsePhone()
{
_iphone.Dail();
_iphone.PickUp();
}
}
interface Iphone
{
void Dail(); // 打
void PickUp(); // 接
}
class HuaWeiPhone:Iphone
{
public void Dail()
{
Console.WriteLine("HuaWei is calling");
}
public void PickUp()
{
Console.WriteLine("Hello!This is HuaWei");
}
}
class XiaoMiPhone:Iphone
{
public void Dail()
{
Console.WriteLine("XiaoMi is calling");
}
public void PickUp()
{
Console.WriteLine("Hello!This is XiaoMi");
}
}
}
- 没有用接口时,如果一个类坏了,你需要 Open 它再去修改,修改时可能产生难以预料的副作用。引入接口后,耦合度大幅降低,换手机只需要换个类名,就可以了。
- 等学了反射后,连这里的一行代码都不需要改,只要在配置文件中修改一个名字即可。
- 在代码中只要有可以替换的地方,就一定有接口的存在;接口就是为了解耦(松耦合)而生。
- 松耦合最大的好处就是让功能的提供方变得可替换,从而降低紧耦合时"功能的提供方不可替换"带来的高风险和高成本。
高风险:功能提供方一旦出问题,依赖于它的功能都挂
高成本:如果功能提供方的程序员崩了,会导致功能使用方的整个团队工作受阻