一、前言
在實際的開發過程中,我們經常會遇到這樣的情況,在進行調試分析問題的時候,經常需要記錄日志信息,這時可以采用輸出到控制台。
因此,我們通常會定義一個日志類,來實現輸出日志。
定義一個生成驗證的邏輯處理方法,
public class Logger
{
public void AddLogger()
{
Console.WriteLine("日志新增成功!");
}
}
然后在控制台中輸出結果。
static void Main(string[] args)
{
Logger logger = new Logger();
logger.AddLogger();
Console.Read();
}
看着實現的結果,我們以為完成任務了,當其實這才是剛剛開始。
二、開始
相信大家在開發中,都會遇到這種情況,有時需要控制台輸出,但也有可能要你輸出到文本,數據庫或者遠程服務器等等,這些都是有可能。因此最初采用直接輸出到控制台已經不能滿足條件了,所以我們需要將上述代碼進行重寫改造,實現不同的輸出方式。
2.1 第一種方式
2.1.1 控制台方式
用到控制台方式輸出的時候:
/// <summary>
/// 控制台輸出
/// </summary>
public class ConsoleLogger
{
public void AddLogger()
{
Console.WriteLine("控制台輸出:日志新增成功!");
}
}
定義一個獲取輸出日志的處理邏輯類,因此我們需要定義ConsoleLogger
類並初始化
/// <summary>
/// 定義一個輸出日志的統一類
/// </summary>
public class LoggerServer
{
private readonly ConsoleLogger consoleLogger = new ConsoleLogger();//添加一個私有變量的對象 個私有變量的數字對象
public void AddLogger()
{
consoleLogger.AddLogger();
}
}
控制台輸出結果:
static void Main(string[] args)
{
LoggerServer loggerServer = new LoggerServer();
loggerServer.AddLogger();
Console.Read();
}
控制台輸出:日志新增成功!
2.1.2 文本輸出
當用到文本輸出日志的時候,我們再次定義一個生成文本日志的方式
/// <summary>
/// 文本輸出
/// </summary>
public class FileLogger
{
public void AddLogger()
{
Console.WriteLine("文本輸出:日志新增成功!");
}
}
然后再次定義一個獲取驗證的處理邏輯類,因此我們需要定義ImageVerification
類並初始化
/// <summary>
/// 定義一個輸出日志的統一類
/// </summary>
public class LoggerServer
{
private readonly FileLogger fileLogger = new FileLogger();//添加一個私有變量的對象
public void AddLogger()
{
fileLogger.AddLogger();
}
}
最后輸出結果:
文本輸出:日志新增成功!
通過以上的方式,我們實現了不同方式輸出不同日志方式,但是仔細觀察可以發現,這種方式不是一種良好的軟件設計方式。
所以可能有人會改成下面第二種方式,以接口來實現。
2.2 第二種方式
定義一個ILogger
接口並聲明一個AddLogger
方法
public interface ILogger
{
void AddLogger();
}
在控制台輸出方式中ConsoleLogger
類,實現ILogger
接口
/// <summary>
/// 控制台輸出
/// </summary>
public class ConsoleLogger : ILogger
{
public void AddLogger()
{
Console.WriteLine("控制台輸出:日志新增成功!");
}
}
在文本輸出日志方式FileLogger
類中,實現ILogger
接口
/// <summary>
/// 文本輸出
/// </summary>
public class FileLogger : ILogger
{
public void AddLogger()
{
Console.WriteLine("文本輸出:日志新增成功!");
}
}
定義一個統一的輸出日志類LoggerServer
類
/// <summary>
/// 定義一個統一的輸出日志類
/// </summary>
public class LoggerServer
{
private readonly ConsoleLogger consoleLogger = new ConsoleLogger();//添加一個私有變量的對象
//private readonly FileLogger fileLogger = new FileLogger();//添加一個私有變量的對象
public void AddLogger()
{
_logger.AddLogger();
}
}
最后,控制台調用輸出:
static void Main(string[] args)
{
LoggerServer loggerServer = new LoggerServer();
loggerServer.AddLogger();
Console.Read();
}
控制台輸出:日志新增成功!
雖然第二種方式中,采用了接口來實現,降低耦合,但還是沒有達到我們想要的效果,因此以上的兩種方式,都不是很好的軟件設計方式。
代碼可拓展性比較差,以及組件之間存在高度耦合,違反了開放關閉原則,在設計的時候,也應當考慮對擴展開放,對修改關閉。
2.3 思考
既然要遵循開放關閉原則,那上面的寫法,選擇采用控制台日志輸出方式所需要的ConsoleLogger
創建和依賴都是在統一的日志類LoggerServer
內部進行的,既然要使內部不直接存在綁定依賴,那有沒有什么方式從外部傳遞的方式給LoggerServer
類內部引用使用呢?
三、引入
3.1 依賴注入
依賴注入 : 它提供一種機制,將需要依賴對象的引用傳遞給被依賴對象。
下面我們先看看具體的幾種注入方式,再做小結說明。
3.1.1 構造函數注入
在LoggerServer
類中,定義一個私有變量_logger
, 然后通過構造函數的方式傳遞依賴
public class LoggerServer
{
private ILogger _logger; //1. 定義私有變量
//2.構造函數
public LoggerServer(ILogger logger)
{
//3.注入 ,傳遞依賴
this._logger = logger;
}
public void AddLogger()
{
_logger.AddLogger();
}
}
通過控制台程序調用,先在外部創建依賴對象,而后通過構造的方式注入依賴
static void Main(string[] args)
{
#region 構造函數注入
// 注入控制台輸出方式
// 外部創建依賴的對象 -> ConsoleLogger
ConsoleLogger console = new ConsoleLogger();
// 通過構造函數注入 -> LoggerServer
LoggerServer loggerServer1 = new LoggerServer(console);
loggerServer1.AddLogger();
// 注入 文件輸出方式
FileLogger file = new FileLogger();
// 通過構造函數注入 -> LoggerServer
LoggerServer loggerServer2 = new LoggerServer(file);
loggerServer2.AddLogger();
#endregion
Console.Read();
}
輸出:
控制台輸出:日志新增成功!
文本輸出:日志新增成功!
顯然的發現,通過這種構造函數注入的方式,在外部定義依賴,降低內部的耦合度,同時也增加了擴展性,只需從外部修改依賴,就可以實現不同的驗證結果。
3.1.2 屬性注入
即通過定義一個屬性來傳遞依賴
/// <summary>
/// 定義一個輸出日志的統一類
/// </summary>
public class LoggerServer
{
//1.定義一個屬性,可接收外部賦值依賴
public ILogger _logger { get; set; }
public void AddLogger()
{
_logger.AddLogger();
}
}
通過控制台,定義不同的方式,通過不同依賴賦值,實現不同的驗證結果:
static void Main(string[] args)
{
#region 屬性注入
// 注入 控制台輸出方式
//外部創建依賴的對象 -> ConsoleLogger
ConsoleLogger console = new ConsoleLogger();
LoggerServer loggerServer1 = new LoggerServer();
//給內部的屬性賦值
loggerServer1._logger = console;
loggerServer1.AddLogger();
// 注入 文件輸出方式
//外部創建依賴的對象 -> FileLogger
FileLogger file = new FileLogger();
LoggerServer loggerServer2 = new LoggerServer();
//給內部的屬性賦值
loggerServer2._logger = file;
loggerServer2.AddLogger();
#endregion
Console.Read();
}
輸出
控制台輸出:日志新增成功!
文本輸出:日志新增成功!
3.1.3 接口注入
先定義一個接口,包含一個設置依賴的方法。
public interface IDependent
{
void SetDepend(ILogger logger);//設置依賴項
}
這個與之前的注入方式不一樣,而是通過在類中繼承並實現這個接口。
public class LoggerServer : IDependent
{
private ILogger _logger;
// 繼承接口,並實現依賴項方法,注入依賴
public void SetDepend(ILogger logger)
{
_logger = logger;
}
public void AddLogger()
{
_logger.AddLogger();
}
}
通過調用,直接通過依賴項方法,傳遞依賴
static void Main(string[] args)
{
#region 接口注入
// 注入 控制台輸出方式
//外部創建依賴的對象 -> ConsoleLogger
ConsoleLogger console = new ConsoleLogger();
LoggerServer loggerServer1 = new LoggerServer();
//給內部賦值,通過接口的方式傳遞
loggerServer1.SetDepend(console);
loggerServer1.AddLogger();
//注入 文件輸出方式
//外部創建依賴的對象 -> FileLogger
FileLogger file = new FileLogger();
LoggerServer loggerServer2 = new LoggerServer();
//給內部賦值,通過接口的方式傳遞
loggerServer2.SetDepend(file);
loggerServer2.AddLogger();
#endregion
Console.Read();
}
輸出
控制台輸出:日志新增成功!
文本輸出:日志新增成功!
3.1.4 小結
依賴注入(DI—Dependency Injection)
它提供一種機制,將需要依賴對象的引用傳遞給被依賴對象通過DI,我們可以在LoggerServer
類在外部ConsoleLogger
對象的引用傳遞給LoggerServer
類對象。 注入某個對象所需要的外部資源(包括對象、資源、常量數據)
依賴注入把對象的創造交給外部去管理,很好的解決了代碼緊耦合的問題,是一種讓代碼實現松耦合的機制。
松耦合讓代碼更具靈活性,能更好地應對需求變動,以及方便單元測試。
3.2 IOC
控制反轉(Inversion of Control,縮寫為IoC),在面向對象編程中,是一種軟件設計模式,教我們如何設計出更優良,更具有松耦合的程序。
在上文的例子中,我們發現如果在獲取對象的過程中靠類內部主動創建依賴對象,則會導致代碼直接高度耦合並且期存在難以維護這種隱患,所以為了避免這種問題,我們采用了由外部提供依賴對象,內部對象類被創建的時候,將其所依賴的對象引用傳遞給它,實現了依賴被注入到對象中去。
通俗的說明:
在類A中用到了類B的對象時候,一般情況下,需要在A的代碼中顯式的new一個B的對象。這種方式都是通過我們自己主動創建出來的,創建合作對象的主動權在自己手上,自己需要哪個對象,就主動去創建,創建對象的主動權和創建時機是由自己把控的,而這樣就會使得對象間的耦合度高了,A對象需要使用對象B來共同完成一件事,A要使用B,那么A就對B產生了依賴,也就是A和B之間存在一種耦合關系,並且是緊密耦合在一起。
public class A { private B b = new B();//主動的new一個B的對象。主動創建出來 public void Get() { B.Create(); } }
采用依賴注入技術之后,A的代碼只需要定義一個私有的B對象,不需要直接new來獲得這個對象,而是通過相關的容器控制程序來將B對象在外部new出來並注入到A類里的引用中。現在創建對象而是有第三方控制創建,你要什么對象,它就給你什么對象,依賴關系就變了,原先的依賴關系就沒了,A和B之間耦合度也就減少了。
public class A { private B b;//外部new出來, 注入到引用中 public void Get() { B.Create(); } }
3.3 關系
控制反轉(IoC) 是一種軟件設計的模式,指導我們設計出更優良,更具有松耦合的程序,
而具體的實現方式有依賴注入和依賴查找。
在這一篇主要說的是常用的依賴注入方式。
你在實際開發中,可能還會聽到另一名詞叫 IoC容器,這其實是一個依賴注入的框架,
用來映射依賴,管理對象創建和生存周期。 (在后續篇章會具體說明)
四、思考
說到依賴,就想到依賴注入和工廠模式這兩者的區別?
這是網上有一個對比例子:
工廠設計模式 | 依賴注入 | |
---|---|---|
對象創建 | 它用於創建對象。我們有單獨的Factory類,其中包含創建邏輯。 | 它負責創建和注入對象。 |
對象的狀態 | 它負責創建有狀態對象。 | 負責創建無狀態對象 |
運行時/編譯時間 | 在編譯時創建對象 | 在運行時配置對象 |
代碼變更 | 如果業務需求發生變化,則可能會更改對象創建邏輯。 | 無需更改代碼 |
機制 | 類依賴於工廠方法,而工廠方法又依賴於具體類 | 父對象和所有從屬對象可以在單個位置創建 |
好啦,這篇文章就先講述到這里吧,在后續篇章中會對常用的IOC容器進行使用說明,希望對大家有所幫助。
如果有不對的或不理解的地方,希望大家可以多多指正,提出問題,一起討論,不斷學習,共同進步。🤣