導入
TaskItemのTitleはTaskTitle(ValueObject)、Priorityはenumです。WPFの画面(View)にそのまま渡すと、バインディングや表示のたびに.Valueや.ToString()を書くことになり、Viewが業務クラスの内部構造を知りすぎてしまいます。画面用のデータの形を別に用意しましょう。
図解
flowchart LR
TI["TaskItem<br/>(Entity・業務データ)"] -->|TaskMapper.ToDto| DTO["TaskDto<br/>(画面用・プレーンな値)"]
DTO --> VM["ViewModel / View"]
style DTO fill:#e1f5fe
サンプル
public record TaskDto(Guid Id, string Title, string Priority, bool IsCompleted);
public static class TaskMapper
{
public static TaskDto ToDto(TaskItem task)
=> new(task.Id, task.Title.Value, task.Priority.ToString(), task.IsCompleted);
}
TaskDtoはrecord(値の入れ物)。TaskTitleではなくstring、Priorityではなくstringというプレーンな型だけを持つTaskMapper.ToDtoが変換を1箇所に集約する。Viewや他のクラスが変換ロジックを個別に持たなくてよい- ViewModelは
TaskDtoだけを扱えばよく、TaskTitleのバリデーションルールなど業務データの内部構造を知らずに済む
やってみよう
TaskDtoからTaskItemを作る変換(逆方向)はこのコースでは扱いませんが、なぜ普通は「表示専用」の一方向(Entity→Dto)で十分なのか考えてみましょう(更新はTaskServiceのメソッド経由で行うため)。
演習
var task = TaskFactory.Create("牛乳を買う", Priority.High);
task.Complete();
var dto = TaskMapper.ToDto(task);
Assert(dto.Id == task.Id, "Idが一致");
Assert(dto.Title == "牛乳を買う", "Titleが文字列になっている");
Assert(dto.Priority == "High", "Priorityが文字列になっている");
Assert(dto.IsCompleted, "IsCompletedが引き継がれる");
Console.WriteLine("全テスト成功");
public record TaskDto(Guid Id, string Title, string Priority, bool IsCompleted);
public static class TaskMapper
{
// TODO: TaskItem を TaskDto に変換する ToDto を実装してください
___
}
void Assert(bool c, string n) { if (!c) throw new Exception($"FAIL: {n}"); }
// --- 型のコピー ---
public readonly struct TaskTitle : IEquatable<TaskTitle>
{
public string Value { get; }
public TaskTitle(string value)
{
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("タイトルは空にできません");
if (value.Length > 50) throw new ArgumentException("タイトルは50文字以内です");
Value = value.Trim();
}
public bool Equals(TaskTitle other) => Value == other.Value;
public override string ToString() => Value;
}
public enum Priority { Low, Medium, High }
public class TaskItem
{
public Guid Id { get; }
public TaskTitle Title { get; private set; }
public Priority Priority { get; private set; }
public bool IsCompleted { get; private set; }
public TaskItem(Guid id, TaskTitle title, Priority priority)
{ Id = id; Title = title; Priority = priority; }
public void Complete() => IsCompleted = true;
public void Rename(TaskTitle newTitle) => Title = newTitle;
}
public static class TaskFactory
{
public static TaskItem Create(string title, Priority priority = Priority.Medium)
=> new TaskItem(Guid.NewGuid(), new TaskTitle(title), priority);
}
- 期待される出力:
全テスト成功
ヒント1を見る
public static TaskDto ToDto(TaskItem task) => new(task.Id, task.Title.Value, task.Priority.ToString(), task.IsCompleted);
ヒント2を見る
TaskTitleは.Valueで文字列に、Priority(enum)は.ToString()で文字列に変換します
まとめ
TaskDtoは画面用のプレーンなデータの形、変換はTaskMapperに集約する- ViewやViewModelは
TaskTitleのバリデーションのような業務データの内部構造を知らずに済む - Entity(
TaskItem)→Repository→Service→Dtoという流れがここまでで揃った
次章: このTaskDtoを状態として持つViewModelを作ります。