跳到主要內容

C# DI 輕鬆學

C# DI 輕鬆學

C# DI 輕鬆學

DI 概念

  1. IoC (Inversion of Control) - 控制反轉

定義:IoC 是一種設計原則,將對象的控制權從內部轉移到外部。具體而言,應用程式不再負責控制物件的建立與管理,而是將這些工作交給外部的容器或框架。

作用:通過將控制權反轉,可以讓應用程式更具彈性、更容易測試,並提高可維護性。

舉例:

傳統設計:類別 A 自行建立類別 B 的實例(new 關鍵字)。

IoC 設計:類別 A 的類別 B 由外部容器或框架提供,類別 A 不直接控制物件的建立。

  1. DIP (Dependency Inversion Principle) - 相依反轉原則

定義:DIP 是 SOLID 原則中的一部分,主要指:

高層模組不應該依賴低層模組;兩者應依賴於抽象(介面或抽象類別),處處都介面。

抽象不應該依賴細節;細節應該依賴抽象。

目的:降低模組間的耦合性,使程式更靈活、更易於擴展。

舉例: 壞設計:類別 A 直接依賴類別 B。

好設計:類別 A 依賴於一個介面,由類別 B 實現該介面。

說明: 當使用 MSSQL 建立的 Repository 來存取資料表時,Service 類別會依賴該 Repository 類別進行資料操作。 也就是說,Service 與低層的 Repository 類別存在相依性。然而,假設有一天需要將 Repository 的實現改為使用 NoSQL 來存取資料,此時勢必需要重新改寫 Repository 的實作,而這種改變也會影響到 Service 類別的內部結構, 導致高層模組(Service)與低層模組(Repository)的耦合性過高。

為了解決這個問題,我們可以採用 DIP(依賴反轉原則) 的設計模式。在此模式下,我們會建立一個 IRepository 介面 ,定義高層模組與低層模組之間的契約。Service 類別只依賴於該介面,而不關心具體的實現。當需要更換資料存取方式時 ,只需實現一個新的 NoSqlRepository 類別(實作 IRepository 介面),並將其注入到 Service 中,這樣就可以不改動 Service 類別的結構,實現高層與低層的解耦。

  1. DI (Dependency Injection) - 相依性注入 定義:DI 是實現 IoC 的一種方法,將類別所需的相依性(例如其他類別的實例)從外部注入,而不是由類別內部自行建立。

類型:

Constructor Injection (建構子注入):透過建構函式注入相依性。

Property Injection (屬性注入):透過屬性設定注入相依性。

Method Injection (方法注入):透過方法參數注入相依性。

優點:

  • 提高程式的可測試性(可以注入 mock 對象)
  • 降低類別的耦合性。
  1. DI Container / IoC Container - 相依性注入容器 定義:DI 容器是一種工具或框架,用來管理物件的生命週期並自動處理相依性注入。 常見的 DI 容器包括:.NET 中的內建 DI 容器、Autofac、Ninject 等。

優點:

  • 管理物件的生命週期(Transient、Scoped、Singleton)。
  • 自動解決並注入相依性。
  • 減少手動初始化對象的麻煩。

  1. SL (Service Locator) - 服務定位器 定義:Service Locator 是一種設計模式,用來提供一個集中式的機制,允許應用程式動態地獲取所需的相依性或服務。

優點: 集中式相依性管理、

缺點:使用 Service Locator 的物件取得方式較為隱晦,可能會降低程式的可測試性與可維護性。

以下實作一個ServiceLocator來建立註冊物件、取得物件值,這樣方便集中式管理服務的物件,但是不好測試

void Main()
{
	
	int a =10 ; 
	string b ="hello" ;
	float c = 10.1f; 
	ISerivce d = new WifiSerivce();
	
	ServiceLocator.RegisterSerivce(a);
	ServiceLocator.RegisterSerivce(b);
	ServiceLocator.RegisterSerivce(c);
	ServiceLocator.RegisterSerivce(d);
	ServiceLocator.TraceServices();
	Console.WriteLine(ServiceLocator.GetService<int>());
}

//Service Locator
public static class ServiceLocator 
{
	private static readonly Dictionary<Type, object> _services = new Dictionary<System.Type, object>();

	public static void RegisterSerivce<T>(T service) 
	{
		var type = typeof(T);
		if (!_services.ContainsKey(type))
		{
			_services.Add(type, service);
		}
		
	}

	public static void TraceServices()
	{
		 

		foreach (KeyValuePair<Type, object> kvp in _services)
		{
			string[] trace = {kvp.Key.ToString(),kvp.Value.ToString()} ;
			
			 
			Console.WriteLine($"{trace[0]},{trace[1]}") ; 
		}
		 
	}
	
	public static T GetService<T>() 
	{
		var type = typeof(T);

		if (_services.TryGetValue(type, out var service)) 
		{
			return (T)service;
		}
		 throw new Exception($"Service of type {type.Name} not registered.");
	}
}


DI 設計模式

我使用電腦主機板來說明DI的設計模式,簡單來說主機板有很多插槽如顯示卡、音效卡、記憶體等等, 我宣告一個抽象介面為IPlugin,簡單實作DI三種模式

  • 建構子注入 VGAPlugin類別實作IPlugin介面,就擁有自己VGA的功能,主機板需要顯示卡功能時,利用建構子的方式注入

  • 方法注入 主機板上面的功能有音效、網路等等,我宣告一個IFunction介面來實作SoundEffectFunction、NetworkFunction, 使用方法注入的方式讓主機板取得該功能

  • 屬性注入 主機板的電源有不同的瓦特數,宣告一個IPower來實作不同的電源供應器,利用屬性方式注入

//DI 設計模式

void Main()
{
	//1. 建構子注入
	IPlugin plugin = new VGAPlugin();
	MotherBoard motherBoard = new MotherBoard(plugin);
	motherBoard.Drive();
	
	//2.方法注入
	IFunction function1 = new SoundEffectFunction();
	IFunction funciton2 = new NetworkFunction();
	
	motherBoard.AddFunction(function1);
	motherBoard.AddFunction(funciton2);
	
	motherBoard.ShowFucntion();

	//3.屬性注入
	IPower power = new SuperPower(2_000);
	motherBoard.power = power ; 
	motherBoard.ShowPower();
	
}

// You can define other methods, fields, classes and namespaces here

//1. 建構子注入
public class MotherBoard
{
	private IPlugin _plugin;


	public MotherBoard(IPlugin plugin)
	{
		if (plugin == null)
		{
			throw new ArgumentNullException(nameof(plugin));
		}

		_plugin = plugin;
	}

	public void Drive()
	{
		_plugin.Execute();
	}

	//2.方法注入
	private List<IFunction> functionList;
	public void AddFunction(IFunction function)
	{
		if (this.functionList is null)
		{
			this.functionList = new List<IFunction>();
		}

		this.functionList.Add(function);
	}

	public void ShowFucntion()
	{
		foreach (IFunction function in functionList)
		{
			function.Feature();
		}
	}

	//3.屬性注入
	public IPower power {set; get;}

	public void ShowPower()
	{
		Console.WriteLine($"Power {power.Power} watt"); 
	}
	
}


//介面IPlugin
public interface IPlugin 
{
	public void Execute() ;
}

//VGA介面實作
public class VGAPlugin : IPlugin
{
	public void Execute()
	{
		Console.WriteLine("Plugin VGA "); 
	}
}

//介面:IFunction
public interface IFunction 
{
	public void Feature();
};

public class SoundEffectFunction : IFunction
{
	public void Feature()
	{
		Console.WriteLine("audio effect enable");
	}
}

public class NetworkFunction : IFunction
{
	public void Feature()
	{
		Console.WriteLine("Network enable");
	}
}

//介面:IPower
public interface IPower 
{
	 int Power {get ;  set ;} 
}

public class SuperPower : IPower
{
	public int Power { get; protected set; }

	int IPower.Power   // Explicit implementation of the interface
	{
		get => Power;
		set => Power = value;
	}

	public SuperPower(int power) 
	{
		this.Power = power ;
		
	}
	 
}


參考

  1. https://medium.com/wenchin-rolls-around/淺入淺出-dependency-injection-ea672ba033ca

留言

這個網誌中的熱門文章

GSON基礎教學

GSON 前言 JSON是很常見的資料交換格式,在JAVA領域常用處理JSON的函式庫:GSON、FastXML和JSON-B,本章節會以GSON為主,學習目標如下 JSON格式說明 GSON 套件函式 GSON: 物件轉換JSON字串 GSON: JSON字串轉換物件 JSON 格式說明 JSON全名為JavaScript Object Notation,它是一種輕量級的資料交換格式,會大為流行的理由,主要是他比傳統用xml更輕巧且容易處理, JSON表達方式物件會用大括弧{},陣列則是用中括號[]。 用JSON字串來表達Employee的物件內容,由JSON字串可以知道物件name、age、sex和salary屬性。 JSON表示員工資料方式: {“name”:”Jack Bryant”, “age”:18, “sex”:”M”,”salary”:3500.00} JSON陣列表示方式: 跟我們使用JAVA的陣列方式類似,內容值可以是數字’、文字、布林、陣列、物件、null等等。 範例: 字串: [“紅”、”橙”、”黃”、”綠”、”青”、”藍”、”紫”} 布林: [true, true, false, false, true, true] GSON 套件函式 Gson為google所發布的函式庫,主要將物件與json字串之間的轉換時方便使用。當我們將JAVA物件轉換成JSON字串稱為 序列化 ,JSON字串轉換至JAVA物件稱為 反序列化 。 GSON: 物件轉換JSON字串 有了JSON基本概念後,我們進入本章重點,首先我們需要建立員工類別(Employee),定義如下 物件 屬性 員工類別 Employee name 名字 age 年紀 sex 性別 salary 薪水 /** * name:員工類別 */ public class Employee implements Serializable { //constructor public Employee(String name, double salary){ this.name = name; this.sala...

Python AI-問題集

Python AI-問題集 問題集 Jupyter Notebook執行ipywidgets會出現kernel死掉的錯誤發生(The kernel appears to have died) 解決方法 (1) 根據log檔來判斷問題: 例如:log訊息出現OMP: Error #15: Initializing libiomp5.dylib, but found libiomp5.dylib already initialized. (2) 根據問題關鍵字找出問題所在: 利用google查詢所遭遇到的問題,例如我把上面的問題上google查詢可以找到這篇的解法 https://blog.csdn.net/bingjianIT/article/details/86182096 (3)實作解法: 我實作下面解法後,就可以順利執行手寫辨識的程式. //在Python宣告時加入 import os os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE" 參考 https://blog.csdn.net/bingjianIT/article/details/86182096

PHP與Python搭配

今天介紹如何利用php網頁呼叫目錄下的python程式工作或是資料交換,本人整理的方法有兩種 使用system()、exec()、shell_exec()呼叫程式 (1) string system ( string return_var ] ) 參考網址 官網解釋system()用來執行外部命令,返回為印出的結果,passthru()跟system()類似但是它不會返回結果。 範例1. 利用system執行ls指定並顯示在網頁上,無法使用變數保留ls的結果 檔案名稱: psystem.php $jsondata= system("ls -al", $result); 結果: (2) exec() : string exec ( string output [, int &$return_var ]] ) 參考網址 範例2. 利用exec執行python程式並可以回傳json格式給前端網頁做處理並顯示。我們ptopy.php就是可以看到callpy()為執行py的函式,它執行完pyEx01.py會將結果給$jsondata變數,做後面json解析。 檔案名稱: ptopy.php function callpy() { $jsondata= exec("/usr/bin/python pyEx01.py"); return $jsondata ; } $jsondata= callpy(); echo $jsondata ; echo " " ; $obj = json_decode($jsondata) ; echo "name:".$obj-> { 'name'} .',' ; echo "id:".$obj-> { 'id'} ; 檔案名稱: pyEx01.py import sys ...