解決“客戶機作業系統已禁用CPU”的問題
前言
文中內容是結合多篇文章的學習筆記,記錄下來以免時間久了忘記。
面向物件設計(OOD)有助於我們開發出高效能、易擴充套件以及易複用的程式。其中,OOD有一個重要的思想那就是依賴倒置原則(DIP),並由此引申出IOC、DI以及IOC容器等概念。通過本文我們將一起學習這些概念,並理清他們之間微妙的關係。
概念
依賴倒置原則(DIP):一種軟體架構設計的原則(抽象概念),依賴抽象而不是依賴細節。
控制反轉(IOC):一種反轉流、依賴和介面的方式(DIP的具體實現方式),通過抽象獲取細節,實現這個動作的時候就是控制反轉。
依賴注入(DI):IOC的一種實現方式,用來反轉依賴(IOC的具體實現方式),DI是實現IOC的一種技術手段。
IOC容器:依賴注入的框架,用來對映依賴,管理物件建立和生存週期(DI框架),IOC容器實際上是一個DI框架,它能簡化我們的工作量。它包含以下幾個功能:
- 動態建立、注入依賴物件。
- 管理物件生命週期。
- 對映依賴關係。
圖示
依賴
以三層架構為例,從上到下,UI->BLL->DAL這就是依賴,這種最基本的依賴是依賴於細節的,耦合度較高,DAL如果修改了程式碼,則底層到高層的程式碼都需要進行改變。所以需要進行解耦,由此引出依賴倒置原則(DIP)。
依賴倒置DIP
耦合關係就是依賴關係,如果依賴關係相當繁雜,牽一髮而動全身,很難維護;依賴關係越少,耦合關係就越低,系統就越穩定,所以我們要減少依賴。
幸虧Robert Martin大師提出了面向物件設計原則----依賴倒置原則:
- A. 上層模組不應該依賴於下層模組,它們共同依賴於一個抽象。
- B. 抽象不能依賴於具象,具象依賴於抽象。
理解:A.上層是使用者,下層是被使用者,這就導致的結果是上層依賴下層了,下層變動了,自然就會影響到上層了,導致系統不穩定,甚至是牽一髮而動全身。那怎麼減少依賴呢?就是上層和下層都去依賴另一個抽象,這個抽象比較穩定,整個就來說就比較穩定了。
B.面向物件程式設計時面向抽象或者面向藉口程式設計,抽象一般比較穩定,實現抽象的具體肯定是要依賴抽象的,抽象不應該去依賴別的具體,應該依賴抽象。
依賴倒置原則,它轉換了依賴,高層模組不依賴於低層模組的實現,而低層模組依賴於高層模組定義的介面
Bob Martins對DIP的定義:
高層模組不應依賴於低層模組,兩者應該依賴於抽象。
抽象不不應該依賴於實現,實現應該依賴於抽象。
控制反轉IOC
DIP是一種 軟體設計原則,它僅僅告訴你兩個模組之間應該如何依賴,但是它並沒有告訴如何做。IOC則是一種 軟體設計模式,它告訴你應該如何做,來解除相互依賴模組的耦合。控制反轉(IOC),它為相互依賴的元件提供抽象,將依賴(低層模組)物件的獲得交給第三方(系統)來控制,即依賴物件不在被依賴模組的類中直接通過new來獲取。
控制反轉IOC是Inversion of Control的縮寫,是說物件的控制權進行轉移,轉移到第三方,比如轉移交給了IOC容器,它就是一個建立工廠,你要什麼物件,它就給你什麼物件,有了IOC容器,依賴關係就變了,原先的依賴關係就沒了,它們都依賴IOC容器了,通過IOC容器來建立它們之間的關係。
軟體設計原則:原則為我們提供指南,它告訴我們什麼是對的,什麼是錯的。它不會告訴我們如何解決問題。它僅僅給出一些準則,以便我們可以設計好的軟體,避免不良的設計。一些常見的原則,比如DRY、OCP、DIP等。
軟體設計模式:模式是在軟體開發過程中總結得出的一些可重用的解決方案,它能解決一些實際的問題。一些常見的模式,比如工廠模式、單例模式等等。
依賴注入(DI)
上面說到控制反轉,是一個思想概念,但是也要具體實現的,上面的配置檔案也是一種實現方式。依賴注入提出了具體的思想。
依賴注入DI是Dependency Injection縮寫,它提出了“哪些東東的控制權被反轉了,被轉移了?”,它也給出了答案:“依賴物件的建立獲得被反轉”。
所謂依賴注入,就是由IoC容器在執行期間,動態地將某種依賴關係注入到物件之中。
依賴注入(DI),它提供一種機制,將需要依賴(低層模組)物件的引用傳遞給被依賴(高層模組)物件。
IOC容器
IOC中最基本的技術就是“反射(Reflection)”程式設計,通俗來講就是根據給出的類名(字串方式)來動態地生成物件。這種程式設計方式可以讓物件在生成時才決定到底是哪一種物件。反射的應用是很廣泛的,很多的成熟的框架,比如象Java中的hibernate、Spring框架,.Net中 NHibernate、Spring.NET框架都是把“反射”做為最基本的技術手段。
我們可以把IOC容器的工作模式看做是工廠模式的昇華,可以把IOC容器看作是一個工廠,這個工廠裡要生產的物件都在配置檔案中給出定義,然後利用程式語言的的反射程式設計,根據配置檔案中給出的類名生成相應的物件。從實現來看,IOC是把以前在工廠方法裡寫死的物件生成程式碼,改變為由配置檔案來定義,也就是把工廠和物件生成這兩者獨立分隔開來,目的就是提高靈活性和可維護性。
動手實現一個無限層級的IOC容器
IFishServiceCollection
public interface IFishServiceCollection
{
/// <summary>
/// 新增具有實現的在serviceType中指定的型別的臨時服務
/// 在ImplementationType中指定的型別為指定的Microsoft.Extensions.DependencyInjection.IServiceCollection。
/// </summary>
/// <param name="serviceType">要註冊的服務的型別</param>
/// <param name="implementationType">服務的實現型別</param>
/// <param name="shortName">服務的實現類型別名</param>
void AddTransient(Type serviceType,Type implementationType,string shortName =null);
/// <summary>
/// 獲取指定型別的服務物件
/// </summary>
/// <typeparam name="T">要獲取的服務型別</typeparam>
/// <returns>生產的服務</returns>
T GetService<T>(string shortName=null);
}
FishServiceCollection
public class FishServiceCollection : IFishServiceCollection
{
private Dictionary<string, Type> richarDictionary = new Dictionary<string, Type>();
//拼接要註冊的服務的型別和別名
private string GetKey(Type type, string shortName) => $"{type.FullName}___{shortName}";
//{
// return $"{type.FullName}___{shortName}";
//}
/// <summary>
/// 註冊服務--其實就是把抽象和具體的對映關係儲存起來
/// </summary>
/// <param name="serviceType">要註冊的服務的型別(抽象:介面等)</param>
/// <param name="implementationType">服務的實現型別(細節:例項)</param>
/// <param name="shortName">別名</param>
public void AddTransient(Type serviceType, Type implementationType,string shortName)
{
//使用別名完成多實現功能
string Key = GetKey(serviceType,shortName);
richarDictionary.Add(Key, implementationType);
//01 將物件與細節儲存在字典內
//richarDictionary.Add(serviceType.FullName,implementationType);
}
/// <summary>
/// 獲取服務例項
/// </summary>
/// <typeparam name="T">要獲取的服務型別(抽象:介面)</typeparam>
/// <returns>生產的服務</returns>
public T GetService<T>(string shortName)
{
#region 第一版 簡單層級
////02 通過字典取出具體的型別
//Type type = richarDictionary[typeof(T).FullName];
////03 通過反射建立例項
////return (T)Activator.CreateInstance(type);//如果需要構造的物件中建構函式引數 不為空;就報錯了 呼叫的無引數的建構函式
////1.先取構造 當前類建構函式中需要的引數的物件 確定構造當前物件使用那個建構函式(預設選擇引數最多的建構函式)
//var contrArray = type.GetConstructors();//獲取Type的所有建構函式
////2.根據建構函式引數數量降序配列,再取第一個==獲取到建構函式引數最多的這一個
//var ctor = contrArray.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
////3.準備建構函式的引數
//List<object> prarlist = new List<object>();
//foreach (ParameterInfo para in ctor.GetParameters())
//{
// Type paraType = para.ParameterType;
// Type parTargetType = richarDictionary[paraType.FullName];
// #region 思路引導
// //1* 確定使用那個建構函式
// //var contrArray1 = parTargetType.GetConstructors();
// //2* 根據建構函式引數數量降序排序,再取第一個==獲取到建構函式引數最多的這一個
// //var ctor1 = contrArray.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
// //3* 準備建構函式的引數········
// //不知道要構造的物件的建構函式具體依賴多少個層級;
// //永遠寫不完
// //怎麼辦?使用遞迴
// #endregion
// var target = Activator.CreateInstance(parTargetType);
// prarlist.Add(target);
//}
////4.帶引數的構造當前物件
//return (T)Activator.CreateInstance(type, prarlist.ToArray());
#endregion
#region 多層級
Type type = richarDictionary[GetKey(typeof(T), shortName)];
return (T)this.GetService(type);
#endregion
}
private object GetService(Type type)
{
#region 建構函式注入
//1.先去構造 當前類建構函式中需要的引數的物件 確定構造當前物件使用那個建構函式(預設選擇引數最多的建構函式)
var contrArray = type.GetConstructors();//獲取Type的所有建構函式
//2.根據建構函式引數數量降序配列,再取第一個==獲取到建構函式引數最多的這一個
//2.1 這是預設情況下,選擇建構函式引數最多的,但是有特殊情況,如果就需要選擇一個自己指定的建構函式呢?
//2.2 需求:需要通過自己指定選擇不同的建構函式,如果沒有指定,就選擇預設
//2.3 如何解決呢?=>通過特性解決
//2.4 通過特性支援,可以在建構函式上,標記一個特性,在選擇建構函式的時候,就判斷,如果標記有特性,就選擇標記有特性的這個建構函式,如果沒有特性,就選擇預設最多的建構函式
//2.4.1 找標記的有特性的建構函式
ConstructorInfo ctor = contrArray.Where(a => a.IsDefined(typeof(SelectConstructorAttribute))).FirstOrDefault();
if (ctor == null)
{
ctor = contrArray.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
}
//3.準備建構函式的引數
List<object> prarlist = new List<object>();
foreach (ParameterInfo para in ctor.GetParameters())
{
Type paraType = para.ParameterType;
string shortName = GetKey(paraType,null);
Type parTargetType = richarDictionary[shortName];
//var target = Activator.CreateInstance(parTargetType);
var target = this.GetService(parTargetType);//遞迴
prarlist.Add(target);
}
#endregion
#region 物件注入
//構造物件
object oInstance = Activator.CreateInstance(type, prarlist.ToArray());
#endregion
#region 屬性注入
//找出物件中需要做注入的屬性,然後例項化屬性對應型別的物件,賦值給屬性
//1.找出所有的屬性;
//2.找出指定標識,按照有標識的去給屬性做注入 標識--Attribute
//拿出所有屬性,使用特性判斷需要注入的屬性
var attArray = type.GetProperties().Where(p=>p.IsDefined(typeof(PropertyInjeictionAttribute),true));
foreach (var prop in attArray)
{
//找到具體型別
Type propType = prop.PropertyType;
string shortName = GetKey(propType,null);
Type propImType = richarDictionary[shortName];
//構造
object propInstance = GetService(propImType);
prop.SetValue(oInstance,propInstance);
}
#endregion
#region 方法注入
foreach (var method in type.GetMethods().Where(p => p.IsDefined(typeof(MethodImplAttributes), true)))
{
List<object> paraInjectionList = new List<object>();
foreach (var para in method.GetParameters())
{
Type paraType = para.ParameterType;
string shortName = GetKey(paraType, null);
Type paraImType = richarDictionary[shortName];
object mInstance = GetService(paraImType);
paraInjectionList.Add(mInstance);
}
method.Invoke(oInstance,paraInjectionList.ToArray());
}
#endregion
//多實現注入--一個介面多實現後,希望在都註冊以後,能夠選擇性的獲取具體的例項
//1.當然要標識一些,一個介面的兩個實現在註冊時候必然要標識下
//2.在獲取的時候,就可以通過這個標識來構造對應的物件
//3.起個別名
return oInstance;
}
}
MethodInjeictionAttribute
[AttributeUsage(AttributeTargets.Method)]
public class MethodInjeictionAttribute:Attribute
{
}
PropertyInjeictionAttribute
[AttributeUsage(AttributeTargets.Property)]
public class PropertyInjeictionAttribute:Attribute
{
}
SelectConstructorAttribute
[AttributeUsage(AttributeTargets.Constructor)]
public class SelectConstructorAttribute:Attribute
{
}