特性和依赖注入都是基于反射的,同时反射一般和接口配合着使用。
接口隔离原则
接口隔离原则:主张应该把客户端对一个类的需求分解成更小、更具体的接口,而不是提供一个包含所有功能的大接口。
接口中的需求是:乙方不能少给,且甲方不能多要,但甲方不能多要是软性的。所以容易导致违反接口隔离原则。
常见的违反接口隔离原则的情况
情况一
1、为满足少数客户的需求而创建了一个包含众多方法的庞大接口,导致其他不需要某些方法的实体也被迫接受这个接口。
在服务调用者角度,这样违法了接口隔离原则。而在服务提供者角度,这样违法了单一职责原则。
解决方案:这种情况就用接口隔离,把一个大接口分解成更小的接口。使每个小接口都描述一个单一的功能。也就是把本质不同的接口隔离开。
示例:
以下有两段代码:
前段代码会体现违反接口隔离原则,导致接口中多余的方法被调用
后段代码会体现使用接口隔离原则,让接口中本质不同的功能,单独成为一个接口
这段代码是京都某公子爷,开着自己的法拉利Car,撞车后又想开动它的坦克。
using System;
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
var driver=new Driver(new Car());
driver.Drive();
}
}
class Driver {
private IVehicle _vehicle;
public Driver(IVehicle vehicle11) {
_vehicle = vehicle11;
}
public void Drive() {
_vehicle.Run();
}
}
interface IVehicle
{
void Run();
}
class Car : IVehicle
{
public void Run()
{
Console.WriteLine("开动了小汽车");
}
}
class Truck : IVehicle
{
public void Run()
{
Console.WriteLine("开动了小卡车");
}
}
interface ITank {
void Run();
void Fire();
}
class LightTank : ITank
{
public void Fire()
{
Console.WriteLine("轻型坦克开火");
}
public void Run()
{
Console.WriteLine("轻型坦克启动");
}
}
class MediumTank : ITank
{
public void Fire()
{
Console.WriteLine("中型坦克开火");
}
public void Run()
{
Console.WriteLine("中型坦克启动");
}
}
class HeavyTank : ITank
{
public void Fire()
{
Console.WriteLine("重型坦克开火");
}
public void Run()
{
Console.WriteLine("重型坦克启动");
}
}
}
//公子爷想开坦克的话,就将Driver类中字段设为ITank,传入ITank子类
//传入坦克实例,调用坦克启动方法
Driver类只是想开动车,那么传入胖接口ITank中的Frie方法实际上用不到。违背了接口隔离原则。
解决方法:将胖接口ITank分成两个接口(也就是让它继承两个接口):
此时对于实现ITank接口的类LightTank,传入Driver类中,由于Driver类只要用Run方法,那么ITank继承的IWeapon接口不会被传入调用。
using System;
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
var driver=new Driver(new LightTank());
driver.Drive();
}
}
class Driver {
private IVehicle _vehicle;
public Driver(IVehicle vehicle11) {
_vehicle = vehicle11;
}
public void Drive() {
_vehicle.Run();
}
}
interface IVehicle
{
void Run();
}
class Car : IVehicle
{
public void Run()
{
Console.WriteLine("开动了小汽车");
}
}
class Truck : IVehicle
{
public void Run()
{
Console.WriteLine("开动了小卡车");
}
}
interface IWeapon
{
void Fire();
}
interface ITank: IVehicle,IWeapon{
}
class LightTank : ITank
{
public void Fire()
{
Console.WriteLine("轻型坦克开火");
}
public void Run()
{
Console.WriteLine("轻型坦克启动");
}
}
class MediumTank : ITank
{
public void Fire()
{
Console.WriteLine("中型坦克开火");
}
public void Run()
{
Console.WriteLine("中型坦克启动");
}
}
class HeavyTank : ITank
{
public void Fire()
{
Console.WriteLine("重型坦克开火");
}
public void Run()
{
Console.WriteLine("重型坦克启动");
}
}
}
情况二
2、传给调用者的接口是由几个小接口合并的,本来是要传入其中的小接口的,而现在传入的是合并小接口的大接口。那么就会将合格的服务者挡在门外,也就是不能调用单独实现该小接口的类。
示例
由前面第二段代码说明:如果Driver类传入的接口是ITank,那么实例Driver时,只能传入那3种坦克。不能传入Car类、Truck类。
但Driver类是需要所有的车都能开的,应该传入小接口IVehicle,而现在传入了合并的大接口ITank,就会将合格的服务者Car、Truck类挡在门外。
接口隔离原则(Interface Segregation Principle,ISP)是一个面向对象设计的原则,它主张应该把客户端对一个类的需求分解成更小、更具体的接口,而不是提供一个包含所有功能的大接口。这样做可以降低类之间的耦合度,使得每个接口只专注于完成特定任务,使得接口更易于维护和复用。
反射
反射(Reflection:)是编程语言提供的一种机制,允许程序在运行时检查和操作自身的结构(如类、方法、属性等),动态地获取和修改程序的状态。以不变应万变。
反射功能示例:
反射原理及依赖注入
展示未封装的反射用法,了解即可:
核心代码:
static void Main(string[] args)
{
ITank tank=new HeavyTank();//静态类型
//-------------以下都不是静态生成--不用new---------
var t = tank.GetType(); //在内存中获得静态tank在运行时的动态信息,类
object o=Activator.CreateInstance(t);//用t类型构造出object对象,并不知道o里面是什么
MethodInfo fireMi = t.GetMethod("Fire");//反射出方法
MethodInfo runMi = t.GetMethod("Run");
fireMi.Invoke(o, null);
runMi.Invoke(o, null);
}
完整代码:
using System;
using System.Reflection;
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
ITank tank=new HeavyTank();//静态类型
//-------------以下都不是静态生成--不用new---------
var t = tank.GetType(); //在内存中获得静态tank在运行时的动态信息,类
object o=Activator.CreateInstance(t);//用t类型构造出object对象,并不知道o里面是什么
MethodInfo fireMi = t.GetMethod("Fire");//反射出方法
MethodInfo runMi = t.GetMethod("Run");
fireMi.Invoke(o, null);
runMi.Invoke(o, null);
}
}
class Driver {
private IVehicle _vehicle;
public Driver(IVehicle vehicle11) {
_vehicle = vehicle11;
}
public void Drive() {
_vehicle.Run();
}
}
interface IVehicle
{
void Run();
}
class Car : IVehicle
{
public void Run()
{
Console.WriteLine("开动了小汽车");
}
}
class Truck : IVehicle
{
public void Run()
{
Console.WriteLine("开动了小卡车");
}
}
interface IWeapon
{
void Fire();
}
interface ITank: IVehicle,IWeapon{
}
class LightTank : ITank
{
public void Fire()
{
Console.WriteLine("轻型坦克开火");
}
public void Run()
{
Console.WriteLine("轻型坦克启动");
}
}
class MediumTank : ITank
{
public void Fire()
{
Console.WriteLine("中型坦克开火");
}
public void Run()
{
Console.WriteLine("中型坦克启动");
}
}
class HeavyTank : ITank
{
public void Fire()
{
Console.WriteLine("重型坦克开火");
}
public void Run()
{
Console.WriteLine("重型坦克启动");
}
}
}
展示依赖注入
依赖反转(DI)是个概念,依赖注入(DI)是以它为基础,结合接口、反射,的一个应用。
依赖注入需要依赖注入框架:
右击项目名,点击管理NuGet程序包,下载应用图中第二个:
依赖注入优点1:
就算有无数行有关HeavyTank代码,需要将HeavyTank改成MediumTank,只需改 sc.AddScope中的HeavyTank。常规写的,是没这么方便的。
static void Main(string[] args)
{
var sc=new ServiceCollection();//依赖注入中最重要的就是这个容器
//往容器中装一对类型,参数是接口,实现接口的类,typeof()是获取动态描述
sc.AddScoped(typeof(ITank),typeof(HeavyTank));
var sp=sc.BuildServiceProvider();
//----------------以上是程序之前注册,下面是任何地方都可以-----------------
ITank tank=sp.GetService<ITank>();
//这里就算有无数行有关HeavyTank代码,需要将HeavyTank改成MediumTank,只需改 sc.AddScope中的HeavyTank
tank.Fire();
tank.Run();
}
}
依赖注入优点2:在动态new一个Driver类型时,会自动在容器里面将需要的IVehicle类型注入
static void Main(string[] args)
{
var sc=new ServiceCollection();//依赖注入中最重要的就是这个容器
//往容器中装一对类型,参数是接口,实现接口的类,typeof()是获取动态描述
sc.AddScoped(typeof(ITank),typeof(HeavyTank));
sc.AddScoped(typeof(IVehicle),typeof(Car));
sc.AddScoped<Driver>();
var sp=sc.BuildServiceProvider();
//----------------以上是程序之前注册,下面是任何地方都可以-----------------
var driver=sp.GetService<Driver>();//它在动态new一个Driver类型时,会自动在容器里面将需要的IVehicle类型注入
driver.Drive();
}
用反射实现更松的耦合
常用于插件程序
using System.Reflection;
public static T ExecuteAction<T>(object targetObject, string methodName, params object[] args)
{
// 获取目标对象的类型信息
Type targetType = typeof(T);
// 使用反射获取指定方法
MethodInfo methodInfo = targetType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance);
if (methodInfo != null)
{
// 创建委托并调用方法
Delegate del = methodInfo.CreateDelegate(typeof(Delegate), targetObject);
return (T)del.DynamicInvoke(args);
}
else
{
throw new ArgumentException($"Method {methodName} not found in type {typeof(T).FullName}");
}
}
// 使用示例
class MyClass
{
public void MyMethod(string param) => Console.WriteLine($"MyClass: {param}");
}
var myInstance = new MyClass();
ExecuteAction<MyClass>(myInstance, "MyMethod", "Hello from reflection");
在这个例子中,ExecuteAction
函数可以根据传入的类型和方法名,在运行时找到并执行相应的方法,这使得调用者无需关心具体的实现细节,提高了代码的灵活性和解耦。