接口和抽象类
抽象类
类和方法前加上abstract就变成抽象的
抽象方法不包容任何实现代码,不能是private
无法使用new关键字直接创建抽象类的对象。只能实例化子类对象
包含抽象方法的类一定是抽象类,但抽象类中的方法不一定是抽象方法,抽象类中可以包容“普通的”方法。
抽象类专为做基类而生的
开闭原则(Open Close Principle)
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类。
using System;
namespace AbstractStudy
{
//为做基类而生的“抽象类”与“开放/关闭原则”
class Program
{
static void Main(string[] args)
{
Vehicle v = new Truck();
v.Run();
}
}
abstract class Vehicle
{
public void Stop()
{
Console.WriteLine("Stopped!");
}
//抽象方法,不需要声明方法体,事实上就算不是抽象方法,是虚方法的话,方法体也用不上,所以建议直接使用抽象方法
public abstract void Run();
}
class Car :Vehicle
{
public override void Run()
{
Console.WriteLine("Car is running...");
}
}
class Truck :Vehicle
{
public override void Run()
{
Console.WriteLine("Truck is running...");
}
}
}
此例中展示了抽象类的好处,满足了开闭原则,当程序需要扩展时,只需要添加子类或者重写抽象方法即可,不需要更改抽象类
接口:更抽象的抽象类
使用interface关键字定义接口,接口定义了所有类继承接口时应遵循的语法合同。接口定义了语法合同 "是什么" 部分,派生类定义了语法合同 "怎么做" 部分。
接口定义了属性、方法和事件,这些都是接口的成员。接口只包含了成员的声明。成员的定义是派生类的责任。接口提供了派生类应遵循的标准结构。
接口命名一般以I开头,成员全部public
接口就等于纯虚的抽象类(抽象类中的方法全是抽象的)
接口的好处1
using System;
using System.Collections;
namespace InterfaceStudy
{
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(nums2));
}
//如果不使用接口,那么求和和求平均值要写四个方法,因为int和arraylist是强类型,不能通用
//需求方的foreach要求数据能够被迭代
//arraylist和int都遵守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;
}
}
}
接口的好处2:
紧耦合的例子:
using System;
using System.Collections;
namespace InterfaceStudy
{
class Program
{
static void Main(string[] args)
{
var engine = new Engine();
var car = new Car(engine);
car.Run(3);
Console.WriteLine(car.Speed);
}
}
//car和engine耦合,Car非常依赖于Engine,弊端很大
class Engine
{
public int RPM { get; private set; }
public void Work(int gas)
{
this.RPM = 1000 * gas;
}
}
class Car
{
private Engine _engine;
public Car(Engine engine)
{
_engine = engine;
}
public int Speed { get; private set; }
public void Run(int gas) {
_engine.Work(gas);
this.Speed = _engine.RPM / 100;
}
}
}
日常开发中要避免紧耦合
解决耦合的方法可以使用接口!
using System;
using System.Collections;
namespace InterfaceStudy
{
class Program
{
static void Main(string[] args)
{
//诺基亚和爱立信实现了解耦合,只需改变调用手机的名字,而接口和类的代码不需要更改
var user = new PhoneUser(new EricsssonPhone());
var user = new PhoneUser(new NokiaPhone());
user.UsePhone();
}
}
//接口
interface IPhone
{
void Dail();
void PickUp();
void Send();
void Receive();
}
class PhoneUser
{
private IPhone _phone;
public PhoneUser(IPhone phone)
{
_phone = phone;
}
public void UsePhone()
{
_phone.Dail();
_phone.PickUp();
_phone.Receive();
_phone.Send();
}
}
//诺基亚
class NokiaPhone : IPhone
{
public void Dail()
{
Console.WriteLine("Nokia calling......");
}
public void PickUp()
{
Console.WriteLine("Hello!This is Tim!");
}
public void Receive()
{
Console.WriteLine("Nokia message ring......");
}
public void Send()
{
Console.WriteLine("Hello!");
}
}
//爱立信手机
class EricsssonPhone : IPhone
{
public void Dail()
{
Console.WriteLine("Ericssson calling......");
}
public void PickUp()
{
Console.WriteLine("Hi!This is Tim!");
}
public void Receive()
{
Console.WriteLine("Ericssson ring......");
}
public void Send()
{
Console.WriteLine("Good evening!");
}
}
}
接口,解耦在单元测试中的应用
先写出紧耦合的代码
using System;
using System.Collections;
namespace InterfaceStudy
{
class Program
{
static void Main(string[] args)
{
var fan = new DeskFan(new PowerSupply());
Console.WriteLine(fan.Work());
}
}
//电源
class PowerSupply
{
public int GetPower()
{ return 100; }
}
//电扇
class DeskFan
{
private PowerSupply _powerSupply;
public DeskFan(PowerSupply powerSupply)
{
_powerSupply = powerSupply;
}
public string Work()
{
int power = _powerSupply.GetPower();
if (power < 0)
{
return "Won't work";
}
else if (power < 100)
{ return "Slow"; }
else if (power < 200)
{ return "Work fine"; }
else
{ return "Warning!"; }
}
}
}
//此代码,如果需要测试不同电源电压时代码输出是否正确,那么就需要不断的更改PowerSupply类
//这样是不对的,因为类设计出来就不应该再被更改,所以需要接口来解耦合。
//更改成下面这样的代码
interface IpowerSupply
{
int GetPower();
}
//电源
class PowerSupply:IpowerSupply
{
public int GetPower()
{ return 100; }
}
//电扇
class DeskFan
{
private IpowerSupply _powerSupply;
public DeskFan(IpowerSupply powerSupply)
{
_powerSupply = powerSupply;
}
//再写单元测试来测试代码是否正确
接口隔离原则
接口隔离原则(Interface Segregation Principle,ISP)要求程序员尽量将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法。
要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
接口隔离原则和单一职责都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想,但两者是不同的:
- 单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。
- 单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。
单一职责原则
单一职责原则的定义
单一职责原则(Single Responsibility Principle,SRP)
又称单一功能原则,由罗伯特·C.马丁(Robert C. Martin)于《敏捷软件开发:原则、模式和实践》一书中提出的。这里的职责是指类变化的原因,单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分(There should never be more than one reason for a class to change)。
该原则提出对象不应该承担太多职责,如果一个对象承担了太多的职责,至少存在以下两个缺点:
- 一个职责的变化可能会削弱或者抑制这个类实现其他职责的能力;
- 当客户端需要该对象的某一个职责时,不得不将其他不需要的职责全都包含进来,从而造成冗余代码或代码的浪费。
单一职责原则的优点
单一职责原则的核心就是控制类的粒度大小、将对象解耦、提高其内聚性。如果遵循单一职责原则将有以下优点。
- 降低类的复杂度。一个类只负责一项职责,其逻辑肯定要比负责多项职责简单得多。
- 提高类的可读性。复杂性降低,自然其可读性会提高。
- 提高系统的可维护性。可读性提高,那自然更容易维护了。
- 变更引起的风险降低。变更是必然的,如果单一职责原则遵守得好,当修改一个功能时,可以显著降低对其他功能的影响。

浙公网安备 33010602011771号