首頁

目前文章總數:157 篇

  

最後更新:2024年 12月 07日

0037. .net Core 6模組化注入代碼,用反射與新增檔案,達到不改代碼完成需求-以多國語言翻譯為例

日期:2023年 05月 20日

標籤: C# Asp.NET Core Web MVC Web Design Pattern

摘要:C# 學習筆記


應用所需:1. .net core 6
     2. Visual Studio 2022
解決問題:如何模組化代碼,未來需要擴充代碼時不改原本的代碼,達到設計模式中的開放封閉原則
     可以避免未來代碼耦合度太高,接手代碼的人不趕改動的問題。
範例檔案:連結
開放封閉原則(OCP):SOLID中的O,軟體實體(類、模塊、函式等)應該可以擴展,但是不可以修改)
基本介紹:本篇分為3大部分。
第一部分:一個簡單的中文轉英文翻譯實作
第二部分:展示增加到多國語系時模組化代碼過程
第三部分:未來擴充時只需新增檔案,達到OCP原則






第一部分:一個簡單的中文轉英文翻譯實作

Step 1:中文翻譯英文的專案架構

1.LanguageController : 中翻英控制器,API接口
2. LanguageService : 中翻英翻譯實作
3. ILanguageService : 中翻英翻譯實作Interface
4. Program : 註冊接口與實例的位置




Step 2:LanguageController代碼說明

當GetLanaguage被呼叫時會執行GetCorrespondMessage(message)
此時message的內容會從中文轉成英文


[ApiController]
[Route("[controller]")]
public class LanguageController : ControllerBase
{
    private readonly ILogger<LanguageController> _logger;
    private readonly ILanguageService _languageService;
    public LanguageController(ILogger<LanguageController> logger, ILanguageService languageService)
    {
        _logger = logger;
        _languageService = languageService;
    }
    [HttpGet(Name = "GetLanaguage")]
    public string GetLanaguage(string message)
    {
        return _languageService.GetCorrespondMessage(message);
    }        
}



Step 3:LanguageService代碼說明

這邊只是為了示範,簡單示意。本篇目的是模組化,而非翻譯實作。
哈囉 -> Hello 假設翻譯成英文


public class LanguageService : ILanguageService
{
    private readonly ILogger<LanguageService> _logger;
    private readonly IServiceProvider _serviceProvider;
    public LanguageService(IServiceProvider serviceProvider, ILogger<LanguageService> logger) 
    {
        _serviceProvider = serviceProvider;
        _logger = logger;
    }
    public string GetCorrespondMessage(string message)
    {
        if (message == "哈囉")
            return "Hello";
        return String.Empty;
    }
}



Step 5:LanguageService代碼說明

接口相當簡單GetCorrespondMessage


public interface ILanguageService
{
    string GetCorrespondMessage(string message);
}



Step 6:Demo執行結果

.net Core 6 執行建置後,預設API專案會有Swagger




Step 7:缺點

這段代碼假設今天要同時翻譯日語或中國語,那麼會改到以下區塊的代碼,違反OCP原則
並且外層呼叫GetCorrespondMessage時會呼叫多次。


public string GetCorrespondMessage(string message, string nationCode)
{
    if(nationCode == "美國")
        if (message == "哈囉")
        return "Hello";
    else if (nationCode == "中國")
        if (message == "哈囉")
        return "哈啰";
    else if (input == "日本")
       if (message == "哈囉")
        return "こんにちは";
    return String.Empty;
}





第二部分:展示增加到多國語系時模組化代碼過程

Step 1:模組化專案架構

為了解決第一部分最後的缺點,我們先看改變後的架構,
1. LanguageController:API層依照需求調整,收集所有國家資料
2. Nation:實例的國家,解耦成美國、中國、日本
3. LanguageService:入口,由此收集所有國家的翻譯內容(未來也不用調整此代碼)
4. Interface Nation:一對一實例,並且每個接口繼承 INationBase
5. ILanguageService:入口接口
6. INationBase:提供所有國家相同的行為接口
7. Program:註冊此次的改動


Step 2:LanguageController代碼說明

當GetLanaguage被呼叫時會執行GetAllMessage(message)
並且將回傳改為List 收集所有翻譯結果


[ApiController]
[Route("[controller]")]
public class LanguageController : ControllerBase
{
    private readonly ILogger<LanguageController> _logger;
    private readonly ILanguageService _languageService;
    public LanguageController(ILogger<LanguageController> logger, ILanguageService languageService)
    {
        _logger = logger;
        _languageService = languageService;
    }
    [HttpGet(Name = "GetLanaguage")]
    public List<string> GetLanaguage(string message)
    {           
        return _languageService.GetAllMessage(message);
    }              
}



Step 3:Nation資料夾下的實例說明

新建一個資料夾Nation,裡面是所有國家的實例,未來新增國家語言只需新增,不用修改
1. 下面這段是美國的,繼承 INationAmericaService


public class NationAmericaService : INationAmericaService
{
    private readonly ILogger<NationAmericaService> _logger;
    public NationAmericaService(ILogger<NationAmericaService> logger)
    {
        _logger = logger;
    }
    public string GetCorrespondMessage(string input)
    {
        #region 此段應實作引用庫,轉成對應文化語言,這邊只是舉例
        if (input == "哈囉")
            return "Hello";
        #endregion
        return string.Empty;
    }
}


2. 下面這段是中國的,繼承 INationChinaService 如果中國有特殊邏輯,也不會改到其他國家代碼


public class NationChinaService : INationChinaService
{
    private readonly ILogger<NationChinaService> _logger;
    public NationChinaService(ILogger<NationChinaService> logger)
    {
        _logger = logger;
    }
    public string GetCorrespondMessage(string input)
    {
        #region 此段應實作引用庫,轉成對應文化語言,這邊只是舉例
        if (input == "哈囉")
            return "哈啰";
        #endregion
        #region 假設這個文化有些文字要過濾
        if (input == "辱華文字")
            return string.Empty;
        #endregion
        return string.Empty;
    }
}


3. 下面這段是日本的,繼承 INationJapanService


public class NationJapanService : INationJapanService
{
    private readonly ILogger<NationJapanService> _logger;
    public NationJapanService(ILogger<NationJapanService> logger)
    {
        _logger = logger;
    }
    public string GetCorrespondMessage(string input)
    {
        #region 此段應實作引用庫,轉成對應文化語言,這邊只是舉例
        if (input == "哈囉")
            return "こんにちは";
        #endregion
        return string.Empty;
    }
}



Step 4:LanguageService的實例說明

1.實做了GetAllMessage
GetNationSericeMapDic() 會依照反射取得INationBase 對照到真正的實例


public List<string> GetAllMessage(string message)
{
    var resultMessages = new List<string>();
    //取得所有國家
    var nationServices = GetNationSericeMapDic();
    foreach (var service in nationServices)
    {
       resultMessages.Add(service.GetCorrespondMessage(message));
    }
    return resultMessages;
}


2. 核心,實際上是將所有INationBase繼承的實例取出,以後新增的國家只要接口繼承了INationBase,會自動被找出來呼叫


private List<INationBase> GetNationSericeMapDic()
{
    //反射取出所有引用INationBase的實例
    var nationSericeItems = new List<INationBase>();
    var types = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.GetInterfaces().Contains(typeof(INationBase)));
    foreach (var type in types)
    {
        var service = _serviceProvider.CreateScope().ServiceProvider.GetService(type) as INationBase;
        if (service != null)
        {
            nationSericeItems.Add(service);
        }
    }
    return nationSericeItems;
}



Step 5:Interface Nation接口說明

1.所有國家都繼承了 INationBase,使其反射的時候可以集中收集關聯性,真正的接口由 INationBase 決定
美國的接口繼承 INationBase


public interface INationAmericaService : INationBase
{
}


2.中國的接口繼承 INationBase


public interface INationChinaService : INationBase
{
}


3.日本的接口繼承 INationBase


public interface INationJapanService : INationBase
{
}



Step 6:ILanguageService接口說明

依照需求改成收集所有翻譯內容


public interface ILanguageService
{
    public List<string> GetAllMessage(string message);
}



Step 7:INationBase接口說明

由此決定所有國家翻譯都該執行的行為


public interface INationBase
{
    public string GetCorrespondMessage(string input);
}



Step 8:Program的異動

如果有反射的實作,其實此段可以忽略,但為了說明,仍有處理此端。
為何反射可以成功? 是因為在region的地方 [注入Interface對應實例]
每個INationXXXX由於都繼承INationBase了,因此真正的對照會是在註冊這段。
在反射時雖然都是INationBase,但註冊時決定了哪個國家接口對到了哪個實例。

using InjectReflectionForTranslateLanguageExample.Implement;
using InjectReflectionForTranslateLanguageExample.Implement.Nation;
using InjectReflectionForTranslateLanguageExample.Interface;
using InjectReflectionForTranslateLanguageExample.Interface.Nation;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

#region 注入Interface對應實例
builder.Services.AddScoped<ILanguageService, LanguageService>();
builder.Services.AddScoped<INationAmericaService, NationAmericaService>();
builder.Services.AddScoped<INationChinaService, NationChinaService>();
builder.Services.AddScoped<INationJapanService, NationJapanService>();
builder.Services.AddScoped<INationGermanyService, NationGermanyService>();

#endregion

var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseAuthorization();
app.MapControllers();
app.Run();



Step 9:觀察反射時取的實例

透過Debug偵錯,可以發現取得的實例


Step 10:組成回傳

最後每個執行的結果都在 LanguageService 收集回傳給Controller層




未來擴充時只需新增檔案,達到OCP原則

Step 1:現在需求新增德國翻譯

1.NationGermanyService:增加德國翻譯實例,繼承INationGermanyService:
2.INationGermanyService:德國接口,繼承: INationBase


Step 2:NationGermanyService實例說明

此段代碼完成德國翻譯實作,繼承了INationGermanyService


public class NationGermanyService : INationGermanyService
{
    private readonly ILogger<NationGermanyService> _logger;
    public NationGermanyService(ILogger<NationGermanyService> logger)
    {
        _logger = logger;
    }
    public string GetCorrespondMessage(string input)
    {
        #region 此段應實作引用庫,轉成對應文化語言,這邊只是舉例
        if (input == "哈囉")
            return "Hallo";
        #endregion
        return string.Empty;
    }
}



Step 3:INationGermanyService接口說明

代碼也是繼承 INationBase,真正的接口在 INationBase


public interface INationGermanyService : INationBase
{
}



Step 4:執行Demo成果

最後可以發現,我們只做了”新增”兩個檔案 INationGermanyService 與 NationGermanyService
過程中不改動原本的代碼,完成模組化,達成OCP原則
未來接手的工程師,不用怕加新的需求