導入
ViewModelのプロパティが変わったとき、画面(View)に「変わったよ」と伝える仕組みが必要です。これがないと、コードの上では値が変わっていても画面表示は古いままになります。ただし、ViewModelを作るたびに「通知する仕組み」を1から書くのは面倒です。共通の土台ViewModelBaseを作り、この面倒を1箇所に押し込みましょう。
図解
classDiagram
class PropertyChangedListener {
<<interface>>
+onPropertyChanged(propertyName)
}
class ViewModelBase {
+addPropertyChangedListener(listener)
#raisePropertyChanged(propertyName)
}
class TaskListViewModel {
+getTasks()
+getNewTitle()
}
ViewModelBase --> PropertyChangedListener : 通知する
ViewModelBase <|-- TaskListViewModel
説明
ViewModelBase.java
import java.util.ArrayList;
import java.util.List;
public interface PropertyChangedListener {
void onPropertyChanged(String propertyName);
}
public abstract class ViewModelBase {
private final List<PropertyChangedListener> listeners = new ArrayList<>();
public void addPropertyChangedListener(PropertyChangedListener listener) {
listeners.add(listener);
}
protected void raisePropertyChanged(String propertyName) {
for (PropertyChangedListener listener : listeners) {
listener.onPropertyChanged(propertyName);
}
}
}
PersonViewModel.java(使い方の例)
class PersonViewModel extends ViewModelBase {
private String name = "";
public String getName() { return name; }
public void setName(String value) {
name = value;
raisePropertyChanged("name"); // 変更を登録済みの全リスナーへ通知する
}
}
PersonViewModel person = new PersonViewModel();
person.addPropertyChangedListener(name -> System.out.println("変わったプロパティ: " + name));
person.setName("Alice");
// 変わったプロパティ: name
PropertyChangedListenerは「プロパティ名を1つ受け取るだけ」の単純なインターフェース。ラムダで渡せるViewModelBaseはリスナーの一覧を持ち、raisePropertyChangedが呼ばれたら全員に通知するViewModelBaseを継承するだけで、以降のViewModelは値が変わったところでraisePropertyChanged("プロパティ名")を呼ぶだけでよくなる
やってみよう
下のエディタで、リスナーを2つ登録して、どちらにも通知が届くことを確かめてみましょう。
演習
エディタはViewModelBase.java(あなたが実装するファイル)とViewModelBaseTest.java(テスト)の2つのタブに分かれています。
import java.util.ArrayList;
import java.util.List;
public interface PropertyChangedListener {
void onPropertyChanged(String propertyName);
}
// TODO: ViewModelBase を完成させてください
// ・addPropertyChangedListener(listener) でリスナーを登録できる
// ・raisePropertyChanged(propertyName) は、登録されている全リスナーに
// propertyName を渡して通知する(サブクラスから呼べるように protected にする)
public abstract class ViewModelBase {
}
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class ViewModelBaseTest {
@Test
void ataiGaKawaruto_ListenerNiTsuchiSareru() {
List<String> notified = new ArrayList<>();
PersonViewModel person = new PersonViewModel();
person.addPropertyChangedListener(name -> notified.add(name));
person.setName("Alice");
assertEquals("Alice", person.getName(), "値が変わる");
assertEquals(1, notified.size(), "1回だけ通知される");
assertEquals("name", notified.get(0), "プロパティ名がnameで通知される");
}
@Test
void listenerWoFukusuTourokuSuruto_ZenInNiTsuchiSareru() {
List<String> a = new ArrayList<>();
List<String> b = new ArrayList<>();
PersonViewModel person = new PersonViewModel();
person.addPropertyChangedListener(name -> a.add(name));
person.addPropertyChangedListener(name -> b.add(name));
person.setName("Bob");
assertEquals(1, a.size(), "1人目にも通知される");
assertEquals(1, b.size(), "2人目にも通知される");
}
}
class PersonViewModel extends ViewModelBase {
private String name = "";
public String getName() { return name; }
public void setName(String value) {
name = value;
raisePropertyChanged("name");
}
}
- 期待される結果: 2件のテストがすべて成功
ヒント1を見る
private final List<PropertyChangedListener> listeners = new ArrayList<>(); public void addPropertyChangedListener(PropertyChangedListener listener) { listeners.add(listener); }
ヒント2を見る
protected void raisePropertyChanged(String propertyName) { for (PropertyChangedListener listener : listeners) { listener.onPropertyChanged(propertyName); } }
まとめ
PropertyChangedListenerは「プロパティが変わったこと」を画面へ知らせる単純な仕組み- 通知処理は
ViewModelBaseに共通化し、サブクラスはraisePropertyChangedを呼ぶだけにする - リスナーは複数登録でき、
raisePropertyChangedは登録されている全員に通知する
次回: この土台の上に、実際に状態を持つTaskListViewModelを作ります。