導入
今までPrism
を使わずにWPF
やXamarin.Forms
で開発を行ってきましたが、今回初めてPrism
を導入することにして現在学習中です。
ちなみに、ReactiveProperty
ライブラリも使っています。
現在.NET5.0 + WPF
でWindowsアプリを開発しているのですが、疑問点が出てきて質問しました(DIの感覚がつかめていないのかな?)。
ひとまず現状私が理解している内容を書きます。
Prism.WPF(Full App)
テンプレートでソリューション(名前はSampleApp)を作成すると...
- SampleApp.Modules.ModuleName
- SampleApp.Servises.Interfaces
- SampleApp.Services
- SampleApp.Modules.ModuleName.Tests
- SampleApp
- SampleApp.Core
という感じでプロジェクトが作成されますよね?(ModuleNameの名前はいつも変更してます^^;)
それぞれのプロジェクトの役割をある程度学習したところなのですが...
- 主にユーザコントロールの作成(リージョンの作成)
- Model部のインターフェイスを定義
- Model部の実装の作成
- 1の単体テスト
- View(主にWindow)とViewModelの定義、DIの定義などアプリ本体の処理を作成
- 5で使われる基底クラス(ViewModelBaseなど)やRegion名をすべて定数で定義するなど(5全体で使われるクラスの定義?)
上記のような感じで理解してます(1番目の箇条書きと番号が対応しています)。
※もしこの時点で間違っている理解があればご指摘いただければ嬉しいです^^
※以下SampleApp.
は省略します。
現在一番わからないのが、Model部の処理をServices
が担当し、Model部のプロパティやメソッドの公開をServices.Interfaces
が担当することです。
もちろんDIでViewModelのコンストラクタに注入できるのはわかるのですが...
「ViewModelがModelクラスをフィールドやプロパティで持つのは良くない」
「あくまで、Services.Interfacesのインターフェイスを定義して、それを使うべき」
...という記述をどこかで読んでそこら辺からこんがらがっています(-_-;)
(この記述自体が間違っているのならある程度すっきりします^^)
ViewModelでModelのクラスを持つことができないのはかなり不便ではないでしょうか?
もちろん疎結合にしたいのはわかりますが、一番疑問に感じるのは列挙体の定義とかですかね...
質問のサンプルコード
prismを使っていなかった頃の簡単なサンプルを書きますので、Prism環境下ならどうなるのかご教授いただけませんでしょうか。
ViewとViewModelのバインディングは大丈夫なのでViewの実装は割愛させていただきます。
ほかにも複雑な処理は割愛しています。
(質問ページに直接入力なのでタイポあったらすいません💦)
「アプリの内容」
入力欄に動物の名前を入力すると、名前を検索して「~類」と種類を表示するアプリ
「モデル部」
C#
1// AnimalType.cs 2public enum AnimalType { 3 Honyu, // 哺乳類 4 Tyou, // 鳥類 5 Hatyu, // 爬虫類 6 Ryousei, // 両生類 7 Gyo, // 魚類 8} 9 10// Animal.cs 11public class Animal { 12 public string Name {get; private set;} 13 public AnimalType Kind {get; private set;} 14 15 public Animal(string name, AnimalType kind) { 16 Name = name; 17 Kind = kind; 18 } 19} 20 21// SearchAnimalService.cs 22// 疑問点3 23public class SearchAnimalService { 24 private SearchAnimalService _instance; 25 public SearchAnimalServicce Default => _instance ?? new SearchAnimalService(); 26 27 public SearchAnimalService() { 28 // DBへの接続などの情報を定義(本来は別のクラスでDBとの接続定義を行う処理を書くと思いますが今回はここで...) 29 } 30 31 // 名前をDBから検索(失敗したらnull) 32 public async Task<Animal> Search(string name) { 33 Animal result = null; 34 35 // await (DBから名前が一致する動物を検索) 36 37 return result; 38 } 39}
「ViewModel部」
C#
1// MainWindowViewModel.cs 2public class MainWindowViewModel { // 本当はINotifyPropertyChangedの空実装を追加しています 3 // ViewにおいてあるTextBoxとバインド 4 public ReactiveProperty<string> SearchName {get;} = new(); 5 6 // 疑問点2 7 // ViewにおいてあるTextBlockとバインド 8 public ReactiveProperty<string> AnimalTypeString {get;} = new(); 9 10 // ViewにおいてあるButtonのCommandとバインド 11 public AsyncReactiveCommand SearchCommand {get;} = new(); 12 13 public MainWindowViewModel() { 14 SearchCommand.Subscribe(OnSearch); 15 } 16 17 private async Task OnSearch() { 18 Animal result = await SearchAnimalService.Default.Search(SearchName.Value); 19 if (result == null) return; 20 21 AnimalTypeString.Value = AnimalTypeToString(Animal.Kind); 22 } 23 24 // 疑問点1 25 private static string AnimalTypeToString(AnimalType kind) { 26 return kind switch { 27 AnimalType.Honyu => "哺乳類", 28 AnimalType.Tyou => "鳥類", 29 AnimalType.Hatyu => "爬虫類", 30 AnimalType.Ryousei => "両生類", 31 AnimalType.Gyo => "魚類", 32 _ => throw new NotImplementedException() // 理論上ここには到達しない 33 }; 34 } 35}
サンプルに対しての質問
「疑問点1」
一番の疑問点です。
「ViewModelはModelで定義されたものを使ってはいけない、インターフェイスを介すべき」
ことに従うなら、AnimalType
を使っていること自体間違いになりますよね?
今まではそんなこと気にせずに使っていましたが、実際のところどうなんでしょうか?
AnimalType
をViewModelに実装はできませんし、Modelで「哺乳類」など表示するための文字列を出すのもおかしいと思いますし...
(この例は列挙体から文字列に変換ですが、列挙体から文字色に変換することなども考えるとModel部で文字色を返すメソッドを作るのはかなりおかしいですよね)
それともModelとViewModelの間に別の層を用意するのでしょうか?(訳が分からない世界に迷い込みそう...)
「疑問点2」
今回はシンプルな処理なのでAnimalTypeString
としてstring
型のプロパティを定義しています。
しかし、Animal
クラスにどんどんプロパティが追加され、写真も表示したい、wikiへのリンクを表示したい...
などとなったときに、ReactiveProperty<Animal>
というように定義したくなりませんでしょうか?
別にそれでもViewとのバインディングには問題ないですし...
(今回の例では必要ないですが、Animal
クラスに変更通知機能を実装することもあると思います。)
それとも、そういう場合はAnimalViewModel
を作ってAnimal
クラスをラップするのがよいのでしょうか?
(でも、そんなことしてもViewModelがModelクラスを使っていることは事実ですよね?)
または、AnimalInterface
なるものを作って、DIでMainWindowViewModel
のコンストラクタに注入するのでしょうか?
(これを使っても疑問点1のような列挙体の問題は解決しませんが...)
(それとも列挙体を使わずに、定数使いまくるのか?複雑になると名前付けが大変...orz)
「疑問点3」
これは疑問というより確認ですが、このようなDBにアクセスするような機能をDIで実装するのが自然なイメージで認識してます。
DBとのやり取りを行うインターフェイスを作成し、それをDIで実装すれば単体テストなども楽に書けますよね?
まぁ...作っている環境は個人開発なので、そこまでこだわらなくてもよいのかもしれませんが💦
なんだか気持ち悪かったので質問しました。
長文になりましたが、よろしくお願いします。
回答1件
あなたの回答
tips
プレビュー