在C#中,接口(Interface)是一种引用类型,它定义了一个契约,指定了一个类必须实现的成员(属性、方法、事件、索引器)。接口不提供这些成员的实现,只指定成员必须按照特定的方式被实现。
1、使用接口隔离原则 (ISP)
将较大的接口划分为更小、更具体的接口,以遵守 ISP,并确保实现类只需要实现它们使用的方法。
// Bad example
// A single interface for both lights and thermostats
public interface IDevice
{
void TurnOn();
void TurnOff();
void SetTemperature(int temperature);
}
public class SmartLight : IDevice
{
public void TurnOn()
{
Console.WriteLine("Smart light turned on");
}
public void TurnOff()
{
Console.WriteLine("Smart light turned off");
}
public void SetTemperature(int temperature)
{
// Unsupported operation for a light
Console.WriteLine("Cannot set temperature for a light");
}
}
// Good example
// Interface for a light device
public interface ILight
{
void TurnOn();
void TurnOff();
}
// Interface for a thermostat device
public interface IThermostat
{
void SetTemperature(int temperature);
}
// A smart light class implementing ILight
public class SmartLight : ILight
{
public void TurnOn()
{
Console.WriteLine("Smart light turned on");
}
public void TurnOff()
{
Console.WriteLine("Smart light turned off");
}
}
// A smart thermostat class implementing IThermostat
public class SmartThermostat : IThermostat
{
public void SetTemperature(int temperature)
{
Console.WriteLine($"Thermostat set to {temperature}°C");
}
}
2、扩展和可测试性设计
接口在设计时应考虑扩展,以适应未来的更改和增强,而不会破坏现有实现。
// Interface representing a shape
public interface IShape
{
double CalculateArea();
}
// Rectangle implementation of the IShape interface
public class Rectangle : IShape
{
public double Width { get; }
public double Height { get; }
public Rectangle(double width, double height)
{
Width = width;
Height = height;
}
public double CalculateArea()
{
return Width \* Height;
}
}
// Circle implementation of the IShape interface
public class Circle : IShape
{
public double Radius { get; }
public Circle(double radius)
{
Radius = radius;
}
public double CalculateArea()
{
return Math.PI * Radius * Radius;
}
}
在此示例中:
我们有一个 IShape 接口,它表示一个形状,并使用 CalculateArea() 方法来计算其面积。
我们有实现 IShape 接口的 Rectangle 和 Circle 形状类,每个类都提供自己特定于该形状的 CalculateArea() 方法的实现。
该设计允许通过添加实现 IShape 接口的新形状类来轻松扩展,而无需修改现有代码。例如,如果我们想为 Square 扩展它,我们可以简单地创建一个新的类 Square 使用它自己的 CalculateArea() 方法实现 IShape。
// Square implementation of the IShape interface
public class Square : IShape
{
public double SideLength { get; }
public Square(double sideLength)
{
SideLength = sideLength;
}
public double CalculateArea()
{
return SideLength * SideLength;
}
}
不可变接口
考虑将接口设计为不可变的,这意味着一旦定义,就无法修改它们。这有助于防止意外更改并确保代码库的稳定性。
// Immutable interface representing coordinates
public interface ICoordinates
{
// Readonly properties for latitude and longitude
double Latitude { get; }
double Longitude { get; }
}
public class Coordinates : ICoordinates
{
public double Latitude { get; }
public double Longitude { get; }
// Constructor to initialize the latitude and longitude
public Coordinates(double latitude, double longitude)
{
Latitude = latitude;
Longitude = longitude;
}
}
首选组合而不是继承
在设计接口时,优先考虑组合而不是继承。这促进了代码的重用和灵活性。
// Interface representing a component that can be composed into other classes
public interface IComponent
{
void Process();
}
// Example class implementing the IComponent interface
public class Component : IComponent
{
public void Process()
{
Console.WriteLine("Performing action in Component");
}
}
// Example class demonstrating composition
public class CompositeComponent
{
private readonly IComponent _component;
public CompositeComponent(IComponent component)
{
_component = component;
}
public void Execute()
{
_component.Process();
}
}
避免接口过载
具有多种方法的重载接口,仅参数的数量或类型不同,可能会导致混淆。请改用不同的方法名称或重构接口。
public interface IVehicle
{
void Start();
void Stop();
void Accelerate(int speed);
void Accelerate(double accelerationRate);
}
虽然类中的重载方法是一种常见的做法,但接口中的重载方法可能会导致混淆,并使类实现哪种方法变得不那么清楚。通常,最好对不同的行为使用不同的方法名称,或者在必要时将它们分隔到多个接口中
使用泛型
利用泛型创建灵活且可重用的接口,这些接口可以处理不同类型的接口。这使我们能够编写更通用的代码,并且可以处理更广泛的场景。
// Generic interface for a data access layer
public interface IDataAccessLayer<T>
{
Task<T> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
}
版本控制接口
当接口随时间推移而发展时,请考虑对它们进行版本控制,以保持向后兼容性,同时引入新功能。这可以通过接口继承或在接口名称中使用版本控制等技术来实现。
// Interface representing a service for processing orders
public interface IOrderService
{
Task ProcessAsync(Order order);
}
// Interface representing a service for processing orders (version 2)
public interface IOrderServiceV2
{
Task ProcessAsync(OrderV2 order);
}
使用协变接口和逆变接口
利用 .NET 中的协方差和逆变,在处理接口实现时允许更灵活的类型转换。
// Covariant interface for reading data
public interface IDataReader<out T>
{
T ReadData();
}
// Contravariant interface for writing data
public interface IDataWriter<in T>
{
void WriteData(T data);
}
避免脂肪界面
FAT 接口包含太多成员,这使得它们难以实现和维护。将大型接口拆分为更小、更集中的接口。
// Bad example
public interface IDataRepository
{
Task<Data> GetByIdAsync(int id);
Task AddAsync(Data data);
Task GenerateReportAsync();
Task<bool> ValidateAsync(Data data);
}
// Good example
// Interface for data retrieval operations
public interface IDataRepository
{
Task<Data> GetByIdAsync(int id);
Task CreateAsync(Data data);
}
// Interface for data reporting operations
public interface IDataReporting
{
Task GenerateReportAsync();
}
// Interface for data validation
public interface IDataValidation
{
Task<bool> ValidateAsync(Data data);
}
使用显式接口实现
当类实现具有相同名称的成员的多个接口时,请使用显式接口实现来消除它们的歧义。这样可以更好地控制接口成员的可见性。
public interface IInterface1
{
void Method();
}
public interface IInterface2
{
void Method();
}
public class MyClass : IInterface1, IInterface2
{
// Explicit implementation of IInterface1.Method
void IInterface1.Method()
{
Console.WriteLine("IInterface1.Method");
}
// Explicit implementation of IInterface2.Method
void IInterface2.Method()
{
Console.WriteLine("IInterface2.Method");
}
}