From 12bda2dcbfa97104c55f36c2da0acc41707a2dd8 Mon Sep 17 00:00:00 2001 From: Yun Chan Date: Tue, 2 Jun 2026 09:37:35 +0900 Subject: [PATCH] =?UTF-8?q?refactor(P5a):=20App=20DI=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EC=A7=80=EC=85=98=20=EB=A3=A8=ED=8A=B8=20+=20MainWindow=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EC=9E=90=20=EC=A3=BC=EC=9E=85=20(=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=EB=A1=9C=EC=BC=80=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MVVM(P5b)의 선행조건 — 엔진/설정을 생성자 주입으로 가시화하고 서비스로케이터 결합을 제거. - App.xaml.cs: new DpapiSettingsStore()+CreateDefault() 직접 생성 → ServiceCollection.AddEverything2Everything() (P2 DI 재사용) 컴포지션 루트. Settings/Engine은 컨테이너 단일 싱글턴에서 해소(공유 보존). Generic Host 대신 ServiceProvider 사용 — Environment.Exit 진입점과 라이프사이클 충돌 회피(적대적 리뷰 반영). - MainWindow: ((App)Application.Current).Engine/.Settings 서비스로케이터 8곳 → 생성자 주입 _engine/_settings. App.ShowMainWindow/ShowConvertDialog가 주입. 헤드리스 테스트 가능성 토대. 70개 테스트 전부 그린, 빌드 0경고/0오류. --- src/Everything2Everything.App/App.xaml.cs | 18 +++++++++---- .../Views/MainWindow.xaml.cs | 25 +++++++++++-------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/Everything2Everything.App/App.xaml.cs b/src/Everything2Everything.App/App.xaml.cs index 95291d4..c4d3140 100644 --- a/src/Everything2Everything.App/App.xaml.cs +++ b/src/Everything2Everything.App/App.xaml.cs @@ -2,19 +2,27 @@ using System.Windows; using Everything2Everything.App.Cli; using Everything2Everything.App.Views; using Everything2Everything.Core; +using Microsoft.Extensions.DependencyInjection; namespace Everything2Everything.App; public partial class App : Application { - /// App·LlmProvider가 공유하는 설정 저장소 (키 저장 즉시 변환에 반영). - public ISettingsStore Settings { get; } = new DpapiSettingsStore(); + private readonly IServiceProvider _services; + + /// App·LlmProvider가 공유하는 설정 저장소 (키 저장 즉시 변환에 반영). DI 컨테이너 단일 싱글턴. + public ISettingsStore Settings { get; } public ConversionEngine Engine { get; } public App() { - Engine = Everything2EverythingBootstrap.CreateDefault(Settings); + // 컴포지션 루트 — Core의 DI 확장(Scrutor 자동등록)으로 Provider/Registry/Engine/Settings를 구성. + var services = new ServiceCollection(); + services.AddEverything2Everything(); + _services = services.BuildServiceProvider(); + Settings = _services.GetRequiredService(); + Engine = _services.GetRequiredService(); } protected override async void OnStartup(StartupEventArgs e) @@ -68,14 +76,14 @@ public partial class App : Application private void ShowMainWindow() { - var window = new MainWindow(); + var window = new MainWindow(Engine, Settings); MainWindow = window; window.Show(); } private void ShowConvertDialog(IReadOnlyList files) { - var window = new Views.MainWindow(files); + var window = new Views.MainWindow(Engine, Settings, files); MainWindow = window; window.Show(); } diff --git a/src/Everything2Everything.App/Views/MainWindow.xaml.cs b/src/Everything2Everything.App/Views/MainWindow.xaml.cs index d73b3a1..52b7a51 100644 --- a/src/Everything2Everything.App/Views/MainWindow.xaml.cs +++ b/src/Everything2Everything.App/Views/MainWindow.xaml.cs @@ -16,6 +16,8 @@ namespace Everything2Everything.App.Views; public partial class MainWindow : Wpf.Ui.Controls.FluentWindow { + private readonly ConversionEngine _engine; + private readonly ISettingsStore _settings; private readonly ObservableCollection _activeQueue = new(); private readonly ObservableCollection _pastResults = new(); private CancellationTokenSource? _cts; @@ -28,10 +30,11 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow public ICommand CloseCommand { get; } public ICommand RefreshCommand { get; } - public MainWindow() : this(null) { } - - public MainWindow(IReadOnlyList? initialFiles) + public MainWindow(ConversionEngine engine, ISettingsStore settings, IReadOnlyList? initialFiles = null) { + _engine = engine; + _settings = settings; + AddFilesCommand = new RelayCommand(_ => PickAndAddFiles()); ProcessQueueCommand = new RelayCommand(_ => OnProcessQueueClick(this, new RoutedEventArgs()), _ => _activeQueue.Count > 0 && _cts is null); @@ -66,7 +69,7 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow private async Task RefreshCapabilityStatusAsync() { - var engine = ((App)Application.Current).Engine; + var engine = _engine; var notReady = new List(); foreach (var p in engine.Providers.All) { @@ -90,7 +93,7 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow private void OnSettingsClick(object sender, RoutedEventArgs e) { - var win = new SettingsWindow(((App)Application.Current).Settings) { Owner = this }; + var win = new SettingsWindow(_settings) { Owner = this }; win.ShowDialog(); _ = RefreshCapabilityStatusAsync(); RefreshAvailableOutputFormats(); @@ -304,7 +307,7 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow var targetLang = AiTargetLangBox?.Text?.Trim(); opts.Ai.TargetLanguage = string.IsNullOrEmpty(targetLang) ? null : targetLang; - opts.VideoPreferGpu = ((App)Application.Current).Settings.Get("video.gpu") != "false"; + opts.VideoPreferGpu = _settings.Get("video.gpu") != "false"; return opts; } @@ -563,7 +566,7 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow UpdateProcessQueueButton(); ShowProcessingProgress(snapshot.Count); - var engine = ((App)Application.Current).Engine; + var engine = _engine; var options = BuildOptions(); var reporter = new Progress(p => @@ -780,7 +783,7 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow { try { - ContextMenuRegistrar.Register(((App)Application.Current).Engine); + ContextMenuRegistrar.Register(_engine); MessageBox.Show(this, "컨텍스트 메뉴를 등록했습니다.\n파일 우클릭 → \"추가 옵션 표시\" 또는 \"JPEG로 빠른 변환/변환…\".", "Everything2Everything", MessageBoxButton.OK, MessageBoxImage.Information); @@ -794,7 +797,7 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow private void OnDiagnoseClick(object sender, RoutedEventArgs e) { - var window = new DiagnoseWindow(((App)Application.Current).Engine) { Owner = this }; + var window = new DiagnoseWindow(_engine) { Owner = this }; window.ShowDialog(); } @@ -934,7 +937,7 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow private LossClass? WorstLossForQueue(string outputExt) { if (_activeQueue.Count == 0) return null; - var graph = ((App)Application.Current).Engine.Providers.Graph; + var graph = _engine.Providers.Graph; LossClass? worst = null; foreach (var item in _activeQueue) { @@ -971,7 +974,7 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow { if (OutputFormatCombo is null) return; - var engine = ((App)Application.Current).Engine; + var engine = _engine; IReadOnlyCollection available; if (_activeQueue.Count == 0)