.net core 中的經典設計模式的應用

WeihanLi發表於2020-08-23

.net core 中的經典設計模式的應用

Intro

前段時間我們介紹了23種設計模式,今天來分享一下 .net core 原始碼中我覺得比較典型的設計模式的應用

例項

責任鏈模式

asp.net core 中介軟體的設計就是責任鏈模式的應用和變形,

每個中介軟體根據需要處理請求,並且可以根據請求資訊自己決定是否傳遞給下一個中介軟體,我也受此啟發,封裝了一個 PipelineBuilder 可以輕鬆構建中介軟體模式程式碼,可以參考這篇文章 https://www.cnblogs.com/weihanli/p/12700006.html

中介軟體示例:

app.UseStaticFiles();

app.UseResponseCaching();
app.UseResponseCompression();

app.UseRouting();

app.UseCors(builder => builder.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());

app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    endpoints.MapControllerRoute(name: "areaRoute", "{area:exists}/{controller=Home}/{action=Index}");
    endpoints.MapDefaultControllerRoute();
});

PipelineBuilder 實際示例:

var requestContext = new RequestContext()
{
    RequesterName = "Kangkang",
    Hour = 12,
};

var builder = PipelineBuilder.Create<RequestContext>(context =>
        {
            Console.WriteLine($"{context.RequesterName} {context.Hour}h apply failed");
        })
        .Use((context, next) =>
        {
            if (context.Hour <= 2)
            {
                Console.WriteLine("pass 1");
            }
            else
            {
                next();
            }
        })
        .Use((context, next) =>
        {
            if (context.Hour <= 4)
            {
                Console.WriteLine("pass 2");
            }
            else
            {
                next();
            }
        })
        .Use((context, next) =>
        {
            if (context.Hour <= 6)
            {
                Console.WriteLine("pass 3");
            }
            else
            {
                next();
            }
        })
    ;
var requestPipeline = builder.Build();
foreach (var i in Enumerable.Range(1, 8))
{
    Console.WriteLine();
    Console.WriteLine($"--------- h:{i} apply Pipeline------------------");
    requestContext.Hour = i;
    requestPipeline.Invoke(requestContext);
    Console.WriteLine("----------------------------");
    Console.WriteLine();
}

建造者模式

asp.net core 中的各種 BuilderHostBuilder/ConfigurationBuilder 等,這些 Builder 大多既是 Builder 又是 Director,Builder 本身知道如何構建最終的 Product(Host/Configuration)

var host = new HostBuilder()
    .ConfigureAppConfiguration(builder =>
    {
        // 註冊配置
        builder
            .AddInMemoryCollection(new Dictionary<string, string>()
            {
                {"UserName", "Alice"}
            })
            .AddJsonFile("appsettings.json")
            ;
    })
    .ConfigureServices((context, services) =>
    {
        // 註冊自定義服務
        services.AddSingleton<IIdGenerator, GuidIdGenerator>();
        services.AddTransient<IService, Service>();
        if (context.Configuration.GetAppSetting<bool>("XxxEnabled"))
        {
            services.AddSingleton<IUserIdProvider, EnvironmentUserIdProvider>();
        }
    })
    .Build()
    ;

工廠模式

依賴注入框架中有著大量的工廠模式的程式碼,註冊服務的時候我們可以通過一個工廠方法委託來獲取服務例項,

依賴注入的本質就是將物件的建立交給 IOC 容器來處理,所以其實 IOC 容器本質就是一個工廠,從 IOC 中獲取服務例項的過程就是工廠建立物件的過程,只是會根據服務的生命週期來決定是建立新物件還是返回已有物件。

services.AddSingleton(sp => new Svc2(sp.GetRequiredService<ISvc1>(), "xx"));

單例模式

在 dotnet 中有一個 TimeQueue 的型別,純正的餓漢模式的單例模式程式碼

class TimerQueue
{
    #region singleton pattern implementation

    // The one-and-only TimerQueue for the AppDomain.
    static TimerQueue s_queue = new TimerQueue();

    public static TimerQueue Instance
    {
        get { return s_queue; }
    }

    private TimerQueue()
    {
        // empty private constructor to ensure we remain a singleton.
    }

    #endregion

    // ...
}

https://referencesource.microsoft.com/#mscorlib/system/threading/timer.cs,49

在 dotnet 原始碼中還有一些懶漢式的單例模式

使用 Interlocked 原子操作

internal class SimpleEventTypes<T>
    : TraceLoggingEventTypes
{
    private static SimpleEventTypes<T> instance;

    internal readonly TraceLoggingTypeInfo<T> typeInfo;

    private SimpleEventTypes(TraceLoggingTypeInfo<T> typeInfo)
        : base(
            typeInfo.Name,
            typeInfo.Tags,
            new TraceLoggingTypeInfo[] { typeInfo })
    {
        this.typeInfo = typeInfo;
    }

    public static SimpleEventTypes<T> Instance
    {
        get { return instance ?? InitInstance(); }
    }

    private static SimpleEventTypes<T> InitInstance()
    {
        var newInstance = new SimpleEventTypes<T>(TraceLoggingTypeInfo<T>.Instance);
        Interlocked.CompareExchange(ref instance, newInstance, null);
        return instance;
    }
}

另外一個示例,需要注意,下面這種方式不能嚴格的保證只會產生一個例項,在併發較高的情況下可能不是同一個例項,這也可以算是工廠模式的一個示例

static internal class ConfigurationManagerHelperFactory 
{
    private const string ConfigurationManagerHelperTypeString = "System.Configuration.Internal.ConfigurationManagerHelper, " + AssemblyRef.System;

    static private volatile IConfigurationManagerHelper s_instance;

    static internal IConfigurationManagerHelper Instance {
        get {
            if (s_instance == null) {
                s_instance = CreateConfigurationManagerHelper();
            }

            return s_instance;
        }
    }

    [ReflectionPermission(SecurityAction.Assert, Flags = ReflectionPermissionFlag.MemberAccess)]
    [SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts", Justification = "Hard-coded to create an instance of a specific type.")]
    private static IConfigurationManagerHelper CreateConfigurationManagerHelper() {
        return TypeUtil.CreateInstance<IConfigurationManagerHelper>(ConfigurationManagerHelperTypeString);
    }
}

原型模式

dotnet 中有兩個資料結構 Stack/Queue 這兩個資料都實現了 ICloneable 介面,內部實現了深複製
來看 StackClone 方法實現:

public virtual Object Clone()
{
    Contract.Ensures(Contract.Result<Object>() != null);
 
    Stack s = new Stack(_size);
    s._size = _size;
    Array.Copy(_array, 0, s._array, 0, _size);
    s._version = _version;
    return s;
}

詳細可以參考: https://referencesource.microsoft.com/#mscorlib/system/collections/stack.cs,6acda10c5f8b128e

享元模式

string intern(字串池),以及 Array.Empty<int>()/Array.Empty<string>()

策略模式

asp.net core 中的認證和授權,我覺得就是策略模式的應用,在使用 [Authorize] 的時候會使用預設的 policy,也可以指定要使用的策略 [Authorize("Policy1")] 這樣就會使用另外一種策略 Policy1,policy 還是比較簡單的

policy 是用來根據使用者的認證資訊來控制授權訪問的,而認證則是根據當前上下文(請求上下文、執行緒上下文、環境上下文等)的資訊進行認證從而獲取使用者資訊的過程

而不同的認證模式(Cookie/JWT/自定義Token等)其實是不同的處理方法,也就是策略模式中不同的演算法實現,指定哪種認證模式,就是使用哪種演算法實現來獲取使用者資訊

觀察者模式

常使用事件(event)進行解耦,外部程式碼通過訂閱事件來解耦,實現對內部狀態的觀察

Process 類中有很多事件,可以用來捕獲另一個程式中的輸出,錯誤等

public event DataReceivedEventHandler OutputDataReceived;

public event DataReceivedEventHandler ErrorDataReceived;

通常這兩個事件我們就可以獲取到另外一個程式中的輸出資訊,除此之外還有很多的類在使用事件,相信你也用過很多

組合模式

WPF、WinForm 中都有控制元件的概念,這些控制元件的設計屬於是組合模式的應用,所有的控制元件都會繼承於某一個共同的基類, 使得單個物件和組合物件都可以看作是他們共同的基類物件

迭代器模式

c# 中定義了迭代器模式,原始定義:

// 聚集抽象
public interface IEnumerable
{
    /// <summary>Returns an enumerator that iterates through a collection.</summary>
    /// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
    IEnumerator GetEnumerator();
}

// 迭代器抽象
public interface IEnumerator
{
    /// <summary>Advances the enumerator to the next element of the collection.</summary>
    /// <returns>
    /// <see langword="true" /> if the enumerator was successfully advanced to the next element; <see langword="false" /> if the enumerator has passed the end of the collection.</returns>
    /// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created.</exception>
    bool MoveNext();

    /// <summary>Gets the element in the collection at the current position of the enumerator.</summary>
    /// <returns>The element in the collection at the current position of the enumerator.</returns>
    object Current { get; }

    /// <summary>Sets the enumerator to its initial position, which is before the first element in the collection.</summary>
    /// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created.</exception>
    void Reset();
}

Array 和 List 各自實現了自己的迭代器,感興趣可以去看下原始碼

More

.net core 中的設計模式應用還有很多,不僅上面提到的這幾個模式,也不僅僅是我所提到的這幾個地方

上面有一些示例是直接用的 dotnet framework 中的原始碼,因為有很多程式碼都是類似的,用的 https://referencesource.microsoft.com 的原始碼

以上均是個人理解,如果有錯誤還望指出,十分感謝,歡迎補充更多設計模式應用的原始碼例項

Reference

相關文章