一、什么是接口和抽象类
1.接口和抽象类都是“软件工程产物”
2.具体类→抽象类→接口:越来越抽象,内部实现的东西越来越少
例1:抽象类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp50
{
class Program
{
static void Main(string[] args)
{
Vehicle v = new Car();
v.Run();
Vehicle raceCar = new RaceCar();
raceCar.Stop();
raceCar.Run();
}
}
abstract class Vehicle
{
public void Stop()
{
Console.WriteLine("Stopped!");
}
//public virtual void Run()
//{
// Console.WriteLine("Vehicle is running !");
//}
//其实我们并不调用这个方法,所以它的方法体都没有意义,所以把它变成抽象方法,把类变成抽象类
//抽象类的派生类,如果不实现未完成的方法,自己也要变成抽象类
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 !");
}
}
class RaceCar : Vehicle
{
public override void Run()
{
Console.WriteLine("RaceCar is running !");
}
}
//abstract class Student//抽象类无法被实例化,只有两个用处:1.作为基类 2.作为基类对象去创建变量再引用一个子类的实例
//{
// abstract public void Study();//抽象方法,又称纯虚方法,一旦类里面有了抽象方法就会变成抽象类,类前面也要加上abstract
//}
}
/*
*为做基类而生的“抽象类”与“开放关闭原则”
* 开闭原则:封装那些不变的、稳定的、固定的和确定的成员;而把那些不确定的、有可能改变的成员声明为抽象成员,留给子类去实现
*
*/
例2:接口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp50
{
class Program
{
static void Main(string[] args)
{
Vehicle v = new Car();
v.Run();
Vehicle raceCar = new RaceCar();
raceCar.Stop();
raceCar.Run();
}
}
/// <summary>
/// 纯抽象类其实可以写成接口,把“abstract class”替换成“interface”;接口里的方法是默认public和abstract的所以要把“public abstract”都删掉
/// 接口名称一般I开头名词结尾
/// </summary>
interface IVehicleBase
{
void Stop();
void Fill();
void Run();
}
abstract class Vehicle:IVehicleBase
{
public void Stop()
{
Console.WriteLine("Stop!");
}
public void Fill()
{
Console.WriteLine("Pay and fill...");
}
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 !");
}
}
class RaceCar : Vehicle
{
public override void Run()
{
Console.WriteLine("RaceCar is running !");
}
}
3.抽象类是为完全实现逻辑的类(可以有字段和非public成员,它们代表了“具体逻辑”)
4.抽象类为复用而生:专门作为基类来使用,也具有解耦功能
5.封装确定的,开放不确定的,推迟到合适的子类中去实现
6.接口是完全来实现逻辑的“类”(“纯虚类”;只有函数成员;全员public)
7.接口为解耦而生:“高内聚,低耦合”,方便单元测试
8.接口是一个“协约”,早已为工业生产所熟知(有分工必有合作,有协作必有协约)
9.它们都不能实例化,只能用来声明变量、引用具体类(concrete class)的实例
‘10.SOLID设计原则分别是:
S:单一职责原则(Single Responsibility)
O:开闭原则(Open Closed)
L:里氏代换原则(Liskov Substitution)
I:接口隔离原则(Interface Segregation)
D:依赖倒置原则(Dependency Inversion) ’
二、接口与单元测试
1.接口的产生:自底向上(重构)、自顶而下(设计)
例1:没有接口,不用泛型的情况下(非常麻烦,要写很多方法)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections;
namespace ConsoleApp51
{
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(Avg(nums1));//无法将ArrayList类型转换成int类型,需要类型转换
}
static int Sum(int[]nums)
{
int sum = 0;
foreach (var num in nums)
{
sum += num;
}
return sum;
}
static double Avg(int[] nums)
{
int sum = 0;
double count = 0;
foreach (var num in nums)
{
sum += num;
count++;
}
return sum / count;
}
static int Sum(ArrayList nums)
{
int sum = 0;
foreach (var num in nums)
{
sum += (int)num;//因为ArrayList是object类型的,需要做类型转换
}
return sum;
}
static double Avg(ArrayList nums)
{
int sum = 0;
double count = 0;
foreach (var num in nums)
{
sum += (int)num;
count++;
}
return sum / count;
}
}
}
例2:根据上个例子,Sum、Avg两个方法可以写成接口,要求是传进来的数组能被foreach迭代就行 ,所以可以使用IEnumerable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections;
namespace ConsoleApp51
{
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(Avg(nums1));//无法将ArrayList类型转换成int类型,需要类型转换
}
static int Sum(IEnumerable nums)//IEnumerable:公开枚举数,该枚举数支持在泛型上进行简单迭代
{
int sum = 0;
foreach (var num in nums)
{
sum += (int)num;
}
return sum;
}
static double Avg(IEnumerable nums)
{
int sum = 0;
double count = 0;
foreach (var num in nums)
{
sum += (int)num;
count++;
}
return sum / count;
}
}
}
2.C#中接口的实现(隐式、显式,多接口)
例:紧耦合的情况
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp52
{
class Program
{
static void Main(string[] args)
{
var engine = new Engine();
var car = new Car(engine);
car.Run(3);
}
}
/// <summary>
/// 紧耦合的情况,被依赖的类出问题就会影响到其他类的功能。
/// 而接口就是为了尽量避免这种情况而出现的
/// </summary>
class Engine
{
public int RPM { get; private set; }
public void Work(int gas)
{
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);
Speed = _engine.RPM / 100;
}
}
}
例:接口:你只管调用,内部逻辑你可以不知道。接口就是为了松耦合而存在
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp53
{
class Program
{
static void Main(string[] args)
{
var user = new PhoneUser(new NokiaPhone());
user.UsePhone();
}
}
class PhoneUser
{
private IPhone _phone;
public PhoneUser (IPhone phone)
{
_phone = phone;
}
public void UsePhone()
{
_phone.Dail();
_phone.PickUp();
_phone.Send();
_phone.Receive();
}
}
interface IPhone
{
void Dail();
void PickUp();
void Send();
void Receive();
}
class NokiaPhone : IPhone
{
public void Dail()
{
Console.WriteLine("Nokia is calling...");
}
public void PickUp()
{
Console.WriteLine("Hello!This is Lin!");
}
public void Receive()
{
Console.WriteLine("Nokia message ring ...");
}
public void Send()
{
Console.WriteLine("Hello");
}
}
class EricssonPhone : IPhone
{
public void Dail()
{
Console.WriteLine("Ericsson is calling...");
}
public void PickUp()
{
Console.WriteLine("Hello!This is Lin!");
}
public void Receive()
{
Console.WriteLine("Ericsson message ring ...");
}
public void Send()
{
Console.WriteLine("Good Evening!");
}
}
}
3.语言对面向对象设计的內建支持:依赖反转,接口隔离,开/闭原则...
例:紧耦合,显然不是我们想要的;需要接口进行解耦
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp54
{
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 "Doesn't work.";
}else if (power<100)
{
return "slow";
}else if (power < 200 && power >=100)
{
return "work well";
}
else
{
return "warning!";
}
}
}
}
例:单元测试
被测对象:需要测试高于200和等于0的情况
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp54
{
class Program
{
static void Main(string[] args)
{
var fan = new DeskFan(new PowerSupply());
Console.WriteLine(fan.Work());
}
}
public interface IPowerSupply
{
int GetPower();
}
public class PowerSupply:IPowerSupply
{
public int GetPower()
{
return 100;
}
}
public class DeskFan
{
private IPowerSupply _powerSupply;//接口解耦
public DeskFan(IPowerSupply powerSupply)
{
_powerSupply = powerSupply;
}
public string Work()
{
int power = _powerSupply.GetPower();
if (power<=0)
{
return "Doesn't work.";
}else if (power<100)
{
return "slow";
}else if (power < 200 && power >=100)
{
return "work well";
}
else
{
return "warning!";
}
}
}
}
下面是单元测试:在解决方案右键→添加→新建项目→测试,根据框架选择合适的项目→生成测试页面如下→在‘’测试‘’选项中选择窗口,再选择测试资源管理器,在弹出的窗口中调试代码
using System;
using ConsoleApp54;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTestProject1
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void PowerLowerThanZero_OK()
{
var fan = new DeskFan(new PowerSupplyLowerThanZero());
var expected = "Doesn't work.";
var actual= fan.Work();
Assert.AreEqual(expected, actual);//Assert用于单元测试的类,不满足被测条件则引发各种异常
}
[TestMethod]
public void PowerMoreThan200_OK()
{
var fan=new DeskFan(new PowerExplode());
var expected = "warning!";
var actual = fan.Work();
Assert.AreEqual(expected, actual);
}
}
class PowerSupplyLowerThanZero : IPowerSupply
{
public int GetPower()
{
return 0;
}
}
class PowerExplode : IPowerSupply
{
public int GetPower()
{
return 1000;
}
}
}
思考:
这样可以调试,但是不够美,每次都要写很多类,有没有什么办法呢?
答:有,在测试截面右键鼠标→选择管理NuGet程序包→搜索Moq,下载安装→在名称空间引用Moq
using System;
using ConsoleApp54;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
namespace UnitTestProject1
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void PowerLowerThanZero_OK()
{
var mock = new Mock<IPowerSupply>();
mock.Setup(ps => ps.GetPower()).Returns(() => 0);
var fan = new DeskFan(mock.Object);
var expected = "Doesn't work.";
var actual = fan.Work();
Assert.AreEqual(expected, actual);//Assert用于单元测试的类,不满足被测条件则引发各种异常
}
[TestMethod]
public void PowerMoreThan200_OK()
{
var mock = new Mock<IPowerSupply>();
mock.Setup(ps => ps.GetPower()).Returns(() => 1000);
var fan = new DeskFan(mock.Object);
var expected = "warning!";
var actual = fan.Work();
Assert.AreEqual(expected, actual);
}
}
}