6日目のゴール
6日目のテーマも「1クラス1役割」。
ただし今日は、ここまでやってきたことを一段“抽象化”して、
「役割そのものをインターフェースとして切り出す」 ところまで行きます。
キーワードはそのまま
責務・分離・保守性。
でも今日は、
「クラス」ではなく「役割(インターフェース)」という視点で
それをもう一段強くする 日です。
ここまでの状態を一度整理する
今のファイル保存アプリの構造
あなたのファイル保存アプリは、すでにこうなっています。
Memo
メモというデータ(ID・日付・本文)を表すだけのクラス。
MemoRepository
メモをファイルに保存・読み込みするクラス。
保存形式やファイルパス、文字コードなどを知っている。
App(たとえば MemoApp)
ユーザーと会話し、メニューを出し、
MemoRepository に「保存して」「読み込んで」と依頼するクラス。
責務も分離されているし、
依存の向きも「App → Repository → ファイル」という形で整っています。
6日目では、ここに
「インターフェース」という“役割の型” を導入します。
なぜインターフェースが必要になるのか
「役割」と「実装」を分けたくなる瞬間
こんな未来を想像してみてください。
今はファイル保存だけど、
いつかデータベース保存にしたくなるかもしれない。
あるいは、
テストのときだけ「メモリ上だけで動くダミー保存」にしたくなるかもしれない。
このとき、
App がこう書かれているとします。
MemoRepository repo = new MemoRepository("memo.txt");
repo.save(memo);
Javaここでの問題は、
App が「MemoRepository という“具体的なクラス”」に
ベッタリ依存していることです。
App が本当に知りたいのは、
「メモを保存してくれる人」
「メモ一覧を返してくれる人」
という “役割” だけのはずです。
そこで登場するのが、
インターフェース です。
インターフェースで「役割そのもの」を表現する
役割をコードにする:MemoStore インターフェース
ファイル保存だろうが DB 保存だろうが、
「メモを保存する人」が持つべき操作は同じです。
メモを保存する
メモ一覧を取得する
ID で削除する
ID で更新する
これをインターフェースとして定義します。
public interface MemoStore {
void save(Memo memo) throws Exception;
List<Memo> findAll() throws Exception;
boolean deleteById(int id) throws Exception;
boolean updateBody(int id, String newBody) throws Exception;
}
Javaここでやっているのは、
「メモ保存という役割の“契約書”を作る」 ことです。
この契約書にサインしたクラスは、
どんな保存方法であっても構いません。
ファイルでもいいし、
DB でもいいし、
メモリ上のリストでもいい。
大事なのは、
「このメソッド群を提供する」という約束を守ること です。
既存の MemoRepository を「役割の実装」にする
FileMemoStore として名乗り直す
今までの MemoRepository は、
「ファイル保存の実装」でした。
これをインターフェースに沿わせて、
名前も役割が分かりやすいように変えてみます。
public class FileMemoStore implements MemoStore {
private final Path filePath;
public FileMemoStore(Path filePath) {
this.filePath = filePath;
}
@Override
public void save(Memo memo) throws IOException {
// ファイルに追記する実装
}
@Override
public List<Memo> findAll() throws IOException {
// ファイルから読み込む実装
}
@Override
public boolean deleteById(int id) throws IOException {
// 一時ファイルで再構築して削除
}
@Override
public boolean updateBody(int id, String newBody) throws IOException {
// 一時ファイルで再構築して更新
}
// private な補助メソッドたち(parseLine, formatLine, replaceFile など)
}
Javaここで重要なのは、
FileMemoStore は「MemoStore という役割を実装している」
という構造になったことです。
App から見ると、
「MemoStore を使っている」だけであり、
それがファイル版なのかどうかは
気にしなくてよくなります。
App 側から見ると何が変わるのか
「具体クラス」ではなく「役割」に依存する
インターフェース導入前の App は、
だいたいこんな感じでした。
public class MemoApp {
private final MemoRepository repository;
public MemoApp(MemoRepository repository) {
this.repository = repository;
}
}
Javaこれを、
「役割」に依存する形に変えます。
public class MemoApp {
private final MemoStore store;
public MemoApp(MemoStore store) {
this.store = store;
}
public void run() {
// ユーザー入力を受け取り、
// store.save(...), store.findAll(), store.deleteById(...) などを呼ぶ
}
}
Javaそして、アプリ起動時にこう組み立てます。
AppConfig config = new AppConfig();
Path memoPath = config.getMemoFile();
MemoStore store = new FileMemoStore(memoPath);
MemoApp app = new MemoApp(store);
app.run();
Javaここでのポイントは、
MemoApp は「FileMemoStore」という具体クラスを知らない
ということです。
MemoApp が知っているのは
「MemoStore という役割」だけ。
これが
責務・分離・保守性
の三つを一気に底上げします。
インターフェースが保守性をどう上げるのか
未来の変更シナリオで見てみる
インターフェースの威力は、
「変えたくなったとき」に本気を出します。
例えば、
「テストのときだけ、ファイルを触りたくない」
という状況を考えてみましょう。
テスト用に、
メモリ上だけで動く実装を作れます。
public class InMemoryMemoStore implements MemoStore {
private final List<Memo> memos = new ArrayList<>();
private int nextId = 1;
@Override
public void save(Memo memo) {
Memo withId = new Memo(nextId++, memo.getDate(), memo.getBody());
memos.add(withId);
}
@Override
public List<Memo> findAll() {
return new ArrayList<>(memos);
}
@Override
public boolean deleteById(int id) {
return memos.removeIf(m -> m.getId() == id);
}
@Override
public boolean updateBody(int id, String newBody) {
for (int i = 0; i < memos.size(); i++) {
Memo m = memos.get(i);
if (m.getId() == id) {
memos.set(i, new Memo(m.getId(), m.getDate(), newBody));
return true;
}
}
return false;
}
}
Javaそして、テスト時にはこう組み立てます。
MemoStore store = new InMemoryMemoStore();
MemoApp app = new MemoApp(store);
JavaApp のコードは一切変えません。
「役割に依存しているから、実装を差し替えられる」 わけです。
同じことが、
将来 DB 保存を導入したときにも起きます。
public class DbMemoStore implements MemoStore {
// JDBC や ORM を使った実装
}
Java本番では DbMemoStore、
ローカル開発では FileMemoStore、
テストでは InMemoryMemoStore、
という切り替えが、
App を一行も変えずにできる ようになります。
これが、
インターフェースがもたらす保守性の高さです。
「1クラス1役割」から「1インターフェース1役割」へ
役割を“クラスの中”だけでなく“外側”にも表現する
ここまでの流れを整理すると、
こういう段階を踏んでいます。
クラスを分ける
Memo/Repository/App に責務を分ける
依存を整える
上の層が下の層を知る形にする
境界線を引く
「ここから先はこのクラスの仕事」という線を意識する
そして今日、
役割そのものをインターフェースとして外に出した。
つまり、
「1クラス1役割」から
「1インターフェース1役割」へと、
設計の粒度が一段上がった わけです。
クラスは「役割の具体的な実装」。
インターフェースは「役割そのものの定義」。
この二段構えになると、
設計は一気に柔軟で、壊れにくくなります。
6日目で本当に掴んでほしいこと
今日のテーマは、
「役割をインターフェースとして切り出すことで、
責務・分離・保守性を一段引き上げる」 ことでした。
感覚として持って帰ってほしいのは、これです。
クラスは「この役割をこう実装します」という存在
インターフェースは「この役割はこういう操作を持ちます」という契約
App は「具体クラス」ではなく「役割(インターフェース)」に依存する
そうすると、実装を差し替えても App を変えなくて済む
それがそのまま、保守性の高さにつながる
あなたはもう、
「クラスをきれいに分けられる人」から、
「役割を抽象化して設計できる人」 に足を踏み入れています。
7日目では、
このインターフェース設計を振り返りながら、
「自分なりの OOP 設計ポリシー」を言葉にしていく ところまで行けます。

