【実務・中級編】【上級者向け】PrismまたはCommunityToolkit.Mvvmを用いた本格的なVB.NETでのMVVMアーキテクチャ構築 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク
スポンサーリンク

Code-Behindの呪縛を解け:VB.NETによる堅牢なMVVMアーキテクチャ構築の極意

業務アプリケーションの開発現場において、多くのエンジニアが陥る「底なし沼」がある。それは、「動けばいい」という思考に基づいた、View(UI)とロジックの密結合だ。

WinFormsの延長線上でWPFを使い、`MainWindow.xaml.vb`(Code-Behind)にデータベース接続、ファイル操作、バリデーション、そしてビジネスロジックをすべて書き込む。その結果、コードはスパゲッティ化し、単体テストは不可能になり、仕様変更のたびに予期せぬバグが噴出する。これが「技術的負債」の正体だ。

本稿では、大規模開発に耐えうる、疎結合でテスト容易性の高い、CommunityToolkit.Mvvmを用いたモダンなVB.NETアーキテクチャの構築術を伝授する。これは単なる書き方の解説ではない。エンジニアとして「設計」を行うための思考プロセスだ。

1. なぜ「CommunityToolkit.Mvvm」なのか?

Prismは非常に強力だが、モジュール分割やリージョン管理が必要な超大規模プロジェクト向けだ。一方で、業務効率化ツールや中規模のシステムにおいて、過剰な機能は学習コストと複雑さを増大させる。

そこで我々が選ぶべきは、CommunityToolkit.Mvvmである。Microsoftが主導するこのライブラリは、軽量でありながら、MVVMの核心である `INotifyPropertyChanged` や `ICommand` の実装を極めてクリーンに行うための基盤を提供してくれる。

VB.NETにおいて重要なのは、C#のソースジェネレーター(`[ObservableProperty]` 等)の恩恵を直接受けられない制約を理解した上で、「いかに型安全に、かつボイラープレート(定型文)を最小限に抑えて実装するか」という戦略である。

2. アーキテクチャの三原則:責務の完全分離

設計の鉄則は、「Viewは見た目を知っているが、中身(ロジック)は何も知らない」状態を作ることだ。

1. Model: データ構造と純粋なビジネスロジック。UIの存在を一切知らない。
2. View: XAML。ユーザーとの接点。ViewModelとの通信は「データバインディング」のみで行う。
3. ViewModel: Viewの「状態」と「振る舞い」を管理する。Modelを操作し、Viewがバインドできる形にデータを加工する。Viewへの直接参照(例:`MainWindow.xaml.vb`のコントロール名へのアクセス)は厳禁だ。

3. 実装例:堅牢なユーザー管理システムの構築

では、実際に「データベースからユーザー情報を取得し、表示・更新する」という実務的なシナリオで、プロダクションレベルのコードを見ていこう。

A. Model: 純粋なデータ構造

Modelは、外部への依存を排除し、データの器としての責務に徹する。

''' <summary>
''' ユーザー情報のエンティティ。
''' ビジネスロジックをここに含める場合は、UIに依存しない形で行う。
''' </summary>
Public Class User
    Public Property Id As Integer
    Public Property Name As String
    Public Property Email As String
    Public Property IsActive As Boolean
End Class

B. Service: 外部連携の抽象化(Dependency Injectionの準備)

ファイルやデータベースへのアクセスは、ViewModelに直接書かない。必ず「サービス」として抽象化せよ。これにより、テスト時に「本物のDB」の代わりに「モック(偽物)」を差し込むことが可能になる。

''' <summary>
''' ユーザーデータの永続化を担うインターフェース。
''' </summary>
Public Interface IUserService
    ''' <summary>
    ''' 非同期でユーザー一覧を取得する。
    ''' </summary>
    <Task> Function<IEnumerable(Of User)>(cancellationToken As CancellationToken)> GetUsersAsync
End Interface

''' <summary>
''' 実装クラス。DB接続などの重い処理はここで行う。
''' </summary>
Public Class UserService Implements IUserService
    Public Async Function GetUsersAsync(cancellationToken As CancellationToken) As Task(Of IEnumerable(Of User)) Implements IUserService.GetUsersAsync
        ' 本来はここでDB接続やAPIコールを行う
        ' 今回はシミュレーションとして遅延を入れる
        Await Task.Delay(1500, cancellationToken)

        Return New List(Of User) From {
            New User With {.Id = 1, .Name = "田中 太郎", .Email = "tanaka@example.com", .IsActive = True},
            New User With {.Id = 2, .Name = "佐藤 次郎", .Email = "sato@example.com", .IsActive = False}
        }
    End Function
End Class

C. ViewModel: 司令塔としての実装

ここが設計の肝だ。`ObservableObject` を継承し、プロパティの変更通知を適切に制御する。また、UIスレッドをブロックしないための非同期処理(Async/Await)を徹底する。

Imports CommunityToolkit.Mvvm.ComponentModel
Imports CommunityToolkit.Mvvm.Input
Imports System.Collections.ObjectModel

''' <summary>
''' ユーザー一覧表示のロジックを管理するViewModel。
''' </summary>
Public Class UserListViewModel
    Inherits ObservableObject

    Private ReadOnly _userService As IUserService
    Private _isBusy As Boolean = False

    ' ObservableCollectionは、コレクション自体の追加・削除をViewに通知する
    Public Property Users As New ObservableCollection(Of User)()

    ''' <summary>
    ''' ローディング状態。ViewのProgressBarのVisibility等にバインドする。
    ''' </summary>
    Public Property IsBusy As Boolean
        Get
            Return _isBusy
        End Get
        Set(value As Boolean)
            ' SetPropertyは値が変わった時のみPropertyChangedイベントを発行する
            If SetProperty(value, _isBusy) Then
                ' プロパティ変更通知を明示的に行う
                OnPropertyChanged(NameOf(IsBusy))
            End If
        End Set
    End Property

    ''' <summary>
    ''' コンストラクタ。依存関係は外部から注入(DI)する。
    ''' </summary>
    Public Sub New(userService As IUserService)
        _userService = userService
        ' コマンドの初期化
        LoadUsersCommand = New AsyncRelayCommand(LoadUsersAsync)
    End Sub

    ''' <summary>
    ''' ユーザー一覧を読み込むコマンド。
    ''' </summary>
    Public Property LoadUsersCommand As IAsyncRelayCommand

    Private Async Function LoadUsersAsync() As Task
        If IsBusy Then Return

        Try
            IsBusy = True
            Users.Clear()

            ' 非同期でデータ取得(UIスレッドを固めない)
            Dim results = Await _userService.GetUsersAsync(CancellationToken.None)

            For Each user In results
                Users.Add(user)
            Next
        Catch ex As Exception
            ' ここでログ出力やエラー通知ロジックを呼び出す
            ' 例: ErrorService.ShowError(ex.Message)
            Debug.WriteLine($"Error loading users: {ex.Message}")
        Finally
            IsBusy = False
        End Try
    End Function
End Class

D. View: XAMLによる宣言的UI

Viewは、ViewModelのプロパティを「眺める」だけだ。

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="User Management" Height="350" Width="500">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <!-- 操作部 -->
        <Button Content="データ読込" 
                Command="{Binding LoadUsersCommand}" 
                Width="100" HorizontalAlignment="Left" Margin="0,0,0,10"/>

        <!-- データ一覧 -->
        <DataGrid Grid.Row="1" ItemsSource="{Binding Users}" AutoGenerateColumns="True" IsReadOnly="True"/>

        <!-- ローディング表示 -->
        <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="10">
            <ProgressBar IsIndeterminate="True" Width="100" Height="15" 
                         Visibility="{Binding IsBusy, Converter={StaticResource BooleanToVisibilityConverter}}"/>
            <TextBlock Text=" 読み込み中..." VerticalAlignment="Center"
                       Visibility="{Binding IsBusy, Converter={StaticResource BooleanToVisibilityConverter}}"/>
        </StackPanel>
    </Grid>
</Window>

4. 現場で差がつく「プロフェッショナルの作法」

設計を実装に落とし込む際、以下の3点を見落とすと、どれだけ優れたフレームワークを使っても失敗する。

① 非同期処理の徹底(UIフリーズの回避)

業務ツールで最もユーザーのストレスになるのは、重いファイル処理やSQL実行中に「アプリが応答なしになる」ことだ。必ず `Async/Await` を使い、`IAsyncRelayCommand` を用いてコマンドを実行せよ。

② エラーハンドリングの階層化

ViewModel内で `Try…Catch` を書くのは正しいが、それを単に `Debug.WriteLine` で済ませてはいけない。エラーが発生した際、「ユーザーにメッセージを出す(Viewの責務)」のか「ログに記録する(Serviceの責務)」のかを明確に分け、ViewModelはそれらを「呼び出す」ことに専念せよ。

③ 依存性の注入 (Dependency Injection)

上記の `UserListViewModel` のコンストラクタを見てほしい。`IUserService` を引数に取っている。これは、ViewModelが「どうやってデータを取るか」を知らなくて良いことを意味する。
`App.xaml.vb` 等のアプリケーション起動時に、`New UserListViewModel(New UserService())` と注入するだけでいい。これにより、ユニットテストでは `New UserListViewModel(New MockUserService())` とすることで、DBなしでロジックのテストが可能になる。

結びに:設計は「未来の自分」への贈り物である

「コードを書く」ことと「ソフトウェアを設計する」ことは全く別物だ。
単に動くコードを書くだけなら、Code-Behindでも十分だろう。しかし、半年後の仕様変更、一年後のバグ修正、そしてチームメンバーへの引き継ぎを考えたとき、MVVMに基づいた堅牢な設計は、開発スピードを落とす「足かせ」ではなく、変化に即応するための「翼」となる。

このアーキテクチャをマスターし、保守性の高い、真にプロフェッショナルな業務アプリケーションを構築してほしい。

タイトルとURLをコピーしました