抽象化(英語:Abstraction)是指以縮減一個概念或是一個現象的資訊含量來將其廣義化(Generalization)的過程,主要是為了只保存和一特定目的有關的資訊。例如,將一個皮製的足球抽象化成一個球,只保留一般球的屬性和行為等資訊。相似地,亦可以將快樂抽象化成一種情緒,以減少其在情緒中所含的資訊量。
Abstraction describe what
e.g.
- Send a Message
- Store a Customer record
Details specify how
e.g.
- Send an SMTP email over port 25
- Serialize Customer to JSON and store in a text file
由 Wiki 的定義 , 我們可以知道抽象化是
針對某個事物或是概念 , 僅使用你對其感興趣或是覺得重要的資訊特徵(Feature)來描述
這樣做的好處是
當我們將現實事物或概念抽象化為類別後 , 其在呈現上會比原本的樣子更簡單 , 這可幫助我們在處理問題時 , 不需要處理過多的雜訊
// 僅使用兩個 Feature 來描述 Student
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
// 若我對學生體重不感興趣,那我就不會將其加入 Student 中
// public double Weight{ get; set; }
}
// 對於 Area 的計算 , 僅列出我們重視或是需要的形狀計算即可. "不需要盡數列出"
public class AreaHelper
{
public static double GetCircleArea(int r) => r * r * Math.PI;
public static double GetRectangleArea(int h, int w) => h * w;
}
PS : 建立一個類別很簡單 , 但如何建好一個類別 , 卻很困難XD.
高內聚和低耦合是我們用來評估程式是否寫得好的一個重要指標 , 也是目標 !!!
- 在計算機科學中,內聚性是指機能相關的程式組合成一模組的程度。應用在物件導向程式設計中,若服務特定型別的方法在許多方面都很類似,則此型別即有高內聚性。在一個高內聚性的系統中,代碼可讀性及復用的可能性都會提高,程式雖然複雜,但可被管理。
- Cohesive means class elemeents that bolong together
- Cohesive describes how closely related elements of a class or module are to one another
public class MyRect
{
public int Height { get; set; }
public int Width { get; set; }
// 可以發現此方法的目標是計算外來物件的 Area , 這與 MyRect 物件沒有太大的關聯.
// 除非這個方法是存在於一個專門計算 Area 的類別.
// public static double GetRectArea(MyRect rect) => rect.Height * rect.Width;
// 也許寫成這樣會比較好 .... ?
// public double GetRectArea() => Height * Width;
}
// MethodA 沒使用到 _b
// 而 MethodB 只使用到 _b
// 故 AandB 可以考慮拆成兩個內聚力更高的 class
public class AandB
{
private int _a;
private int _a1;
private int _b;
public int MethodA() => _a + _a1;
public int MethodB() => _b;
}
// A and B 的內聚力 高於合再一起時的 AandB
public class A
{
private int _a;
private int _a1;
public int MethodA() => _a + _a1;
}
public class B
{
private int _b;
public int MethodB() => _b;
}
- 耦合性(英語:Coupling,Dependency)或稱耦合力或耦合度,是一種軟體度量,是指一程式中,模組及模組之間資訊或參數依賴的程度。
- Coupling meas Binds two (or more) details together in a way that's diffucult to change
- Offers a modular way to choose which details are involved in a particular operation
- Approaches that can be used to support having different details of the application interact with one another in a modular fashion
// 舉一些可能會影響無法達到低耦合目標的行為.
// 假設 OtherClass 在好多地方被使用 , 以下是其中之一.
public class MyClass
{
// 假設需求改變 , 然後我們就直接修改 OtherClass 的實作 , 是否會影響到 MyMethod 的運作... ?
public MyMethod(OtherClass otherClass)
{
// 當 OtherClass DoSomeThing() 實作修改或簽章移除的時候 , 可能會影響到 MyMethod
var result = otherClass.DoSomeThing();
// 當 OtherClass Prop 實作修改或簽章移除的時候 , 可能會影響到 MyMethod
var prop = otherClass.Prop;
// New 是造成強偶合的行為.
var obj = new OtherClass()
}
}
public class B{}
public class A{ // A 直接耦合 B
B Bp { get; } = new B();
}
public interface IB{}
public class B : IB {}
public class B2 : IB {}
public class A{ // A 耦合 IB , 再耦合 IB 的實作
public A(IB b){}
}
PS : 通常我們會讓高位模組不知道其真正使用的低位模組的型別 , 所以我們會盡可能使用抽象耦合 , 而非直接耦合
OOP 三大特性 , 若能善加運用這三個特性 , 可以幫助我們寫出更好的程式
繼承(英語:inheritance)。如果一個類別B「繼承自」另一個類別A,就把這個B稱為「A的子類」,而把A稱為「B的父類別別」也可以稱「A是B的超類」。繼承可以使得子類具有父類別別的各種屬性和方法,而不需要再次編寫相同的代碼。在令子類別繼承父類別別的同時,可以重新定義某些屬性,並重寫某些方法,即覆蓋父類別別的原有屬性和方法,使其獲得與父類別別不同的功能。另外,為子類追加新的屬性和方法也是常見的做法。
- 封裝(英語:Encapsulation)是指,一種將抽象性函式介面的實作細節部份包裝、隱藏起來的方法。同時,它也是一種防止外界呼叫端,去存取物件內部實作細節的手段,這個手段是由程式語言本身來提供的。封裝被視為是物件導向的四項原則之一。
- 封裝可以隱藏成員變數以及成員函式,物件的內部實現通常被隱藏,並用定義代替
public interface IMyInterface
{
string GetName(); // 隱藏實作 - 外界不需要知道實際的實作方式
}
public class MyClass : IMyInterface
{
// 隱藏資料 - 外界不需要知道其內部使用哪些成員
private string _importantVariable = "private data 1";
public string GetName() => _importantVariable
}
public class MyClass2 : IMyInterface
{
// 隱藏資料 - 外界不需要知道其內部使用哪些成員
private string _importantVariable = "private data 2";
public string GetName() => _importantVariable
}
public class Caller
{
public void Exec(IMyInterface interface) // 隱藏真實型別 - Caller 不需要知道真實型別
{
// do some thing
}
}
多型(英語:polymorphism)指為不同資料類型的實體提供統一的介面,或使用一個單一的符號來表示多個不同的類型
多型的最常見主要類別有:
- 特設多型:為個體的特定類型的任意集合定義一個共同介面。
- 參數多型:指定一個或多個類型不靠名字而是靠可以標識任何類型的抽象符號。
- 子類型(也叫做子類型多型或包含多型):一個名字指稱很多不同的類別的實例,這些類有某個共同的超類
多型 : 一個訊息的含意並非由發出訊息的人決定, 而是由接受訊息的人來決定
// 統一介面為 GetName ; 當使用者使用時 , 才會知道使用到哪一個多載方法
public class MyClass
{
public string GetName() => "Name";
public string GetName(string name) => name;
}
// 統一介面為 T ; 只有當使用者使用時 , 才會知道 T 為何
public class MyClass<T>
{
}
// 統一介面為 IMyInterface ; 只有當使用者使用時 , 才會知道真實型別為何
public interface IMyInterface
{
string GetName();
}
public class MyClass : IMyInterface
{
private string _importantVariable = "private data 1";
public string GetName() => _importantVariable
}
public class MyClass2 : IMyInterface
{
private string _importantVariable = "private data 2";
public string GetName() => _importantVariable
}
public class Caller
{
public void Exec(IMyInterface interface) // 不知道是哪一個子類別被傳入
{
// do some thing
}
}
繼承 (電腦科學)
封裝 (物件導向程式設計)
多型 (電腦科學)
遵守 SOLID 設計原則可以幫我們更好地達成高內聚以及低耦合的目標.
- Programs should be separated into distinct sections, each addressing a separate concern, or set of information that affects the program.
- 關注點分離(Separation of concerns,SOC)是對只與「特定概念、目標」(關注點)相關聯的軟件組成部分進行「標識、封裝和操縱」的能力,即標識、封裝和操縱關注點的能力。是處理複雜性的一個原則。由於關注點混雜在一起會導致複雜性大大增加,所以能夠把不同的關注點分離開來,分別處理就是處理複雜性的一個原則,一種方法。
關注點分離是面向對象的程序設計的核心概念。分離關注點使得解決特定領域問題的代碼從業務邏輯中獨立出來,業務邏輯的代碼中不再含有針對特定領域問題代碼的調用(將針對特定領域問題代碼抽象化成較少的程式碼,例如將代碼封裝成function或是class),業務邏輯同特定領域問題的關係通過側面來封裝、維護,這樣原本分散在整個應用程式中的變動就可以很好的管理起來。
Each class in your system should have only one responsibility
Each module should have one and only one reason to change
ps : A module might refer to a class or interface or even a single function.
// clean code bad sample
// Employee 負責計算員工的工作時數以及月薪
// 1.如果會計部想改變薪水計算方式 , 要改 CalculateMonthlySalary()
// 2.如果人資想改變加班時數計算方式 , 要改 ProduceMonthlyHoursReport()
public class Employee
{
// 當人資部請你修改時數計算方式時 , 則你去修改了 ProduceMonthlyHoursReport 方法
// 但你沒發現此修改會導致 CalculateMonthlySalary() 的計算結果因此不正確.
public int CalculateMonthlySalary()
{
// ...
var hp = ProduceMonthlyHoursReport();
// ...
}
public HoursReport ProduceMonthlyHoursReport()
{
//...
}
}
// clean code good sample
// 將職責拆出去. 讓每個類別他們各自的成員 , 都與其類別的創造目的有關.
// 提升內聚 , 降低耦合
// 讓類別比較小以及簡單 , 也比較好維護 ~
class Employee
{
private string _id;
public string getId()=> _id;
}
class PaymentService
{
public int calculateMonthlySalary(Employee employee)
{
//...
}
}
class WorkHoursService
{
public HoursReport produceMonthlyHoursReport(Employee employee)
{
//...
}
}
// 1. Bank 負責錢的相關業務. 一個職責(?)
// 2. 若認為其是兩個職責(負責存錢/負責提錢) , 則有可能發生
// "想改變存錢的邏輯" or "想改變提錢的邏輯"
// 此時須將 SaveMoney 及 WithdrawMoney , 各自獨立成一個 Service Class
// (各自負責存錢/提錢的業務)
// 再透過組合讓 Bank 類別使用這兩個 Service class 以完成工作
public class Bank
{
public void SaveMoney()
{
// do some thing
}
public void WithdrawMoney()
{
// do some thing
}
}
// bad code sample
public interface MyInterFace // <-- 這介面超難用
{
// 100 個以上的成員
}
public void MyMethod() // 做了超多事情
{
// 3000 行以上的程式碼
}
PS: 實務上 , 很可能無法一開始就實現這個原則 , 即使一開始是 , 也有可能在之後的開發 , 隨著不斷疊加新的功能而變得沒有滿足這個原則的要求. 當你發現一個類別負責太多功能(過於龐大) , 導致難以維護時 , 請適時的進行類別的重構 , 將類別中實作相關功能的部分抽取出來並封裝成一個新的類別 , 再透過組合的方式將新增的類別加入原類別之中 , 以此慢慢再度達成單一職責的要求.
深入淺出單一職責原則 Single Responsibility Principle
單一功能原則
一個類或者模塊應該有且只有一個改變的原因。
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
It should be possible to change the behavior of a method without editing its source code
public class DoOneThing
{ // 行為將依據傳入參數而改變 <-- 印的字串會不同
public void Do (string name) => Console.Write(name)
}
public class DoOneThing
{
public virtual void Do () => Console.Write("Hi C#")
}
public class DoTwoThing : DoOneThing
{
public override Do () => Console.Write(" Hello World ")
}
public class DoOneThing
{ // 產生 Message 的責任被移動到 IMessageService, DoOneThing
// 僅需要注入 IMessageService 後使用
private readonly IMessageService _messageService;
public DoOneThing(IMessageService messageService)
=> _messageService = messageService;
public virtual void Do ()
=> Console.Write($"{_messageService.GetNessage()}")
}
// code sample
public abstract class 加密演算法 //<-- 盡可能不會改變的抽象 (也可以用 Interface)
{
public virtual void 加密()
{
// 某種簡單的加密
}
}
// 新的需求是用 MD5加密檢算法加密
public class MD5加密演算法 : 加密演算法 //<-- 對型別拓展 , 以對應新的需求
{
public override void 加密()
{
// MD5 加密
}
}
public class Customer
{
// Customer 不知道"加密演算法"的真實型別. 未來有新的需求 , Customer 也不需要改變任何程式碼
public DoSomeThing(加密演算法 algorithm)
{
// 使用 algorithm 去加密某個東西
}
}
深入淺出開放封閉原則 Open-Closed Principle
If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of the program (correctness, task performed, etc.)
Subtypes must be substitutable for their base types
public class BaseClass
{
public void Exec(string value)
=> Console.WriteLine($"{nameof(BaseClass)} {nameof(Exec)} {value}");
}
public class DerivedClass : BaseClass
{
public void Exec(object value)
{
// 需要加入這些程式碼以避免變數型別為基底時,行為會不一致 <-- 違反 LSP
// if (value is string s)
// {
// base.Exec(s);
// return;
// }
Console.WriteLine($"{nameof(DerivedClass)} {nameof(Exec)} {value}");
}
}
// code sample
public class A{
public virtual void CanOverrideMethod() {
// do some thing
}
public void Method() {
// do some thing
}
// 若 A 擁有一個 B 不具備的能力.
//public virtual void NotCanOverrideMethod(){ // do some thing}
}
public class B : A {
public override void CanOverrideMethod() {
// do some thing
}
// 若 B 類別不具備 NotCanOverrideMethod 的能力 , 則無法達成里式替換原則的要求
//public override void NotCanOverrideMethod()=> throw new NotImplementedException();
// 隱藏父類別的成員 Method -> 這會導致無法遵守里式替換原則
// public new void Method();
}
public class User {
public void Use() {
//不論 obj 實際型別為何, User 都應該可以使用 CanOverrideMethod 以及 Method
A obj = new A(); //<-- 可以被替換成 A obj = new B()
obj.CanOverrideMethod();
obj.Method();
}
}
public void Method (SomeObj obj){
if( obj is Aobj )
// Do Some Thing
else if ( obj is Bobj)
// Do Another Thing
// ....
}
public void Method (IEnumerable<SomeObj> objs)
{
foreach(var obj in objs)
{
if(obj is null)
{
// do some thing;
break;
}
// Do Normal Thing;
}
}
public void Method (){
throw new NotImplementedException();
}
//不應該在客戶端看到強制轉型成某個子類別的寫法
public class User
{
public DoA(A obj) { (obj as B)?.DoMethod() }
}
深入淺出 Liskov 替換原則 Liskov Substitution Principle
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
- High-level modules
- More Abstract
- Business Rules
- Process-Oriented
- Further from input/output (I/O)
- Low-level modules
- Closer to I/O
- Plumbing code
- Interacts with specific external systems and hardware
public interface IProgrammable // <-- 抽象
{
void Program();
}
public class Computer : IProgrammable // <-- 低位模組
{
public void Program() => throw new NotImplementedException();
}
public class Programmer // <-- 高位模組
{ // 高位模組 Programmer 依賴於抽象 IProgrammable
// 若是未來 Programmer 需要使用 NoteBook 工作 ,
// 也僅需要新增一個繼承 IProgrammable 的 NoteBook , 傳給 Programmer 即可.
// 不需要修改任何 Programmer 的程式碼
private readonly IProgrammable _programmable;
public Programmer(IProgrammable programmable)
=> _programmable = programmable;
public void Coding() => _programmable.Program();
}
public Interface 加密 // <-- 先思考"加密"介面的特徵
{
void 加密();
}
public class RSA加密演算法 : 加密 // <-- 再實作 RSA 加密演算法
{
public void 加密()
{
// RSA加密
}
}
// 若是之後需要增加 DES 加密 , 因為接口處都是使用 加密介面.
// 僅需要新建 DES加密類別 , 並且傳給 DES加密類別給高位模組就好.
// 高位模組的程式碼不需要有任何的更動.
public class DES加密演算法 : 加密 // <-- 再實作 DES 加密演算法
{
public void 加密()
{
// DES加密
}
}
// 若低位模組已經就位,開始使用(沒有實作抽象層)
// 可在日後需求修改 , 需要重構時 , 慢慢補上.
public interface IService{ //<-- 先已有介面
public Do();
}
public class ServiceImplement : IService{ //<-- 再依據介面去決定實作
public Do(){
// Do some thing
}
}
深入淺出依賴反向原則 Dependency Inversion Principle
No client should be forced to depend on methods it does not use.
Clients should not be forced to depend on methods they do not use.
PS: client is the code that is interacting with an instance of the interface.
interface IA{
int Method()
int Method2();
}
class A : IA{ // A 只需要使用到 Method
public int Method(){
// 自己的實作.
}
// A 不具有 IA 的能力 --> 違反 LSP 原則
public int Method2() => throw new NotImplementException();
}
class 高位模組{
IA a = new A();
a.Method2(); // 因為 IA 規定此方法必須實作 , 所以預期會正常運作
// 但結果是不正常運作 --> 噴例外
}
// bad code sample
interface I智慧手機 { // <-- 這個介面很難被重複使用
void 拍照(); //<--拍照和上網這兩個動作 , 明顯與手機沒有關聯 , 應該各別抽介面使用.
void 上網() ;
}
class 智慧手機 : I智慧手機{
//實作介面成員
}
// good code sample
interface 照相 { // <-- 只有拍照的職責
void 拍照()
}
interface 上網 { // <-- 只有上網的職責
void 上網()
}
class 智慧型手機 : 照相,上網 {
// 實作介面成員
}
class 普通手機 : 照相 {
// 實作介面成員
}
class 爛手機 {
// 實作介面成員
}
深入淺出介面分割原則 Interface Segregation Principle
菜雞與物件導向 (13): 介面隔離原則
- Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
- Each unit should only talk to its friends; don't talk to strangers.
- Only talk to your immediate friends.
// 範例
// 需求 : Login 之前必須先檢查帳號以及密碼
// bad code sample
class LoginService {
public bool CheckAccount(){// 實作}
public bool CheckPassword(){// 實作}
public void Login(){ // 實作}
}
class 高位模組 {
public ExecLogin(){
// 高位模組必須知道登入系統前必須"先檢查帳號以及密碼". <-- 需要知道正確執行的細節
// 當 LoginService 的正確使用細節越複雜 , 代表對高位模組來說 , 其越不親切(不好用).
// 有誤用的可能性存在 -> e.g.高位模組可以不檢查就直接呼叫 login.Login(). 違反需求
var login = new LoginService();
if(login.CheckAccount() && login.CheckPassword()){
login.Login();
}
}
}
// good code sample
class LoginService {
private bool CheckAccount(){// 實作}
private bool CheckPassword(){// 實作}
public void Login(){ // <--- 外界只看的到 Login
// 檢查帳號和密碼 , 若通過才登入系統.
}
}
class 高位模組 {
public ExecLogin(){
// 封裝外界不需要知道的細節到 LoginService ,
// 高位模組只需要看簽章 , 知道呼叫 Login() 就可以登入這件事就好.
var login = new LoginService();
login.Login();
}
}
Law of Demeter
迪米特法则
[心得整理] c# 物件導向程式 - 6.LKP 最少知識原則
In software engineering, inversion of control (IoC) is a programming principle. IoC inverts the flow of control as compared to traditional control flow. In IoC, custom-written portions of a computer program receive the flow of control from a generic framework. A software architecture with this design inverts control as compared to traditional procedural programming: in traditional programming, the custom code that expresses the purpose of the program calls into reusable libraries to take care of generic tasks, but with inversion of control, it is the framework that calls into the custom, or task-specific, code.
Inversion of control is used to increase modularity of the program and make it extensible,[1] and has applications in object-oriented programming and other programming paradigms.
The term is related to, but different from, the dependency inversion principle, which concerns itself with decoupling dependencies between high-level and low-level layers through shared abstractions. The general concept is also related to event-driven programming in that it is often implemented using IoC so that the custom code is commonly only concerned with the handling of events, whereas the event loop and dispatch of events/messages is handled by the framework or the runtime environment.
In traditional programming, the flow of the business logic is determined by objects that are statically bound to one another. With inversion of control, the flow depends on the object graph that is built up during program execution.Such a dynamic flow is made possible by object interactions that are defined through abstractions.
// a 只能是 A 型態的物件.
var a = new A();
// 回傳值的真實型態取決於參數 isA1
// 實務上參數通常都會是 Type 型態. IOC 透過 Type 物件決定要建立什麼樣的物件回傳.
public IA CreateA(bool isA1) => isA1 ? new A1() : new A2();
In software engineering, dependency injection is a technique in which an object receives other objects that it depends on, called dependencies. Typically, the receiving object is called a client and the passed-in ('injected') object is called a service. The code that passes the service to the client is called the injector. Instead of the client specifying which service it will use, the injector tells the client what service to use. The 'injection' refers to the passing of a dependency (a service) into the client that uses it.
The service is made part of the client's state.[1] Passing the service to the client, rather than allowing the client to build or find the service, is the fundamental requirement of the pattern.
The intent behind dependency injection is to achieve separation of concerns of construction and use of objects. This can increase readability and code reuse.
Dependency injection is one form of the broader technique of inversion of control. A client who wants to call some services should not have to know how to construct those services. Instead, the client delegates to external code (the injector). The client is not aware of the injector.[2] The injector passes the services, which might exist or be constructed by the injector itself, to the client. The client then uses the services.
This means the client does not need to know about the injector, how to construct the services, or even which services it is actually using. The client only needs to know the interfaces of the services, because these define how the client may use the services. This separates the responsibility of 'use' from the responsibility of 'construction'.
// 先在 IOC 容器註冊 IMyApp & MyApp 以及 IWriter & ConsoleWriter
public class MyApp : IMyApp // 高位模組
{
private readonly IWriter _writer;
// ConsoleWriter 會透過建構子被注入到 MyApp 中
public MyApp(IWriter writer) => _writer = writer;
public void OutputString(string text) => _writer.Write($"you output {text}!");
}
public interface IWriter // 抽象
{
void Write(string s);
}
public class ConsoleWriter : IWriter // 低位模組
{
public void Write(string s) => Console.WriteLine(s);
}
public class MyContainer
{
private readonly Dictionary<Type, Type> _types = new Dictionary<Type, Type>();
public void Register<TKey, TImplementation>() where TImplementation : TKey
=> _types[typeof(TKey)] = typeof(TImplementation);
public T Create<T>() => (T)Create(typeof(T));
public object Create(Type type)
{
//Find a default constructor using reflection
var defaultConstructor = _types[type].GetConstructors()[0];
//Verify if the default constructor requires params
var defaultParams = defaultConstructor.GetParameters();
//Instantiate all constructor parameters using recursion
var parameters = defaultParams.Select(param
=> Create(param.ParameterType)).ToArray();
return defaultConstructor.Invoke(parameters);
}
}
public interface IMyApp
{
void OutputString(string name);
}
public class MyApp : IMyApp
{
private readonly IWriter _writer;
public MyApp(IWriter writer) => _writer = writer;
public void OutputString(string text) => _writer.Write($"you output {text}!");
}
public interface IWriter
{
void Write(string s);
}
public class ConsoleWriter : IWriter
{
public void Write(string s) => Console.WriteLine(s);
}
internal static class Program
{
private static void Main(string[] args)
{
// Create IOC Container
var container = new MyContainer();
// Register Type
container.Register<IWriter, ConsoleWriter>();
container.Register<IMyApp, MyApp>();
// Get MyApp Instance from IOC Container
// 高位模組不需要自己 new MyApp 以及 ConsoleWriter 以及設定它們. 就可以使用它們
var myApp = container.Create<IMyApp> ();
myApp.OutputString("QQQ"); //<--- you output QQQ!
Console.ReadKey();
}
}
// 先在 IOC 容器註冊 Computer & Computer
public class Computer
{
public void Program() => throw new NotImplementedException();
}
public class Programmer
{
private readonly Computer _computer;
// 透過建構子注入從 IOC 取得 Computer 物件
public Programmer(Computer computer) => _computer = computer;
public void Coding() => _computer.Program();
}
此程式違反了依賴倒置原則. 但 Programmer 仍然可以由 IOC 容器取得 Computer 物件.
// 這裡遵守依賴反轉
public interface IProgrammable
{
void Program();
}
public class Computer : IProgrammable
{
public void Program() => throw new NotImplementedException();
}
public class Programmer
{
private readonly IProgrammable _programmable;
public Programmer(IProgrammable programmable) => _programmable = programmable;
public void Coding() => _programmable.Program();
}
// 這裡不遵守控制反轉 <--- 出來混, 總是要還的. 總有一天要 New object 的 XD
public class MainClass
{
private readonly Programmer _programmer;
public MainClass()
{
// 自己 new Computer 物件 違反 IOC 原則
_programmer = new Programmer(new Computer);
}
}
依賴並沒有完全解除 !!!
// 先在 IOC 容器註冊 IProgrammable & Computer 以及 IProgrammer & Programmer
public interface IProgrammable
{
void Program();
}
public class Computer : IProgrammable
{
public void Program() => throw new NotImplementedException();
}
public interface IProgrammer
{
void Coding();
}
public class Programmer : IProgrammer
{
private readonly IProgrammable _programmable;
// 透過建構子注入取得 Computer 物件
public Programmer(IProgrammable programmable) => _programmable = programmable;
public void Coding() => _programmable.Program();
}
public class MainClass
{
private readonly IProgrammer _programmer;
// 透過建構子注入取得 Programmer 物件
public MainClass(IProgrammer programmer)
{
_programmer = programmer;
}
}
你確定懂?徹底搞懂 控制反轉(IoC Inversion of Control )與依賴注入(DI Dependency Inversion Principle )
淺入淺出 Dependency Injection
控制反轉 (IoC) 與 依賴注入 (DI)
Writing a Minimal IoC Container in C#
控制反轉(Inversion of Control)與依賴注入(Dependency Inversion)
You can find me on
若有謬誤 , 煩請告知 , 新手發帖請多包涵