C#中关于增强类功能的几种方式

本文主要讲解如何利用C#语言自身的特性来对一个类的功能进行丰富与增强,便于拓展现有项目的一些功能。

拓展方法

扩展方法被定义为静态方法,通过实例方法语法进行调用。方法的第一个参数指定该方法作用于哪个类型,并且该参数以 this 修饰符为前缀。仅当使用 using 指令将命名空间显式导入到源代码中之后,扩展方法才可使用。

namespace Extensions
{ public static class StringExtension
{
public static DateTime ToDateTime(this string source)
{
DateTime.TryParse(source, out DateTime result);
return result;
}
}
}
注意:
  • 如果扩展方法与该类型中定义的方法具有相同的签名,则扩展方法永远不会被调用。
  • 在命名空间级别将扩展方法置于相应的作用范围内。例如,在一个名为 Extensions 的命名空间中具有多个包含扩展方法的静态类,则在使用这些拓展方法时,必须引用其命名空间 using Extensions

继承

继承 面向对象的一个特性,属于Is a 关系,比如说Student继承Person,则说明Student is a Person。子类可以通过重写父类的方法或添加新的方法来实现对父类的拓展。

namespace Inherit
{
public class Persion
{
public string Name { get; set; } public int Age { get; set; } public void Eat()
{
Console.WriteLine("吃饭");
} public void Sleep()
{
Console.WriteLine("睡觉");
}
} public class Student : Persion
{
public void Study()
{
Console.WriteLine("学习");
} public new void Sleep()
{
Console.WriteLine("做作业,复习功课");
base.Sleep();
}
}
}
继承的缺点:
  • 父类的内部细节对子类是可见的
  • 子类与父类的继承关系在编译阶段就确定下来了,无法在运行时动态改变从父类继承方法的行为
  • 如果父类方法做了修改,所有的子类都必须做出相应的调整,子类与父类是一种高度耦合,违反了面向对象的思想。

组合

组合就是在设计类的时候把需要用到的类作为成员变量加入到当前类中。

组合的优缺点:
  • 优点:

    • 隐藏了被引用对象的内部细节
    • 降低了两个对象之间的耦合
    • 可以在运行时动态修改被引用对象的实例
  • 缺点:
    • 系统变更可能需要不停的定义新的类
    • 系统结构变复杂,不再局限于单个类

建议多使用组合,少用继承

装饰者模式

装饰者模式指在不改变原类定义及继承关系的情况跟下,动态的拓展一个类的功能,就是利用创建一个包装类(wrapper)来装饰(decorator)一个已有的类。

包含角色:
  • 被装饰者:

    • Component 抽象被装饰者
    • ConcreteComponent 具体被装饰者,Component的实现,在装饰者模式中装饰的就是这货。
  • 装饰者:
    • Decorator 装饰者 一般是一个抽象类并且作为Component的子类,Decorator必然会有一个成员变量用来存储Component的实例
    • ConcreateDecorator 具体装饰者 Decorator的实现

在装饰者模式中必然会有一个最基本,最核心,最原始的接口或抽象类充当component和decorator的抽象组件

实现要点:
  • 定义一个类或接口,并且让装饰者及被装饰者的都继承或实现这个类或接口
  • 装饰者中必须持有被装饰者的引用
  • 装饰者中对需要增强的方法进行增强,不需要增强的方法调用原来的业务逻辑

namespace Decorator
{ /// <summary>
/// Component 抽象者装饰者
/// </summary>
public interface IStudent
{
void Learn();
} /// <summary>
/// ConcreteComponent 具体被装饰者
/// </summary>
public class Student : IStudent
{
private string _name;
public Student(string name)
{
this._name = name;
}
public void Learn()
{
System.Console.WriteLine(this._name + "学习了以上内容");
}
}
/// <summary>
/// Decorator 装饰者
/// </summary>
public abstract class Teacher : IStudent
{
private IStudent _student;
public Teacher(IStudent student)
{
this._student = student;
}
public virtual void Learn()
{
this.Rest();
this._student.Learn();
} public virtual void Rest()
{
Console.WriteLine("课间休息");
}
} /// <summary>
/// ConcreteDecorator 具体装饰者
/// </summary>
public class MathTeacher : Teacher
{
private String _course;
public MathTeacher(IStudent student, string course) : base(student)
{
this._course = course;
}
public override void Learn()
{
System.Console.WriteLine("学习新内容:" + this._course);
base.Learn();
}
public override void Rest()
{
System.Console.WriteLine("课间不休息,开始考试");
}
} /// <summary>
/// ConcreteDecorator 具体装饰者
/// </summary>
public class EnlishTeacher : Teacher
{
private String _course;
public EnlishTeacher(IStudent student, string course) : base(student)
{
this._course = course;
} public override void Learn()
{
this.Review();
System.Console.WriteLine("学习新内容:" + this._course);
base.Learn();
} public void Review()
{
System.Console.WriteLine("复习英文单词");
}
} public class Program
{
static void Main(string[] args)
{
IStudent student = new Student("student");
student = new MathTeacher(student, "高数");
student = new EnlishTeacher(student, "英语");
student.Learn();
}
}
}
装饰者模式优缺点:
  • 优点:

    • 装饰者与被装饰者可以独立发展,不会互相耦合
    • 可以作为继承关系的替代方案,在运行时动态拓展类的功能
    • 通过使用不同的装饰者类或不同的装饰者排序,可以得到各种不同的结果
  • 缺点:
    • 产生很多装饰者类
    • 多层装饰复杂

代理模式

代理模式就是给一个对象提供一个代理对象,并且由代理控制原对象的引用。

包含角色:
  • 抽象角色:抽象角色是代理角色和被代理角色的所共同继承或实现的抽象类或接口
  • 代理角色:代理角色是持有被代理角色引用的类,代理角色可以在执行被代理角色的操作时附加自己的操作
  • 被代理角色:被代理角色是代理角色所代理的对象,是真实要操作的对象

静态代理

动态代理涉及到反射技术相对静态代理会复杂很多,掌握好动态代理对AOP技术有很大帮助


namespace Proxy
{
/// <summary>
/// 共同抽象角色
/// </summary>
public interface IBuyHouse
{
void Buy();
} /// <summary>
/// 真实买房人,被代理角色
/// </summary>
public class Customer : IBuyHouse
{
public void Buy()
{
System.Console.WriteLine("买房子");
}
} /// <summary>
/// 中介-代理角色
/// </summary>
public class CustomerProxy : IBuyHouse
{
private IBuyHouse target;
public CustomerProxy(IBuyHouse buyHouse)
{
this.target = buyHouse;
}
public void Buy()
{
System.Console.WriteLine("筛选符合条件的房源");
this.target.Buy();
}
} public class Program
{
static void Main(string[] args)
{
IBuyHouse buyHouse = new CustomerProxy(new Customer());
buyHouse.Buy();
System.Console.ReadKey();
}
}
}

动态代理


namespace DynamicProxy
{
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection; /// <summary>
/// 方法拦截器接口
/// </summary>
public interface IMethodInterceptor
{
/// <summary>
/// 调用拦截器
/// </summary>
/// <param name="targetMethod">拦截的目标方法</param>
/// <param name="args">拦截的目标方法参数列表</param>
/// <returns>拦截的目标方法返回值</returns>
object Interceptor(MethodInfo targetMethod, object[] args);
} /// <summary>
/// 代理类生成器
/// </summary>
public class ProxyFactory : DispatchProxy
{
private IMethodInterceptor _interceptor; /// <summary>
/// 创建代理类实例
/// </summary>
/// <param name="targetType">要代理的接口</param>
/// <param name="interceptor">拦截器</param>
/// <returns></returns>
public static object CreateInstance(Type targetType, IMethodInterceptor interceptor)
{
object proxy = GetProxy(targetType);
((ProxyFactory)proxy).GetInterceptor(interceptor);
return proxy;
} /// <summary>
/// 创建代理类实例
/// </summary>
/// <param name="targetType">要代理的接口</param>
/// <param name="interceptorType">拦截器</param>
/// <param name="parameters">拦截器构造函数参数值</param>
/// <returns>代理实例</returns>
public static object CreateInstance(Type targetType, Type interceptorType, params object[] parameters)
{
object proxy = GetProxy(targetType);
((ProxyFactory)proxy).GetInterceptor(interceptorType, parameters);
return proxy;
} /// <summary>
/// 创建代理类实例
/// </summary>
/// <typeparam name="TTarget">要代理的接口</typeparam>
/// <typeparam name="TInterceptor">拦截器</typeparam>
/// <param name="parameters">拦截器构造函数参数值</param>
/// <returns></returns>
public static TTarget CreateInstance<TTarget, TInterceptor>(params object[] parameters) where TInterceptor : IMethodInterceptor
{
object proxy = GetProxy(typeof(TTarget));
((ProxyFactory)proxy).GetInterceptor(typeof(TInterceptor), parameters);
return (TTarget)proxy;
} /// <summary>
/// 获取代理类
/// </summary>
/// <param name="targetType"></param>
/// <returns></returns>
private static object GetProxy(Type targetType)
{
MethodCallExpression callexp = Expression.Call(typeof(DispatchProxy), nameof(DispatchProxy.Create), new[] { targetType, typeof(ProxyFactory) });
return Expression.Lambda<Func<object>>(callexp).Compile()();
} /// <summary>
/// 获取拦截器
/// </summary>
/// <param name="interceptorType"></param>
/// <param name="parameters"></param>
private void GetInterceptor(Type interceptorType, object[] parameters)
{
Type[] ctorParams = parameters.Select(x => x.GetType()).ToArray();
IEnumerable<ConstantExpression> paramsExp = parameters.Select(x => Expression.Constant(x));
NewExpression newExp = Expression.New(interceptorType.GetConstructor(ctorParams), paramsExp);
this._interceptor = Expression.Lambda<Func<IMethodInterceptor>>(newExp).Compile()();
} /// <summary>
/// 获取拦截器
/// </summary>
/// <param name="interceptor"></param>
private void GetInterceptor(IMethodInterceptor interceptor)
{
this._interceptor = interceptor;
} /// <summary>
/// 执行代理方法
/// </summary>
/// <param name="targetMethod"></param>
/// <param name="args"></param>
/// <returns></returns>
protected override object Invoke(MethodInfo targetMethod, object[] args)
{
return this._interceptor.Interceptor(targetMethod, args);
}
} /// <summary>
/// 表演者
/// </summary>
public interface IPerform
{
/// <summary>
/// 唱歌
/// </summary>
void Sing(); /// <summary>
/// 跳舞
/// </summary>
void Dance();
} /// <summary>
/// 具体的表演者——刘德华 Andy
/// </summary>
public class AndyPerformer : IPerform
{
public void Dance()
{
System.Console.WriteLine("给大家表演一个舞蹈");
} public void Sing()
{
System.Console.WriteLine("给大家唱首歌");
}
} /// <summary>
/// 经纪人——负责演员的所有活动
/// </summary>
public class PerformAgent : IMethodInterceptor
{
public IPerform _perform;
public PerformAgent(IPerform perform)
{
this._perform = perform;
}
public object Interceptor(MethodInfo targetMethod, object[] args)
{
System.Console.WriteLine("各位大佬,要我们家艺人演出清闲联系我");
object result = targetMethod.Invoke(this._perform, args);
System.Console.WriteLine("各位大佬,表演结束该付钱了");
return result;
}
} public class Program
{
static void Main(string[] args)
{
IPerform perform; //perform = ProxyFactory.CreateInstance<IPerform, PerformAgent>(new AndyPerformer());
//perform.Sing();
//perform.Dance(); ServiceCollection serviceDescriptors = new ServiceCollection();
serviceDescriptors.AddSingleton<IPerform>(ProxyFactory.CreateInstance<IPerform, PerformAgent>(new AndyPerformer()));
IServiceProvider serviceProvider = serviceDescriptors.BuildServiceProvider();
perform = serviceProvider.GetService<IPerform>();
perform.Sing();
perform.Dance(); System.Console.ReadKey();
}
} }

总结

  • 使用拓展方法只能拓展新增方法,不能增强已有的功能
  • 使用继承类或接口,类只能单继承,并且在父类改变后,所有的子类都要跟着变动
  • 使用代理模式与继承一样代理对象和真实对象之间的的关系在编译时就确定了
  • 使用装饰者模式能够在运行时动态地增强类的功能

参考引用

利用.NET Core类库System.Reflection.DispatchProxy实现简易Aop

C#中关于增强类功能的几种方式的更多相关文章

  1. C# 中一些类关系的判定方法 C#中关于增强类功能的几种方式 Asp.Net Core 轻松学-多线程之取消令牌

    1.  IsAssignableFrom实例方法 判断一个类或者接口是否继承自另一个指定的类或者接口. public interface IAnimal { } public interface ID ...

  2. 在一个web 应用中,改变url无非是2种方式,一种是利用超链接进行跳转,另一种是使用浏览器的前进和回退功能

    在一个web 应用中,改变url无非是2种方式,一种是利用超链接进行跳转,另一种是使用浏览器的前进和回退功能 https://www.jianshu.com/p/27ee7df4ccc1

  3. Struts2中获取HttpServletRequest,HttpSession等的几种方式

    转自:http://www.kaifajie.cn/struts/8944.html package com.log; import java.io.IOException; import java. ...

  4. Mybatis中使用association进行关联的几种方式

    这里以一对一单向关联为例.对使用或不使用association的配置进行举例.  实体类: @Data @ToString @NoArgsConstructor public class IdCard ...

  5. 【Java8新特性】面试官问我:Java8中创建Stream流有哪几种方式?

    写在前面 先说点题外话:不少读者工作几年后,仍然在使用Java7之前版本的方法,对于Java8版本的新特性,甚至是Java7的新特性几乎没有接触过.真心想对这些读者说:你真的需要了解下Java8甚至以 ...

  6. Action中取得request,session的四种方式

    Action中取得request,session的四种方式 在Struts2中,从Action中取得request,session的对象进行应用是开发中的必需步骤,那么如何从Action中取得这些对象 ...

  7. WPF中使用文件浏览对话框的几种方式

    原文:WPF中使用文件浏览对话框的几种方式 WPF本身并没有为我们提供文件浏览的控件, 也不能直接使用Forms中的控件,而文件浏览对话框又是我们最常用的控件之一. 下面是我实现的方式 方式1: 使用 ...

  8. Dojo初探之2:设置dojoConfig详解,dojoConfig参数详解+Dojo中预置自定义AMD模块的四种方式(基于dojo1.11.2)

    Dojo中想要加载自定义的AMD模块,需要先设置好这个模块对应的路径,模块的路径就是这个模块的唯一标识符. 一.dojoConfig参数设置详解 var dojoConfig = { baseUrl: ...

  9. 转 Velocity中加载vm文件的三种方式

    Velocity中加载vm文件的三种方式   velocitypropertiespath Velocity中加载vm文件的三种方式:    方式一:加载classpath目录下的vm文件 Prope ...

随机推荐

  1. Python -- 网络编程 -- 认识Python3的urllib库

    Python3的urllib包含5个模块 urllib error parse request response robotparser 各个模块的主要成员: error ['ContentTooSh ...

  2. numpy.linalg.norm(求范数)

    1.linalg=linear(线性)+algebra(代数),norm则表示范数. 2.函数参数 x_norm=np.linalg.norm(x, ord=None, axis=None, keep ...

  3. 05-python中的异常

    python的所有的异常都继承自基类: Exception 处理方式和java类似: path = raw_input('input the path') array = path.split('/' ...

  4. WPF设置控件获得焦点FocusManager

      简单用法如下: 在父类容器中通过附加属性FocusManager.FocusedElement来绑定需要强制获得焦点的控件,用法如下: <Grid FocusManager.FocusedE ...

  5. Hadoop MapReduce流程及容错

    shuffle流程 输入分片(input split):在进行map计算之前,mapreduce会根据输入文件计算输入分片(input split),每个输入分片(input split)针对一个ma ...

  6. Nginx使用记录

    配置常见解释: ########### 每个指令必须有分号结束.################# #user administrator administrators; #配置用户或者组,默认为no ...

  7. 常用工具说明--jsdoc 前端文档输出工具

    1.利用npm安装jsdoc模块 npm install jsdoc -g   2.由于jsdoc默认的生成文档模板不好看,可以下载一套好看些的模板,如 jaguar.js 下载地址:https:// ...

  8. val();html();.text()区别

    对于innerHTML 属性,几乎所有的元素都有innerHTML属性,它是一个字符串,用来设置或获取位于对象起始和结束标签内的HTML.(获取HTML当前标签的起始和结束里面的内容) 对于inner ...

  9. 转载:Remote Validation

    http://www.jb51.net/article/89474.htm 大多数的开发者,可能会遇到这样的情况:当我们在创建用户之前,有必要去检查是否数据库中已经存在相同名字的用户.换句话说就是,我 ...

  10. .net core 2.2 部署CentOS7(3)安装Xshell操控CentOS7

    目录: .net core 2.2 部署CentOS7(1)安装虚拟机 .net core 2.2 部署CentOS7(2)给虚拟机安装CentOS7 .net core 2.2 部署CentOS7( ...