在 .NET 源代码中,设计模式是常见的编程范式,用于解决经典的问题,如对象创建、接口解耦、算法透明等。本文将介绍.NET 源代码中常见的设计模式及其示例。
创建型模式
创建型模式用于处理对象的创建过程,包括对象的实例化时机、构造方法参数、实例化过程等。常见的创建型模式有:
- 工厂方法模式 (Factory Method Pattern)
- 抽象工厂模式 (Abstract Factory Pattern)
- 单例模式 (Singleton Pattern)
- 建造者模式 (Builder Pattern)
- 原型模式 (Prototype Pattern)
这些设计模式是.NET 源代码中的精华所在,在实际开发中也有广泛应用。
设计模式和示例
以下是一些 .NET 源代码中常见的设计模式及其示例:
1、工厂模式
工厂模式 - Factory Pattern 在 .NET 中使用工厂模式来创建对象。例如,以下是 .NET 中使用的一些工厂模式示例:
- 在 System.Data 中,使用 DbProviderFactory 工厂类来创建数据库提供程序;
- 在 System.Net.Sockets 中,使用 SocketFactory 工厂类来创建套接字;
- 在 System.Threading.Tasks.Dataflow 中,使用 DataflowBlockOptions 静态工厂方法来创建数据流块。
DbProviderFactory说明和示例
DbProviderFactory 是一个抽象工厂类,提供了一种方式来创建与 ADO.NET 数据库提供程序所支持的特定数据源的命令对象、连接对象、数据适配器对象和数据读取器对象。 它使得应用程序可以在不直接调用特定数据库提供程序的情况下与多个数据库提供程序进行交互,从而提高了代码的可移植性和灵活性。
在 .NET Framework 中,常用的数据库提供程序如 System.Data.SqlClient、System.Data.OracleClient、System.Data.OleDb、System.Data.Odbc 等都继承自 DbProviderFactory 类,并实现了它的抽象成员,如 CreateCommand(), CreateConnection(), CreateDataAdapter(), CreateParameter() 等,这些成员通过使用特定数据库的 ADO.NET 提供程序,来创建与该数据库所兼容的命令对象、连接对象、数据适配器对象和数据读取器对象。
使用 DbProviderFactory 可以避免将应用程序绑定到特定的数据库提供程序,并且它还可以通过简单地更改配置文件中的设置来切换到另一个数据库提供程序,从而增加了代码的灵活性。例如,可以在 Web.config 文件中指定与 Oracle 数据库兼容的数据库提供程序,在另一个 config 文件中指定与 SQL Server 数据库兼容的数据库提供程序,然后通过更改配置文件来切换到不同的数据库提供程序。
下面是一个使用 DbProviderFactory 创建 SQL Server 数据库连接对象的示例:
using System;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
public class Program
{
static void Main(string[] args)
{
// 获取数据库提供程序工厂
DbProviderFactory factory = DbProviderFactories.GetFactory("System.Data.SqlClient");
// 创建连接对象
using (DbConnection connection = factory.CreateConnection())
{
// 从配置文件中获取连接字符串
string connectionString = ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString;
// 设置连接对象的连接字符串
connection.ConnectionString = connectionString;
// 打开连接
connection.Open();
Console.WriteLine("连接成功!");
// 关闭连接
connection.Close();
}
}
}
在这个示例中,我们首先通过
DbProviderFactories.GetFactory() 方法获取了一个特定的数据库提供程序工厂。然后,我们使用 CreateConnection() 方法创建了一个连接对象,并从配置文件中获取了一个连接字符串。最后,我们通过调用 Open() 方法打开连接,并在控制台输出了一条连接成功的消息。当使用完连接对象之后,我们应该调用 Close() 方法关闭连接,以释放与之关联的任何资源。
SocketFactory说明和示例
在 System.Net.Sockets 命名空间中,可以使用 SocketFactory 工厂类来创建套接字。SocketFactory 提供了一些静态方法来创建不同类型的套接字,例如 CreateSocket() 方法可以用于创建一个基于 TCP/IP 协议的套接字实例。
下面是一个使用 SocketFactory 创建套接字的示例:
using System.Net.Sockets;
// 创建一个基于TCP/IP协议的套接字
Socket tcpSocket = SocketFactory.CreateSocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 设置套接字选项
tcpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
在上面的示例中,我们使用了 SocketFactory 工厂类的 CreateSocket 静态方法来创建一个基于 TCP/IP 协议的套接字实例。然后,我们使用 SetSocketOption 方法为套接字设置一些选项,例如重用地址选项等。
需要注意的是,SocketFactory 工厂类虽然提供了方便的套接字创建方法,但在某些情况下可能不够灵活,无法满足一些特定需求。这时,可以通过手动创建一个 Socket 实例并设置其属性和选项来获得更精细的控制。
DataflowBlockOptions说明和示例
在
System.Threading.Tasks.Dataflow 命名空间中,为了方便使用和创建数据流块,提供了一些静态工厂方法来创建常用的数据流块类型,这些工厂方法会返回已经设置好一些默认选项的数据流块实例。
这些静态工厂方法通常会使用 DataflowBlockOptions 作为参数,以便为创建的数据流块设置选项。下面是几个常用的静态工厂方法示例:
- TransformBlock<TInput,TOutput>(Func<TInput,TOutput>, DataflowBlockOptions):创建一个转换块,用于从输入元素中转换出新的输出元素。
- BroadcastBlock<T>(DataflowBlockOptions):创建一个广播块,它将每个输入元素广播到多个目标块中。
- BufferBlock<T>(DataflowBlockOptions):创建一个缓冲块,用于在不同块之间存储元素的缓冲区。
- ActionBlock<T>(Action<T>, DataflowBlockOptions):创建一个动作块,用于执行某些操作而不生成任何输出元素。
下面是一个使用静态工厂方法创建数据流块的示例:
using System.Threading.Tasks.Dataflow;
// 创建一个转换块,将int类型的输入转换为double类型的输出
var transformBlock = new TransformBlock<int, double>(
input => input * 2.0, // 转换函数
new DataflowBlockOptions() // 使用DataflowBlockOptions设置选项
{
BoundedCapacity = 10, // 设置缓冲区大小
CancellationToken = cancellationToken, // 设置取消标记
MaxDegreeOfParallelism = 2 // 设置最大并行度为2
}
);
在上面的示例中,我们使用了 TransformBlock 静态工厂方法来创建一个转换块,并将其配置为可以处理 int 类型的输入,并且将其转换为 double 类型的输出。同时,我们使用了 DataflowBlockOptions 参数来设置了一些常用选项,包括了缓冲区大小、取消标记以及最大并行度等。
2、单例模式
单例模式 - Singleton Pattern 在 .NET 中使用单例模式来创建全局唯一的对象和服务。例如,以下是 .NET 中使用的一些单例模式示例:
- 在 System.Diagnostics 中,使用 TraceSource 类来创建全局唯一的跟踪源;
- 在 System.Net 中,使用 ServicePointManager 类来创建全局唯一的服务点管理器;
- 在 ASP.NET 中,使用 HttpRuntime 类来创建全局唯一的运行时对象。
TraceSource说明和示例
在 System.Diagnostics 命名空间中,可以使用 TraceSource 类来创建全局唯一的跟踪源,以便进行跟踪和调试工作。
TraceSource 跟踪源提供了一种跟踪和记录应用程序的方式,它具有比 Trace 和 Debug 类更高的灵活性和可配置性。使用 TraceSource 类创建的跟踪源可以定义多个监听器,每个监听器可以将跟踪信息记录到不同的目标,例如控制台、文本文件、事件日志等。
下面是一个示例,展示如何使用 TraceSource 类创建全局唯一的跟踪源:
using System.Diagnostics;
// 创建一个名为 "MyApp" 的跟踪源
TraceSource traceSource = new TraceSource("MyApp");
// 添加一个名称为 "console" 的 TextWriterTraceListener 监听器
traceSource.Listeners.Add(new TextWriterTraceListener(Console.Out, "console"));
// 启用跟踪级别为 Verbose 的跟踪信息
traceSource.Switch.Level = SourceLevels.Verbose;
// 在跟踪源上记录一条信息
traceSource.TraceInformation("Hello, world!");
在上面的示例中,我们首先创建了一个名为 "MyApp" 的跟踪源,并向其添加了一个名称为 "console" 的监听器,该监听器将跟踪信息输出到控制台。然后,我们使用 Switch 属性来启用跟踪级别为 Verbose 的跟踪信息,最后通过 TraceInformation 方法在跟踪源上记录了一条信息。
需要注意的是,TraceSource 的使用与 Trace 和 Debug 类相比更加复杂,这是因为它提供了更高的灵活性和可配置性。在实际应用中,应根据需要选择适当的跟踪方式和工具,并进行合理的跟踪级别设置,以避免对应用程序性能和安全造成不必要的影响。
ServicePointManager说明和示例
在 System.Net 命名空间中,可以使用 ServicePointManager 类来创建全局唯一的服务点管理器,以便对 HTTP 连接进行管理和配置。
ServicePointManager 类提供了一种集中式管理服务点的方式,它允许设置一些常见的 HTTP 协议参数,例如超时、缓存、安全等级等。使用 ServicePointManager 创建的服务点可以在应用程序范围内共享和重用,从而提高应用程序的性能和可维护性。
下面是一个示例,展示如何使用 ServicePointManager 类创建全局唯一的服务点管理器:
using System.Net;
// 设置默认的最大连接数和默认连接超时时间
ServicePointManager.DefaultConnectionLimit = 10;
ServicePointManager.DefaultConnectTimeout = 10000;
// 创建一个新的服务点并设置特定的 HTTP 选项
ServicePoint sp = ServicePointManager.FindServicePoint(new Uri("http://www.example.com"));
sp.ConnectionLeaseTimeout = 60000;
// 使用服务点发送 HTTP 请求
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri("http://www.example.com"));
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
在上面的示例中,我们首先使用 ServicePointManager 类设置了默认的最大连接数和连接超时时间。然后,我们创建了一个新的服务点,并设置了该服务点的连接租约超时时间。最后,我们使用该服务点发送了一个 HTTP 请求。
需要注意的是,ServicePointManager 类提供了一些常见的 HTTP 协议参数设置,但是并不适用于所有场景。在实际应用中,应根据具体的业务需求和系统环境来选择合适的 HTTP 参数设置方式,并进行适当地调整和优化。
HttpRuntime说明和示例
在 ASP.NET 中,可以使用 HttpRuntime 类来创建全局唯一的运行时对象,以便对 ASP.NET 应用程序进行管理和配置。
HttpRuntime 类是 ASP.NET 运行时环境的核心类之一,它提供了一种集中式管理 ASP.NET 应用程序的方式。使用 HttpRuntime 创建的运行时对象可以访问和设置一些重要的应用程序参数,例如应用程序域、路径映射、编译器选项等。
下面是一个示例,展示如何使用 HttpRuntime 类创建全局唯一的运行时对象:
using System.Web;
// 获取全局唯一的 HttpRuntime 对象
HttpRuntime runtime = HttpRuntime.AppDomainAppId == null ? null : HttpRuntime.Instance;
// 输出当前应用程序域的 ID
string appDomainId = runtime?.AppDomainId;
Console.WriteLine("Current app domain ID: " + appDomainId);
// 输出当前应用程序的虚拟路径
string appVirtualPath = runtime?.AppVirtualPath;
Console.WriteLine("Current app virtual path: " + appVirtualPath);
在上面的示例中,我们首先使用 HttpRuntime 类获取了全局唯一的运行时对象,并输出了当前应用程序域的 ID 和当前应用程序的虚拟路径。
需要注意的是,HttpRuntime 类并不适用于所有场景,它主要用于管理和配置 ASP.NET 应用程序本身。在实际应用中,应根据具体的业务需求和系统环境来选择合适的 ASP.NET 参数设置方式,并进行适当地调整和优化。
3、建造者模式
建造者模式 - Builder Pattern 在 .NET 中,使用建造者模式来创建和组装复杂对象。例如,以下是 .NET 中使用的一些建造者模式示例:
- 在 ASP.NET Core 中,使用 Startup 类中的 ConfigureServices 和 Configure 方法来组装 Web 应用程序的服务和中间件;
- 在 Entity Framework 中,使用 DbContextOptionsBuilder 和 DbContext 构造函数来配置和创建数据上下文;
- 在 Prism 中,使用 IUnityContainer 接口和 RegisterType 方法来实现依赖项注入和对象构建。
Startup说明和示例
在ASP.NET Core中,使用 Startup 类中的 ConfigureServices 和 Configure 方法来组装 Web 应用程序的服务和中间件。
ConfigureServices 方法在应用程序启动时被调用,用于注册应用程序所需的服务。在这个方法中,可以通过依赖注入容器向应用程序添加服务,并配置服务的生命周期、作用域等属性。
Configure 方法则用于配置应用程序的 HTTP 请求管道。在这个方法中,可以添加各种中间件(Middleware)来处理请求和响应,例如路由、静态文件、认证授权、日志记录等。
下面是一个示例,展示了如何在 Startup 类中使用 ConfigureServices 和 Configure 方法来组装 Web 应用程序的服务和中间件:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public class Startup
{
// 注册应用程序所需的服务
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<MyDbContext>();
services.AddTransient<IMyService, MyServiceImpl>();
services.AddAuthentication();
}
// 配置应用程序的 HTTP 请求管道
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action}/{id?}");
});
}
}
在上面的示例中,我们定义了一个 Startup 类,并在 ConfigureServices 方法中注册了一些服务,例如 MVC 控制器、数据库上下文、自定义服务和身份认证等。在 Configure 方法中,我们添加了一些中间件(Middleware),例如开发者异常页、HTTPS 重定向、静态文件、路由、身份验证和授权等。
需要注意的是,ASP.NET Core 中的依赖注入容器通常使用泛型主机建立,而不是使用全局唯一的静态类。因此,在实际应用中,我们需要使用依赖注入容器的 API 来注册和解析服务,而不是直接使用静态类。
DbContextOptionsBuilder说明和示例
在 Entity Framework 中,使用 DbContextOptionsBuilder 和 DbContext 构造函数来配置和创建数据上下文(DbContext)。
DbContextOptionsBuilder 提供了一种链式调用的方式来配置数据上下文的选项,例如数据库连接字符串、日志记录、缓存策略等。通过 DbContextOptionsBuilder,我们可以灵活地配置数据上下文的行为和属性。
下面是一个示例,展示了如何使用 DbContextOptionsBuilder 来配置数据上下文的选项:
using Microsoft.EntityFrameworkCore;
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
}
public DbSet<MyEntity> MyEntities { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=MyDb;Integrated Security=True;MultipleActiveResultSets=True");
}
}
}
在上面的示例中,我们定义了一个继承自 DbContext 的数据上下文类 MyDbContext,并重载了它的构造函数和 OnConfiguring 方法。在构造函数中,我们接受了一个 DbContextOptions<MyDbContext> 类型的参数,并将其传递给基类的构造函数。在 OnConfiguring 方法中,我们检查当前是否已经配置了数据库选项,如果没有,则使用 SQL Server 数据库连接字符串配置选项。
需要注意的是,在实际应用中,我们通常不会在数据上下文中硬编码数据库连接字符串或其他配置选项,而是通过依赖注入容器和配置文件来进行动态配置。可以使用 IConfiguration 类来读取应用程序的配置文件,然后将配置选项传递给 DbContextOptionsBuilder 的方法中。
在使用数据上下文时,我们通常需要使用依赖注入容器来创建和解析数据上下文的实例。例如,在 ASP.NET Core 中,我们可以使用内置的依赖注入容器(IServiceProvider)来注册数据上下文,并在需要时从容器中解析出实例。
IUnityContainer说明和示例
在 Prism 中,可以使用 IUnityContainer 接口和 RegisterType 方法来实现依赖项注入和对象构建。
IUnityContainer 是 Unity 容器的接口,用于管理对象的生命周期和依赖关系。通过向容器注册类型并指定其依赖项,我们可以让容器自动解析对象和依赖项,并在需要时创建和销毁对象实例。
RegisterType 方法是 IUnityContainer 接口中的一个方法,用于向容器注册类型并指定其依赖项。通过 RegisterType 方法,我们可以指定对象的生命周期、作用域和其他属性,并配置容器如何处理对象和依赖项之间的关系。
下面是一个示例,展示了如何使用 IUnityContainer 和 RegisterType 方法来实现依赖项注入和对象构建:
using Microsoft.Practices.Unity;
public class MyService : IMyService
{
private readonly IDbContext dbContext;
private readonly ILogger logger;
public MyService(IDbContext dbContext, ILogger logger)
{
this.dbContext = dbContext;
this.logger = logger;
}
public void DoSomething()
{
// 使用 dbContext 和 logger 执行操作
}
}
// 在应用程序启动时注册服务
var container = new UnityContainer();
container.RegisterType<IDbContext, MyDbContext>(new ContainerControlledLifetimeManager());
container.RegisterType<ILogger, MyLogger>(new TransientLifetimeManager());
container.RegisterType<IMyService, MyService>(new TransientLifetimeManager());
// 在需要使用服务的地方解析服务
var myService = container.Resolve<IMyService>();
myService.DoSomething();
在上面的示例中,我们定义了一个类 MyService,它需要依赖于 IDbContext 和 ILogger 接口。我们使用 RegisterType 方法来向容器注册这些类型,并指定它们的生命周期为 Singleton 和 Transient。最后,在需要使用服务的地方,我们从容器中解析出实现了 IMyService 接口的对象,并调用其方法。
需要注意的是,在 Prism 中,通常建议我们使用基于接口的编程和依赖项注入,以便实现松耦合和可测试性。可以使用 RegisterInstance 方法来向容器注册单例对象或配置选项,也可以使用 RegisterFactory 方法来自定义对象创建逻辑。在实际应用中,我们通常会将容器的配置封装到模块或组件中,并通过 Prism 模块化架构来实现灵活的应用程序架构和组织。
4、原型模式
原型模式用于创建与现有对象相似的新对象,而不是通过类的构造函数或工厂方法创造对象。它适用于创建复杂对象的场景,其中构造函数的参数较多或创建对象的过程较为复杂,或者在创建对象的代价较高的情况下,通过复制已有的对象来创建新对象可以提高性能。
在 .NET 源代码中,有一个和原型模式相关的接口和一个实现。
这个接口就是 System.ICloneable 接口。它定义了一个名为 Clone() 的方法,用于创建当前对象的浅表副本。在使用原型模式时,通常我们会让对象实现 ICloneable 接口,并在 Clone() 方法中返回对象的浅表副本。
下面是一个示例:
public class MyObject : ICloneable
{
public int Id { get; set; }
public string Name { get; set; }
public object Clone()
{
return this.MemberwiseClone();
}
}
在上面的示例中,我们定义了一个类 MyObject,它实现了 ICloneable 接口,并在 Clone() 方法中返回 MemberwiseClone() 的结果。这个方法会创建并返回一个对象的浅表副本,也就是仅复制对象中的值类型成员和引用类型成员的引用,而不会复制整个对象图。
需要注意的是,在使用原型模式时,我们通常需要谨慎处理对象中的引用类型成员,以确保副本对象与原始对象的引用类型成员指向正确的对象。另外,由于 Clone() 方法只能创建浅表副本,如果需要深复制对象图,则需要使用其他的技术或模式来实现,例如序列化和反序列化、手动复制等。
总结
在使用这些创建型设计模式时,我们需要权衡其优缺点,并根据具体场景进行选择。同时,我们需要注意代码的可维护性和扩展性,并尽可能地遵循SOLID原则,以便更好地管理对象的创建过程。