Java再入門
市岡岳彦
2020/6/3
© 2020 SG Corporation 2
なんとなくで書いているコードをもうちょっとなんとかしたい。
Java再入門
© 2020 SG Corporation 3
Javaの古くからある機能を一度振り返っておこう。
Java再入門
© 2020 SG Corporation 4
サンプルコード
https://www.sgnet.co.jp/edu/2020050701_java.zip
Java再入門
© 2020 SG Corporation 5
◍ スコープ再考
◍ 抽象クラスと抽象メソッド
◍ 制御構造再考
◍ オブジェクトのライフサイクル
◍ 疎結合としてのサービス
目次
© 2020 SG Corporation 6
◍ スコープ再考
◍ 抽象クラスと抽象メソッド
◍ 制御構造再考
◍ オブジェクトのライフサイクル
◍ 疎結合としてのサービス
目次
© 2020 SG Corporation 7
Javaのスコープはpublic,private,protectedだよね。
ん?なんか忘れてないか。
スコープ再考
© 2020 SG Corporation 8
 アクセス修飾子がないメソッド
スコープ再考
int foo() …
© 2020 SG Corporation 9
 なぜ、Javaのデフォルトスコープがパッケージなのか?
 パッケージを小さなライブラリと考える。
 パッケージ内のクラスが協働して小さな問題を解決する。
スコープ再考
© 2020 SG Corporation 10
 なぜ、Javaのデフォルトスコープがパッケージなのか?
 パッケージ外からは見えないけど、中からは見える。
 1クラス中のprivateメソッドが多くなってきたら、パッケージプ
ライベートなクラスを作るべきサイン。
 テスト容易性という副産物も。
スコープ再考
© 2020 SG Corporation 11
問題1
 ファクトリメソッドでオブジェクトを構築した後に値を変更した
いけど、外部からはアクセスさせたくない。
スコープ再考
© 2020 SG Corporation 12
問題1 解答例の概要
従業員管理
 従業員はチームに所属する。
 従業員は複数チームに所属してもいい。
 チームは固定とする。
 従業員にどのチームに所属しているかを問い合わせるインター
フェースがある。
 ある従業員を後からチームに追加したいが、勝手に個々の従業員
オブジェクトにチームを追加されるのも困る。
スコープ再考
© 2020 SG Corporation 13
問題1 解答例の概要
 クラス
 Book 管理簿
 Employee 従業員
 Team チーム
スコープ再考 サンプルコード:1_scope
© 2020 SG Corporation 14
問題1解答編
 パブリック
 😄後からチームを追加することができる。
 😓管理簿内の従業員を外から変更できてしまう。
スコープ再考
public final class Employee {
private final EnumSet<Team> teams;
…
public boolean addTeam(Team team) {
return teams.add(team);
}
サンプルコード:1_scope/1_1
© 2020 SG Corporation 15
問題1解答編
 防御的コピー
 😄後からチームを追加しても、管理簿内の従業員は変わらない。
 😓逆に、管理簿に反映されていない!という誤解を生む。
→インターフェースがデザインの動機を表していない。
スコープ再考
public final class Book {
private final Map<Integer, Employee> employees =
…
public void add(Employee employee) {
employees.put(employee.getID(),
Employee.create(employee.getID(),
employee.getTeams()));
サンプルコード:1_scope/1_2
© 2020 SG Corporation 16
問題1解答編
 パッケージプライベート
 😄チーム追加は必ず管理簿を通して行うことが明確になる。
 😓パッケージプライベートにしている意図を分かっていないと、
改修時にパブリックにしがち。
スコープ再考
public final class Employee {
public boolean addTeam(Team team) {
…
public final class Book {
public boolean addEmployeeTeam(int id, Team team) {
…
employee.addTeam(team);
サンプルコード:1_scope/1_3
© 2020 SG Corporation 17
 スコープでメソッドの意図を伝える one more step
 公開APIと拡張ポイントが一緒になっている…
スコープ再考
public abstract int foo();
© 2020 SG Corporation 18
 スコープでメソッドの意図を伝える one more step
 公開APIと拡張ポイントを明示する。
 オーバーライド側はこれ以上継承されたくなければ、finalにする。
スコープ再考
public final int foo() {
return fooImpl();
}
protected abstract int fooImpl();
© 2020 SG Corporation 19
 スコープでクラスの意図を伝える one more step
 クラス自体は公開したいが、継承はパッケージ内のみとしたい…
スコープ再考
public class Foo {
…
}
© 2020 SG Corporation 20
 スコープでクラスの意図を伝える one more step
 継承をパッケージ内のみに許可する。
 コンストラクタをパッケージプライベートにすることによって、
実質的finalにする。
スコープ再考
public class Foo {
Foo() {
…
}
…
}
© 2020 SG Corporation 21
 補足:コンストラクタチェーン
 コンストラクタが呼び出されたとき何が起きるか?
 コンストラクタ内で明示的にsuperを呼び出さない場合は、暗黙
的にスーパークラスの引数なしのコンストラクタが呼び出される。
 このとき、スーパークラスのコンストラクタにアクセス不可能だ
と、コンパイル時にエラーになる。
 明示的にsuperを呼び出した場合も同じ。
スコープ再考
© 2020 SG Corporation 22
まとめ
 パッケージを小さなライブラリと考える
 パッケージ内のクラスが協働して小さな問題を解決する
 修飾子でクラスデザインの意図を伝える
 メソッドのオーバーライド可能性に注意する
スコープ再考
© 2020 SG Corporation 23
◍ スコープ再考
◍ 抽象クラスと抽象メソッド
◍ 制御構造再考
◍ オブジェクトのライフサイクル
◍ 疎結合としてのサービス
目次
© 2020 SG Corporation 24
 抽象クラスと抽象メソッドをどう使うかは悩みどころ。
 ふるまいを一般化する場合は、基本的にはインターフェースを選
択するけど、抽象クラスはどういうときに使うか。
abstract…?
抽象クラスと抽象メソッド
© 2020 SG Corporation 25
 protectedメソッドを呼ぶpublicメソッドでメソッド間の関係性を
保証する。
 例)java.util.AbstractList, java.util.AbstractSequentialList
 どのような実装であれ、リストとして期待される動作を保証する。
抽象クラスと抽象メソッド
© 2020 SG Corporation 26
問題2
 多態性は持たせたいが、自由に継承はさせたくない。
 関係のあるメソッド間の契約を保証したい。
抽象クラスと抽象メソッド
© 2020 SG Corporation 27
問題2 解答例の概要
給与計算
 基本給と手当がある。
 給与は 基本給+手当 とする。
 手当は 単価×時間 とするが、計算式が異なる可能性がある。
 手当の計算式が異なっても、給与=基本給+手当 は保証する。
抽象クラスと抽象メソッド サンプルコード:2_abstract
© 2020 SG Corporation 28
問題2 解答例の概要
 クラス
 Salary 給与
抽象クラスと抽象メソッド サンプルコード:2_abstract
© 2020 SG Corporation 29
問題2解答編
 パブリックコンストラクタ
 😄わかりやすい。
 😓勝手に継承される。
抽象クラスと抽象メソッド
public class Salary {
public Salary(int base, int wage) {
サンプルコード:2_abstract/2_1
© 2020 SG Corporation 30
問題2解答編
 パッケージプライベートコンストラクタ
 😄継承をパッケージ内のみにとどめる。
 😓オーバーライドするポイントが分からない。
抽象クラスと抽象メソッド
public class Salary {
public Salary(int base, int wage) {
…
public static Salary create(int base, int wage) {
return new Salary(base, wage);
}
サンプルコード:2_abstract/2_2
© 2020 SG Corporation 31
問題2解答編
 abstractメソッド
 😄オーバーライドするポイントが分かる。
 😓意図したオーバーライドポイント以外もオーバーライドできて
しまう。
抽象クラスと抽象メソッド
public abstract class Salary {
public abstract int getBase();
…
public abstract int getWage(int hour);
サンプルコード:2_abstract/なし
© 2020 SG Corporation 32
問題2解答編
 finalメソッド
 😄オーバーライド不可なポイントを守ることにより、メソッド間
の契約を保証できる。
抽象クラスと抽象メソッド
public abstract class Salary {
public final int getBase() {
return getBaseImpl();
}
protected abstract int getBaseImpl();
…
public final int total(int hour) {
return getBase() + getWage(hour);
}
サンプルコード:2_abstract/2_3
© 2020 SG Corporation 33
問題2 いったんまとめ
 abstractメソッドは最小限にする。publicメソッドはabstractメ
ソッド同士の関係性を定義する。
 拡張性を公開しないのであれば、公開するべきではない。
 公開ポイントと拡張ポイントは区別する。
抽象クラスと抽象メソッド
© 2020 SG Corporation 34
問題2解答編
 公開クラスと拡張クラスを分ける。
 😄拡張ポイントを分割・明示できる。
抽象クラスと抽象メソッド
public final class Salary {
private final SalaryImpl salaryImpl;
Salary(int base, int wage, SalaryImpl salaryImpl) {
…
this.salaryImpl = salaryImpl;
static interface SalaryImpl {
サンプルコード:2_abstract/2_4
© 2020 SG Corporation 35
問題2解答編
 拡張クラスを公開する。
 😄拡張ポイントのみを別個に公開できる。
抽象クラスと抽象メソッド
public final class Salary {
private final SalaryImpl salaryImpl;
Salary(int base, int wage, SalaryImpl salaryImpl) {
…
this.salaryImpl = salaryImpl;
public interface SalaryImpl {
サンプルコード:2_abstract/2_5
© 2020 SG Corporation 36
「疎結合としてのサービス」
で、もうちょっとやります。
抽象クラスと抽象メソッド
© 2020 SG Corporation 37
まとめ
 抽象クラスは継承クラスを容易に作成するためのヘルパー
 公開メソッドは抽象メソッド同士の関係性を定義する
 公開メソッドはオーバーライド不可とする
抽象クラスと抽象メソッド
© 2020 SG Corporation 38
◍ スコープ再考
◍ 抽象クラスと抽象メソッド
◍ 制御構造再考
◍ オブジェクトのライフサイクル
◍ 疎結合としてのサービス
目次
© 2020 SG Corporation 39
Javaの制御構造はもちろんわかってるよ。
ifとかforとか…
制御構造再考
© 2020 SG Corporation 40
問題3
 単位処理数ごとのバッチ処理、コントロールブレイクをどう実現
するか。
 例)
 1000件ごと(バッチ)
 何かグループ化の条件が変わった場合(コントロールブレイク)
制御構造再考
© 2020 SG Corporation 41
問題3解答編
 そのままループ
 😄シンプルな解決策
 😓外側のループと内側のループでiを共有しているので分かりにく
い。
制御構造再考
for (int i = 0; i < employees.size(); i += 1000) {
for (int id : employees.subList(i,
Math.min(i + 1000, employees.size()))) {
for (int i = 0; i < employees.size();) {
for (int from = i; !isBreakIndex(employees, from, i); i++) {
サンプルコード:3_control/3_1
© 2020 SG Corporation 42
問題3解答編
 そのままループ その2(バッチ)
 😄外側と内側のループを分けた。
制御構造再考
for (int i = 0, n = 0; i < employees.size(); i += n) {
n = batchLoopSub(employees, i);
…
private int batchLoopSub(List<Employee> employees,
int fromIndex) {
int count =
Math.min(fromIndex + 1000, employees.size());
for (int id : employees.subList(fromIndex, count)) {
…
return count - fromIndex;
サンプルコード:3_control/3_2
© 2020 SG Corporation 43
問題3解答編
 そのままループ その2 (コントロールブレイク)
 😄外側と内側のループを分けた。
制御構造再考
for (int i = 0, n = 0; i < employees.size(); i += n) {
n = controlBreakLoopSub(employees, i);
…
private int controlBreakLoopSub(List<Employee> employees,
int fromIndex) {
int i = fromIndex;
for (; !isBreakIndex(employees, fromIndex, i); i++) {
…
return i - fromIndex;
サンプルコード:3_control/3_2
© 2020 SG Corporation 44
問題3解答編
なんか似てないか、これ
制御構造再考
© 2020 SG Corporation 45
問題3解答編
 そのままループ その2
 😕ここ以外同じだな…
制御構造再考
int count =
Math.min(fromIndex + 1000, employees.size());
for (int id : employees.subList(fromIndex, count)) {
int i = fromIndex;
for (; !isBreakIndex(employees, fromIndex, i); i++) {
サンプルコード:3_control/3_2
© 2020 SG Corporation 46
問題3解答編
 そのままループ その3
 バッチのループ継続条件をメソッド化
 😳同じ構造になった!
制御構造再考
int i = fromIndex;
for (; !isBatchIndex(employees, fromIndex, i); i++) {
private boolean isBatchIndex(List<Employee> employees,
int from, int index) {
return index == Math.min(from + 1000, employees.size());
}
サンプルコード:3_control/3_3
© 2020 SG Corporation 47
問題3解答編
同じ構造のものはまとめよう
制御構造再考
© 2020 SG Corporation 48
問題3解答編
 ループ用クラス
制御構造再考
public final class Loop<E> {
@FunctionalInterface
public static interface ListBreak<E> {
boolean test(List<E> list, int from, int index);
}
private final ListBreak<E> listBreak;
public Loop(ListBreak<E> listBreak) {
this.listBreak = listBreak;
}
サンプルコード:3_control/3_3
© 2020 SG Corporation 49
問題3解答編
 ループ用クラス 続き
制御構造再考
…
public void loop(List<E> list) {
for (int i = 0, n = 0; i < list.size(); i += n) {
n = loopSub(list, i);
…
private int loopSub(List<E> list, int fromIndex) {
int i = fromIndex;
for (; !listBreak.test(list, fromIndex, i); i++) {
…
return i - fromIndex;
…
サンプルコード:3_control/3_3
© 2020 SG Corporation 50
問題3解答編
 ループ用クラス 呼び出し側
 😄バッチとコントロールブレイクでループを共通化できた。
 😓ループの中身である処理本体は同じことしかできない。
制御構造再考
Loop<Employee> batch =
new Loop<>(Main::batchBreakIndex);
batch.loop(employees);
Loop<Employee> control =
new Loop<>(Main::controlBreakIndex);
control.loop(employees);
サンプルコード:3_control/3_3
© 2020 SG Corporation 51
問題3解答編
 ビューのリストに変形する
制御構造再考
public final class Loop<E> {
…
public List<List<E>> listView(List<E> list) {
List<List<E>> view = new ArrayList<>();
for (int i = 0, n = 0; i < list.size(); i += n) {
n = loopSub(list, i);
view.add(list.subList(i, i + n));
}
return view;
}
サンプルコード:3_control/3_4
© 2020 SG Corporation 52
問題3解答編
 ビューのリストに変形する 呼び出し側
 😄ループの処理本体を外に出せた。
 😄呼び出し側は一般的な拡張for文のみで対応できる。
 😓一時的ではあるが、リストの生成が必要になる。
制御構造再考
Loop<Employee> batch =
new Loop<>(Main::batchBreakIndex);
for (List<Employee> list : batch.listView(employees)) {
for (Employee e : list) {
サンプルコード:3_control/3_3
© 2020 SG Corporation 53
問題3解答編
 ビューを返すイテレータを作る
制御構造再考
public final class Loop<E> implements Iterable<List<E>> {
…
private final List<E> list;
public Loop(List<E> list, ListBreak<E> listBreak) {
…
}
@Override public Iterator<List<E>> iterator() {
return new Iterator<List<E>>() {
…
}
}
サンプルコード:3_control/3_4
© 2020 SG Corporation 54
問題3解答編
 ビューを返すイテレータを作る 呼び出し側
 😄ブレイク条件のみを定義すれば、後はイテレータがやってくれ
る。
 😓初見ではわかりづらい。
制御構造再考
Loop<Employee> batch =
new Loop<>(employees, Main::batchBreakIndex);
for (List<Employee> list : batch) {
for (Employee e : list) {
サンプルコード:3_control/3_4
© 2020 SG Corporation 55
問題3解答編
せっかくなので、ブレイク条件も一般化しよう
制御構造再考
© 2020 SG Corporation 56
問題3解答編 one more step
 バッチブレイク条件
制御構造再考
static <E> ListBreak<E> batchBreakIndex(int size) {
return (list, from, index) ->
index == Math.min(from + size, list.size());
}
Loop<Employee> batch =
new Loop<>(employees, batchBreakIndex(1000));
サンプルコード:3_control/3_5
© 2020 SG Corporation 57
問題3解答編 one more step
 コントロールブレイク条件
制御構造再考
static <E> ListBreak<E> controlBreakIndex(
BiFunction<E, E, Boolean> test) {
return (list, from, index) -> {
if (index == list.size()) return true;
if (index == from) return false;
return !test.apply(list.get(index - 1), list.get(index));
};
}
Loop<Employee> control = new Loop<>(employees,
controlBreakIndex((a, b) -> Objects.equals(a, b)));
サンプルコード:3_control/3_5
© 2020 SG Corporation 58
 問題がデータ構造であるのなら、データ構造をプログラミングす
る。
 ビューとコピーを区別する。コピーが必要であればコピーする。
 問題が制御構造であるのなら、制御構造をプログラミングする。
 コピーが必要なければ、ビューで対応する。
 使う側は動作をプログラミングしなくてもいい。
 宣言型プログラミング
制御構造再考
© 2020 SG Corporation 59
 とは言え、そのプロジェクトで頻出のイディオムであるという場
合に使うべき。
 素直にループを書いたほうがシンプルな解決策であることには変
わりはない。
 簡明さは失われてしまった。
制御構造再考
© 2020 SG Corporation 60
 補足:ラムダ式・メソッド参照
 Java8以降、メソッド参照とラムダ式が使えるようになった。
 オーバーライド用のメソッドを1つだけ持つインターフェースを
実装する手間を減らす。
 これにより、ラッパークラスを作らなくても、シグネチャが合致
するメソッドをそのまま渡すことができる。
制御構造再考
@FunctionalInterface
public interface Runnable {
void run();
}
© 2020 SG Corporation 61
 補足:ラムダ式・メソッド参照
制御構造再考
public static void main(String ... args) {
ExecutorService service = Executors.newSingleThreadExecutor();
// 無名クラス
service.execute(new Runnable() {
public void run() { print(); }
});
// メソッド参照
service.execute(Main::print);
// ラムダ式
service.execute(() -> print());
service.shutdown();
}
private static void print() {
System.out.println("run.");
}
© 2020 SG Corporation 62
まとめ
 問題がデータ構造であるのなら、データ構造をプログラミングす
る
 問題が制御構造であるのなら、制御構造をプログラミングする
 再利用性と簡明さはトレードオフ(の場合がある)
制御構造再考
© 2020 SG Corporation 63
◍ スコープ再考
◍ 抽象クラスと抽象メソッド
◍ 制御構造再考
◍ オブジェクトのライフサイクル
◍ 疎結合としてのサービス
目次
© 2020 SG Corporation 64
オブジェクトにはcloseとかfreeとかないよね…
オブジェクトのライフサイクル
© 2020 SG Corporation 65
 Javaは明示的にメモリ領域を管理しない。
 オブジェクトのライフサイクルを気にしなくていいということで
はない。
オブジェクトのライフサイクル
© 2020 SG Corporation 66
問題4
 登録されたイベントリスナーをイベント発生時に呼び出す。
 リスナーが途中でいなくなっても、登録された側で保持している
と、GCで回収されない。
オブジェクトのライフサイクル
© 2020 SG Corporation 67
問題4 解答例の概要
従業員管理
 管理簿に従業員の追加を依頼する。
 追加依頼は非同期に終了する。
 追加完了時にリスナーにコールバックがかかる。
オブジェクトのライフサイクル サンプルコード:4_lifecycle
© 2020 SG Corporation 68
問題4 解答例の概要
 クラス
 Book 管理簿
オブジェクトのライフサイクル サンプルコード:4_lifecycle
© 2020 SG Corporation 69
問題4解答編
 リスナーの登録のみをする
オブジェクトのライフサイクル
public final class Book {
@FunctionalInterface
public static interface Listener {
void action(Employee employee);
}
private final Map<Listener, Object> listeners =
new ConcurrentHashMap<>();
public void addListener(Listener listener) {
listeners.put(listener, new Object());
}
サンプルコード:4_lifecycle/4_1
© 2020 SG Corporation 70
問題4解答編
 リスナーの登録のみをする 続き
オブジェクトのライフサイクル
public void addAsync(Employee employee) {
addService.execute(new Runnable() {
@Override public void run() {
add(employee);
actionAddEvent(employee);
}
});
}
サンプルコード:4_lifecycle/4_1
© 2020 SG Corporation 71
問題4解答編
 リスナーの登録のみをする 続き
 😓リスナーがいなくなっても、オブジェクトがGCで回収されない。
オブジェクトのライフサイクル
private void actionAddEvent(Employee employee) {
for (Listener l : listeners.keySet()) {
l.action(employee);
}
}
サンプルコード:4_lifecycle/4_1
© 2020 SG Corporation 72
問題4解答編
 削除APIも公開する
 😄削除メソッドが呼ばれれば、GCで回収される。
 😓公開APIだと、削除メソッドが呼ばれる保証がない。
オブジェクトのライフサイクル
public void removeListener(Listener listener) {
listeners.remove(listener);
}
サンプルコード:4_lifecycle/4_2
© 2020 SG Corporation 73
問題4解答編
 弱い参照
 マップ内のリスナーを弱い参照(WeakReference)で保持する。
オブジェクトのライフサイクル
public final class Book {
private final Map<WeakReference<Listener>, Object>
listeners = new ConcurrentHashMap<>();
public void addListener(Listener listener) {
listeners.put(new WeakReference<>(listener),
new Object());
}
サンプルコード:4_lifecycle/4_3
© 2020 SG Corporation 74
問題4解答編
 弱い参照 続き
オブジェクトのライフサイクル
private void actionAddEvent(Employee employee) {
for (Iterator<WeakReference<Listener>> itr =
listeners.keySet().iterator(); itr.hasNext(); ) {
Listener l = itr.next().get();
if (l == null) {
itr.remove();
continue;
}
l.action(employee);
}
}
サンプルコード:4_lifecycle/4_3
© 2020 SG Corporation 75
問題4解答編
 弱い参照
 😄通常の参照が0件になれば、GCで回収される。
 😄オブジェクトのライフサイクルを気にしていないことを明示で
きる。
 😓API仕様で、弱い参照をしていることを明示しておく必要があ
る。
オブジェクトのライフサイクル
© 2020 SG Corporation 76
 ブロードキャストする側はオブジェクトのライフサイクルを気に
しない。
 登録と登録解除を提供してもいいけど、ユーザーが登録解除を呼
ぶ保証はない!
 オブジェクトのライフサイクルを気にしていないということを明
示する。
オブジェクトのライフサイクル
© 2020 SG Corporation 77
 オブジェクト生成 one more step
 派生型が絡むと多少厄介…
オブジェクトのライフサイクル
© 2020 SG Corporation 78
 リストをコピーするメソッドのデザインとして、2通りが考えら
れる。
アウトパラメーターとする
戻り値とする
オブジェクトのライフサイクル
<E> void copyList(List<E> dest, List<E> src)
<E> List<E> copyList(List<E> src)
© 2020 SG Corporation 79
 アウトパラメーター
 呼び出し元がコピー先リストを生成する
 リストの具象クラスを呼び出し元が決めたい場合
 呼び出し先は実行時クラスが何かを気にしていない
 メソッドがアルゴリズムを表している場合はこのパターン
オブジェクトのライフサイクル
<E> void copyList(List<E> dest, List<E> src)
© 2020 SG Corporation 80
 戻り値
 呼び出し先がコピー先リストを生成する
 リストの具象クラスを呼び出し先が決めたい場合
 呼び出し元は実行時クラスが何かを気にしていない
 メソッドがファクトリーを表している場合はこのパターン
オブジェクトのライフサイクル
<E> List<E> copyList(List<E> src)
© 2020 SG Corporation 81
まとめ
 オブジェクトのライフサイクルを気にしなくていいということで
はない
 オブジェクトの生成消滅に対する責務をどこが担っているかを表
現する
 メソッドがアルゴリムなのか、ファクトリーなのかでも責務が変
わってくる
オブジェクトのライフサイクル
© 2020 SG Corporation 82
◍ スコープ再考
◍ 抽象クラスと抽象メソッド
◍ 制御構造再考
◍ オブジェクトのライフサイクル
◍ 疎結合としてのサービス
目次
© 2020 SG Corporation 83
突然ですが、MySQLに接続しよう…
疎結合としてのサービス
© 2020 SG Corporation 84
 JDBCドライバ(というかコネクション)を取得する例
 😳“jdbc:mysql://〜”を渡すとMySQLのドライバが取得できる!
疎結合としてのサービス
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/testdb",
"user", "pass");
© 2020 SG Corporation 85
JDBCドライバのような機構はどのように実現されているのか?
疎結合としてのサービス
© 2020 SG Corporation 86
問題5
 状態を保存したいが、保存先をプロパティファイル、XML、DB等
で切り替えたい。
疎結合としてのサービス
© 2020 SG Corporation 87
問題5 解答例の概要
従業員管理
 従業員管理簿を外部に保存する。
 外部保存の実装は継承で拡張できる。
疎結合としてのサービス サンプルコード:5_service
© 2020 SG Corporation 88
問題5 解答例の概要
 クラス
 Book 管理簿
 BookHelper 管理簿と保存機能の橋渡し
 Persistance 保存機能
疎結合としてのサービス サンプルコード:5_service
© 2020 SG Corporation 89
問題5解答編
 ファクトリメソッドで文字列から作成
疎結合としてのサービス
public abstract class Persistance {
public static Persistance create(String persist) throws
IOException {
switch (persist) {
case "xml":
return new XMLPersistance();
case "db":
return new DBPersistance();
default:
throw new IOException("No suitable provider found for ”
+ persist);
…
サンプルコード:5_service/5_1
© 2020 SG Corporation 90
問題5解答編
 ファクトリメソッドで文字列から作成
 😄難しい仕組みがいらない。
 😓コンパイル時に実装クラスが必要。
疎結合としてのサービス
© 2020 SG Corporation 91
問題5解答編
 リフレクションで実現
 😄仕組みがまあまあ簡潔。
 😓インスタンス生成するクラスが公開サービスなのかが不明確。
 😓パッケージ名とサービス名が直接結びついてしまう。
疎結合としてのサービス
public abstract class Persistance {
public static Persistance create(String persist) throws … {
return
Class.forName(persist).asSubclass(Persistance.class).
getDeclaredConstructor().newInstance();
}
サンプルコード:5_service/5_2
© 2020 SG Corporation 92
問題5解答編
 サービスで実現
疎結合としてのサービス
public interface PersistanceService {
boolean acceptName(String name);
void read(BookHelper book) throws IOException;
void write(BookHelper book) throws IOException;
}
サンプルコード:5_service/5_3
© 2020 SG Corporation 93
問題5解答編
 サービスで実現 続き
 😄仕組みがまあまあ簡潔。
 😓インスタンス生成するクラスが公開サービスなのかが不明確。
 😓パッケージ名とサービス名が直接結びついてしまう。
疎結合としてのサービス
public final class Book {
private final PersistanceService service;
public static Book createPersistance(String persist) throws
IOException {
PersistanceService found = findPersistanceService(persist);
if (found == null) {
throw new IOException("No suitable provider found for "
+ persist);
}
return new Book(found);
}
サンプルコード:5_service/5_3
© 2020 SG Corporation 94
問題5解答編
 サービスで実現 続き
疎結合としてのサービス
private static PersistanceService findPersistanceService(
String persist) {
for (PersistanceService service :
ServiceLoader.load(PersistanceService.class)) {
if (service.acceptName(persist)) return service;
}
return null;
}
サンプルコード:5_service/5_3
© 2020 SG Corporation 95
問題5解答編
 サービスで実現 続き
 😄サービスとして公開していることを明示できる。
 😄パッケージ名とサービス名を間接的な結びつきにできる。
 😓若干、仕組みが面倒。
疎結合としてのサービス
公開するサービスプロバイダー名を記載した
META-
INF/services/jp.co.sgnet.hrp.skillup202001.hr.spi.PersistanceService
を配置
jp.co.sgnet.hrp.skillup202001.hr.spi.XMLPersistanceServiceProvider
サンプルコード:5_service/5_3
© 2020 SG Corporation 96
 APIとSPI
 API : Application Programming Interface
 SPI : Service Provider Interface
 APIとSPIのクラス階層は分けておく。
 API側の実装クラスが構築時にSPIのオブジェクトをもらって、中
にオブジェクトとして持つ形式。
 APIそのものが継承されることを前提としていることは少ないは
ず。
疎結合としてのサービス
© 2020 SG Corporation 97
 補足: Java9のモジュール機構
 Java9以降、アクセス修飾子とは別にjarの外側に公開する/しな
いを制御することができるようになった。
疎結合としてのサービス
© 2020 SG Corporation 98
 補足: Java9のモジュール機構
 module-info.javaにクラスの公開/非公開を記載する。
疎結合としてのサービス
module local.api {
exports local.api;
uses local.api.MyService;
}
公開側: local.apiパッケージ
module local.app {
requires local.api;
uses local.api.MyService;
}
利用側: local.appパッケージ
module local.provider {
requires local.api;
provides local.api.MyService with
local.provider.MyServiceProvider1;
}
実装側: local.providerパッケージ
© 2020 SG Corporation 99
 補足: Java9のモジュール機構
疎結合としてのサービス
private static PersistanceService findPersistanceService(
String persist) {
for (PersistanceService service :
ServiceLoader.load(PersistanceService.class)) {
if (service.acceptName(persist)) return service;
}
return null;
}
© 2020 SG Corporation 100
まとめ
 APIとSPIは分けておく
 APIは継承を前提としない
 SPIは継承を前提とする
疎結合としてのサービス
© 2020 SG Corporation 101
◍ Effective Java 第3版
Joshua Bloch (著), 柴田 芳樹 (翻訳)
丸善出版
◍ APIデザインの極意 Java/NetBeansアーキテクト探究ノート
Jaroslav Tulach (著), 柴田 芳樹 (翻訳)
インプレス
参考文献
© 2020 SG Corporation 102
◍ SGソフトウェア開発ブログ
https://blog.sgnet.co.jp
◍ SlideShare公開資料
https://www.slideshare.net/t_ichioka_sg/
こちらもどうぞ
© 2020 SG Corporation 103
顧客と社員に信頼されるエス・ジー

Java class design

  • 1.
  • 2.
    © 2020 SGCorporation 2 なんとなくで書いているコードをもうちょっとなんとかしたい。 Java再入門
  • 3.
    © 2020 SGCorporation 3 Javaの古くからある機能を一度振り返っておこう。 Java再入門
  • 4.
    © 2020 SGCorporation 4 サンプルコード https://www.sgnet.co.jp/edu/2020050701_java.zip Java再入門
  • 5.
    © 2020 SGCorporation 5 ◍ スコープ再考 ◍ 抽象クラスと抽象メソッド ◍ 制御構造再考 ◍ オブジェクトのライフサイクル ◍ 疎結合としてのサービス 目次
  • 6.
    © 2020 SGCorporation 6 ◍ スコープ再考 ◍ 抽象クラスと抽象メソッド ◍ 制御構造再考 ◍ オブジェクトのライフサイクル ◍ 疎結合としてのサービス 目次
  • 7.
    © 2020 SGCorporation 7 Javaのスコープはpublic,private,protectedだよね。 ん?なんか忘れてないか。 スコープ再考
  • 8.
    © 2020 SGCorporation 8  アクセス修飾子がないメソッド スコープ再考 int foo() …
  • 9.
    © 2020 SGCorporation 9  なぜ、Javaのデフォルトスコープがパッケージなのか?  パッケージを小さなライブラリと考える。  パッケージ内のクラスが協働して小さな問題を解決する。 スコープ再考
  • 10.
    © 2020 SGCorporation 10  なぜ、Javaのデフォルトスコープがパッケージなのか?  パッケージ外からは見えないけど、中からは見える。  1クラス中のprivateメソッドが多くなってきたら、パッケージプ ライベートなクラスを作るべきサイン。  テスト容易性という副産物も。 スコープ再考
  • 11.
    © 2020 SGCorporation 11 問題1  ファクトリメソッドでオブジェクトを構築した後に値を変更した いけど、外部からはアクセスさせたくない。 スコープ再考
  • 12.
    © 2020 SGCorporation 12 問題1 解答例の概要 従業員管理  従業員はチームに所属する。  従業員は複数チームに所属してもいい。  チームは固定とする。  従業員にどのチームに所属しているかを問い合わせるインター フェースがある。  ある従業員を後からチームに追加したいが、勝手に個々の従業員 オブジェクトにチームを追加されるのも困る。 スコープ再考
  • 13.
    © 2020 SGCorporation 13 問題1 解答例の概要  クラス  Book 管理簿  Employee 従業員  Team チーム スコープ再考 サンプルコード:1_scope
  • 14.
    © 2020 SGCorporation 14 問題1解答編  パブリック  😄後からチームを追加することができる。  😓管理簿内の従業員を外から変更できてしまう。 スコープ再考 public final class Employee { private final EnumSet<Team> teams; … public boolean addTeam(Team team) { return teams.add(team); } サンプルコード:1_scope/1_1
  • 15.
    © 2020 SGCorporation 15 問題1解答編  防御的コピー  😄後からチームを追加しても、管理簿内の従業員は変わらない。  😓逆に、管理簿に反映されていない!という誤解を生む。 →インターフェースがデザインの動機を表していない。 スコープ再考 public final class Book { private final Map<Integer, Employee> employees = … public void add(Employee employee) { employees.put(employee.getID(), Employee.create(employee.getID(), employee.getTeams())); サンプルコード:1_scope/1_2
  • 16.
    © 2020 SGCorporation 16 問題1解答編  パッケージプライベート  😄チーム追加は必ず管理簿を通して行うことが明確になる。  😓パッケージプライベートにしている意図を分かっていないと、 改修時にパブリックにしがち。 スコープ再考 public final class Employee { public boolean addTeam(Team team) { … public final class Book { public boolean addEmployeeTeam(int id, Team team) { … employee.addTeam(team); サンプルコード:1_scope/1_3
  • 17.
    © 2020 SGCorporation 17  スコープでメソッドの意図を伝える one more step  公開APIと拡張ポイントが一緒になっている… スコープ再考 public abstract int foo();
  • 18.
    © 2020 SGCorporation 18  スコープでメソッドの意図を伝える one more step  公開APIと拡張ポイントを明示する。  オーバーライド側はこれ以上継承されたくなければ、finalにする。 スコープ再考 public final int foo() { return fooImpl(); } protected abstract int fooImpl();
  • 19.
    © 2020 SGCorporation 19  スコープでクラスの意図を伝える one more step  クラス自体は公開したいが、継承はパッケージ内のみとしたい… スコープ再考 public class Foo { … }
  • 20.
    © 2020 SGCorporation 20  スコープでクラスの意図を伝える one more step  継承をパッケージ内のみに許可する。  コンストラクタをパッケージプライベートにすることによって、 実質的finalにする。 スコープ再考 public class Foo { Foo() { … } … }
  • 21.
    © 2020 SGCorporation 21  補足:コンストラクタチェーン  コンストラクタが呼び出されたとき何が起きるか?  コンストラクタ内で明示的にsuperを呼び出さない場合は、暗黙 的にスーパークラスの引数なしのコンストラクタが呼び出される。  このとき、スーパークラスのコンストラクタにアクセス不可能だ と、コンパイル時にエラーになる。  明示的にsuperを呼び出した場合も同じ。 スコープ再考
  • 22.
    © 2020 SGCorporation 22 まとめ  パッケージを小さなライブラリと考える  パッケージ内のクラスが協働して小さな問題を解決する  修飾子でクラスデザインの意図を伝える  メソッドのオーバーライド可能性に注意する スコープ再考
  • 23.
    © 2020 SGCorporation 23 ◍ スコープ再考 ◍ 抽象クラスと抽象メソッド ◍ 制御構造再考 ◍ オブジェクトのライフサイクル ◍ 疎結合としてのサービス 目次
  • 24.
    © 2020 SGCorporation 24  抽象クラスと抽象メソッドをどう使うかは悩みどころ。  ふるまいを一般化する場合は、基本的にはインターフェースを選 択するけど、抽象クラスはどういうときに使うか。 abstract…? 抽象クラスと抽象メソッド
  • 25.
    © 2020 SGCorporation 25  protectedメソッドを呼ぶpublicメソッドでメソッド間の関係性を 保証する。  例)java.util.AbstractList, java.util.AbstractSequentialList  どのような実装であれ、リストとして期待される動作を保証する。 抽象クラスと抽象メソッド
  • 26.
    © 2020 SGCorporation 26 問題2  多態性は持たせたいが、自由に継承はさせたくない。  関係のあるメソッド間の契約を保証したい。 抽象クラスと抽象メソッド
  • 27.
    © 2020 SGCorporation 27 問題2 解答例の概要 給与計算  基本給と手当がある。  給与は 基本給+手当 とする。  手当は 単価×時間 とするが、計算式が異なる可能性がある。  手当の計算式が異なっても、給与=基本給+手当 は保証する。 抽象クラスと抽象メソッド サンプルコード:2_abstract
  • 28.
    © 2020 SGCorporation 28 問題2 解答例の概要  クラス  Salary 給与 抽象クラスと抽象メソッド サンプルコード:2_abstract
  • 29.
    © 2020 SGCorporation 29 問題2解答編  パブリックコンストラクタ  😄わかりやすい。  😓勝手に継承される。 抽象クラスと抽象メソッド public class Salary { public Salary(int base, int wage) { サンプルコード:2_abstract/2_1
  • 30.
    © 2020 SGCorporation 30 問題2解答編  パッケージプライベートコンストラクタ  😄継承をパッケージ内のみにとどめる。  😓オーバーライドするポイントが分からない。 抽象クラスと抽象メソッド public class Salary { public Salary(int base, int wage) { … public static Salary create(int base, int wage) { return new Salary(base, wage); } サンプルコード:2_abstract/2_2
  • 31.
    © 2020 SGCorporation 31 問題2解答編  abstractメソッド  😄オーバーライドするポイントが分かる。  😓意図したオーバーライドポイント以外もオーバーライドできて しまう。 抽象クラスと抽象メソッド public abstract class Salary { public abstract int getBase(); … public abstract int getWage(int hour); サンプルコード:2_abstract/なし
  • 32.
    © 2020 SGCorporation 32 問題2解答編  finalメソッド  😄オーバーライド不可なポイントを守ることにより、メソッド間 の契約を保証できる。 抽象クラスと抽象メソッド public abstract class Salary { public final int getBase() { return getBaseImpl(); } protected abstract int getBaseImpl(); … public final int total(int hour) { return getBase() + getWage(hour); } サンプルコード:2_abstract/2_3
  • 33.
    © 2020 SGCorporation 33 問題2 いったんまとめ  abstractメソッドは最小限にする。publicメソッドはabstractメ ソッド同士の関係性を定義する。  拡張性を公開しないのであれば、公開するべきではない。  公開ポイントと拡張ポイントは区別する。 抽象クラスと抽象メソッド
  • 34.
    © 2020 SGCorporation 34 問題2解答編  公開クラスと拡張クラスを分ける。  😄拡張ポイントを分割・明示できる。 抽象クラスと抽象メソッド public final class Salary { private final SalaryImpl salaryImpl; Salary(int base, int wage, SalaryImpl salaryImpl) { … this.salaryImpl = salaryImpl; static interface SalaryImpl { サンプルコード:2_abstract/2_4
  • 35.
    © 2020 SGCorporation 35 問題2解答編  拡張クラスを公開する。  😄拡張ポイントのみを別個に公開できる。 抽象クラスと抽象メソッド public final class Salary { private final SalaryImpl salaryImpl; Salary(int base, int wage, SalaryImpl salaryImpl) { … this.salaryImpl = salaryImpl; public interface SalaryImpl { サンプルコード:2_abstract/2_5
  • 36.
    © 2020 SGCorporation 36 「疎結合としてのサービス」 で、もうちょっとやります。 抽象クラスと抽象メソッド
  • 37.
    © 2020 SGCorporation 37 まとめ  抽象クラスは継承クラスを容易に作成するためのヘルパー  公開メソッドは抽象メソッド同士の関係性を定義する  公開メソッドはオーバーライド不可とする 抽象クラスと抽象メソッド
  • 38.
    © 2020 SGCorporation 38 ◍ スコープ再考 ◍ 抽象クラスと抽象メソッド ◍ 制御構造再考 ◍ オブジェクトのライフサイクル ◍ 疎結合としてのサービス 目次
  • 39.
    © 2020 SGCorporation 39 Javaの制御構造はもちろんわかってるよ。 ifとかforとか… 制御構造再考
  • 40.
    © 2020 SGCorporation 40 問題3  単位処理数ごとのバッチ処理、コントロールブレイクをどう実現 するか。  例)  1000件ごと(バッチ)  何かグループ化の条件が変わった場合(コントロールブレイク) 制御構造再考
  • 41.
    © 2020 SGCorporation 41 問題3解答編  そのままループ  😄シンプルな解決策  😓外側のループと内側のループでiを共有しているので分かりにく い。 制御構造再考 for (int i = 0; i < employees.size(); i += 1000) { for (int id : employees.subList(i, Math.min(i + 1000, employees.size()))) { for (int i = 0; i < employees.size();) { for (int from = i; !isBreakIndex(employees, from, i); i++) { サンプルコード:3_control/3_1
  • 42.
    © 2020 SGCorporation 42 問題3解答編  そのままループ その2(バッチ)  😄外側と内側のループを分けた。 制御構造再考 for (int i = 0, n = 0; i < employees.size(); i += n) { n = batchLoopSub(employees, i); … private int batchLoopSub(List<Employee> employees, int fromIndex) { int count = Math.min(fromIndex + 1000, employees.size()); for (int id : employees.subList(fromIndex, count)) { … return count - fromIndex; サンプルコード:3_control/3_2
  • 43.
    © 2020 SGCorporation 43 問題3解答編  そのままループ その2 (コントロールブレイク)  😄外側と内側のループを分けた。 制御構造再考 for (int i = 0, n = 0; i < employees.size(); i += n) { n = controlBreakLoopSub(employees, i); … private int controlBreakLoopSub(List<Employee> employees, int fromIndex) { int i = fromIndex; for (; !isBreakIndex(employees, fromIndex, i); i++) { … return i - fromIndex; サンプルコード:3_control/3_2
  • 44.
    © 2020 SGCorporation 44 問題3解答編 なんか似てないか、これ 制御構造再考
  • 45.
    © 2020 SGCorporation 45 問題3解答編  そのままループ その2  😕ここ以外同じだな… 制御構造再考 int count = Math.min(fromIndex + 1000, employees.size()); for (int id : employees.subList(fromIndex, count)) { int i = fromIndex; for (; !isBreakIndex(employees, fromIndex, i); i++) { サンプルコード:3_control/3_2
  • 46.
    © 2020 SGCorporation 46 問題3解答編  そのままループ その3  バッチのループ継続条件をメソッド化  😳同じ構造になった! 制御構造再考 int i = fromIndex; for (; !isBatchIndex(employees, fromIndex, i); i++) { private boolean isBatchIndex(List<Employee> employees, int from, int index) { return index == Math.min(from + 1000, employees.size()); } サンプルコード:3_control/3_3
  • 47.
    © 2020 SGCorporation 47 問題3解答編 同じ構造のものはまとめよう 制御構造再考
  • 48.
    © 2020 SGCorporation 48 問題3解答編  ループ用クラス 制御構造再考 public final class Loop<E> { @FunctionalInterface public static interface ListBreak<E> { boolean test(List<E> list, int from, int index); } private final ListBreak<E> listBreak; public Loop(ListBreak<E> listBreak) { this.listBreak = listBreak; } サンプルコード:3_control/3_3
  • 49.
    © 2020 SGCorporation 49 問題3解答編  ループ用クラス 続き 制御構造再考 … public void loop(List<E> list) { for (int i = 0, n = 0; i < list.size(); i += n) { n = loopSub(list, i); … private int loopSub(List<E> list, int fromIndex) { int i = fromIndex; for (; !listBreak.test(list, fromIndex, i); i++) { … return i - fromIndex; … サンプルコード:3_control/3_3
  • 50.
    © 2020 SGCorporation 50 問題3解答編  ループ用クラス 呼び出し側  😄バッチとコントロールブレイクでループを共通化できた。  😓ループの中身である処理本体は同じことしかできない。 制御構造再考 Loop<Employee> batch = new Loop<>(Main::batchBreakIndex); batch.loop(employees); Loop<Employee> control = new Loop<>(Main::controlBreakIndex); control.loop(employees); サンプルコード:3_control/3_3
  • 51.
    © 2020 SGCorporation 51 問題3解答編  ビューのリストに変形する 制御構造再考 public final class Loop<E> { … public List<List<E>> listView(List<E> list) { List<List<E>> view = new ArrayList<>(); for (int i = 0, n = 0; i < list.size(); i += n) { n = loopSub(list, i); view.add(list.subList(i, i + n)); } return view; } サンプルコード:3_control/3_4
  • 52.
    © 2020 SGCorporation 52 問題3解答編  ビューのリストに変形する 呼び出し側  😄ループの処理本体を外に出せた。  😄呼び出し側は一般的な拡張for文のみで対応できる。  😓一時的ではあるが、リストの生成が必要になる。 制御構造再考 Loop<Employee> batch = new Loop<>(Main::batchBreakIndex); for (List<Employee> list : batch.listView(employees)) { for (Employee e : list) { サンプルコード:3_control/3_3
  • 53.
    © 2020 SGCorporation 53 問題3解答編  ビューを返すイテレータを作る 制御構造再考 public final class Loop<E> implements Iterable<List<E>> { … private final List<E> list; public Loop(List<E> list, ListBreak<E> listBreak) { … } @Override public Iterator<List<E>> iterator() { return new Iterator<List<E>>() { … } } サンプルコード:3_control/3_4
  • 54.
    © 2020 SGCorporation 54 問題3解答編  ビューを返すイテレータを作る 呼び出し側  😄ブレイク条件のみを定義すれば、後はイテレータがやってくれ る。  😓初見ではわかりづらい。 制御構造再考 Loop<Employee> batch = new Loop<>(employees, Main::batchBreakIndex); for (List<Employee> list : batch) { for (Employee e : list) { サンプルコード:3_control/3_4
  • 55.
    © 2020 SGCorporation 55 問題3解答編 せっかくなので、ブレイク条件も一般化しよう 制御構造再考
  • 56.
    © 2020 SGCorporation 56 問題3解答編 one more step  バッチブレイク条件 制御構造再考 static <E> ListBreak<E> batchBreakIndex(int size) { return (list, from, index) -> index == Math.min(from + size, list.size()); } Loop<Employee> batch = new Loop<>(employees, batchBreakIndex(1000)); サンプルコード:3_control/3_5
  • 57.
    © 2020 SGCorporation 57 問題3解答編 one more step  コントロールブレイク条件 制御構造再考 static <E> ListBreak<E> controlBreakIndex( BiFunction<E, E, Boolean> test) { return (list, from, index) -> { if (index == list.size()) return true; if (index == from) return false; return !test.apply(list.get(index - 1), list.get(index)); }; } Loop<Employee> control = new Loop<>(employees, controlBreakIndex((a, b) -> Objects.equals(a, b))); サンプルコード:3_control/3_5
  • 58.
    © 2020 SGCorporation 58  問題がデータ構造であるのなら、データ構造をプログラミングす る。  ビューとコピーを区別する。コピーが必要であればコピーする。  問題が制御構造であるのなら、制御構造をプログラミングする。  コピーが必要なければ、ビューで対応する。  使う側は動作をプログラミングしなくてもいい。  宣言型プログラミング 制御構造再考
  • 59.
    © 2020 SGCorporation 59  とは言え、そのプロジェクトで頻出のイディオムであるという場 合に使うべき。  素直にループを書いたほうがシンプルな解決策であることには変 わりはない。  簡明さは失われてしまった。 制御構造再考
  • 60.
    © 2020 SGCorporation 60  補足:ラムダ式・メソッド参照  Java8以降、メソッド参照とラムダ式が使えるようになった。  オーバーライド用のメソッドを1つだけ持つインターフェースを 実装する手間を減らす。  これにより、ラッパークラスを作らなくても、シグネチャが合致 するメソッドをそのまま渡すことができる。 制御構造再考 @FunctionalInterface public interface Runnable { void run(); }
  • 61.
    © 2020 SGCorporation 61  補足:ラムダ式・メソッド参照 制御構造再考 public static void main(String ... args) { ExecutorService service = Executors.newSingleThreadExecutor(); // 無名クラス service.execute(new Runnable() { public void run() { print(); } }); // メソッド参照 service.execute(Main::print); // ラムダ式 service.execute(() -> print()); service.shutdown(); } private static void print() { System.out.println("run."); }
  • 62.
    © 2020 SGCorporation 62 まとめ  問題がデータ構造であるのなら、データ構造をプログラミングす る  問題が制御構造であるのなら、制御構造をプログラミングする  再利用性と簡明さはトレードオフ(の場合がある) 制御構造再考
  • 63.
    © 2020 SGCorporation 63 ◍ スコープ再考 ◍ 抽象クラスと抽象メソッド ◍ 制御構造再考 ◍ オブジェクトのライフサイクル ◍ 疎結合としてのサービス 目次
  • 64.
    © 2020 SGCorporation 64 オブジェクトにはcloseとかfreeとかないよね… オブジェクトのライフサイクル
  • 65.
    © 2020 SGCorporation 65  Javaは明示的にメモリ領域を管理しない。  オブジェクトのライフサイクルを気にしなくていいということで はない。 オブジェクトのライフサイクル
  • 66.
    © 2020 SGCorporation 66 問題4  登録されたイベントリスナーをイベント発生時に呼び出す。  リスナーが途中でいなくなっても、登録された側で保持している と、GCで回収されない。 オブジェクトのライフサイクル
  • 67.
    © 2020 SGCorporation 67 問題4 解答例の概要 従業員管理  管理簿に従業員の追加を依頼する。  追加依頼は非同期に終了する。  追加完了時にリスナーにコールバックがかかる。 オブジェクトのライフサイクル サンプルコード:4_lifecycle
  • 68.
    © 2020 SGCorporation 68 問題4 解答例の概要  クラス  Book 管理簿 オブジェクトのライフサイクル サンプルコード:4_lifecycle
  • 69.
    © 2020 SGCorporation 69 問題4解答編  リスナーの登録のみをする オブジェクトのライフサイクル public final class Book { @FunctionalInterface public static interface Listener { void action(Employee employee); } private final Map<Listener, Object> listeners = new ConcurrentHashMap<>(); public void addListener(Listener listener) { listeners.put(listener, new Object()); } サンプルコード:4_lifecycle/4_1
  • 70.
    © 2020 SGCorporation 70 問題4解答編  リスナーの登録のみをする 続き オブジェクトのライフサイクル public void addAsync(Employee employee) { addService.execute(new Runnable() { @Override public void run() { add(employee); actionAddEvent(employee); } }); } サンプルコード:4_lifecycle/4_1
  • 71.
    © 2020 SGCorporation 71 問題4解答編  リスナーの登録のみをする 続き  😓リスナーがいなくなっても、オブジェクトがGCで回収されない。 オブジェクトのライフサイクル private void actionAddEvent(Employee employee) { for (Listener l : listeners.keySet()) { l.action(employee); } } サンプルコード:4_lifecycle/4_1
  • 72.
    © 2020 SGCorporation 72 問題4解答編  削除APIも公開する  😄削除メソッドが呼ばれれば、GCで回収される。  😓公開APIだと、削除メソッドが呼ばれる保証がない。 オブジェクトのライフサイクル public void removeListener(Listener listener) { listeners.remove(listener); } サンプルコード:4_lifecycle/4_2
  • 73.
    © 2020 SGCorporation 73 問題4解答編  弱い参照  マップ内のリスナーを弱い参照(WeakReference)で保持する。 オブジェクトのライフサイクル public final class Book { private final Map<WeakReference<Listener>, Object> listeners = new ConcurrentHashMap<>(); public void addListener(Listener listener) { listeners.put(new WeakReference<>(listener), new Object()); } サンプルコード:4_lifecycle/4_3
  • 74.
    © 2020 SGCorporation 74 問題4解答編  弱い参照 続き オブジェクトのライフサイクル private void actionAddEvent(Employee employee) { for (Iterator<WeakReference<Listener>> itr = listeners.keySet().iterator(); itr.hasNext(); ) { Listener l = itr.next().get(); if (l == null) { itr.remove(); continue; } l.action(employee); } } サンプルコード:4_lifecycle/4_3
  • 75.
    © 2020 SGCorporation 75 問題4解答編  弱い参照  😄通常の参照が0件になれば、GCで回収される。  😄オブジェクトのライフサイクルを気にしていないことを明示で きる。  😓API仕様で、弱い参照をしていることを明示しておく必要があ る。 オブジェクトのライフサイクル
  • 76.
    © 2020 SGCorporation 76  ブロードキャストする側はオブジェクトのライフサイクルを気に しない。  登録と登録解除を提供してもいいけど、ユーザーが登録解除を呼 ぶ保証はない!  オブジェクトのライフサイクルを気にしていないということを明 示する。 オブジェクトのライフサイクル
  • 77.
    © 2020 SGCorporation 77  オブジェクト生成 one more step  派生型が絡むと多少厄介… オブジェクトのライフサイクル
  • 78.
    © 2020 SGCorporation 78  リストをコピーするメソッドのデザインとして、2通りが考えら れる。 アウトパラメーターとする 戻り値とする オブジェクトのライフサイクル <E> void copyList(List<E> dest, List<E> src) <E> List<E> copyList(List<E> src)
  • 79.
    © 2020 SGCorporation 79  アウトパラメーター  呼び出し元がコピー先リストを生成する  リストの具象クラスを呼び出し元が決めたい場合  呼び出し先は実行時クラスが何かを気にしていない  メソッドがアルゴリズムを表している場合はこのパターン オブジェクトのライフサイクル <E> void copyList(List<E> dest, List<E> src)
  • 80.
    © 2020 SGCorporation 80  戻り値  呼び出し先がコピー先リストを生成する  リストの具象クラスを呼び出し先が決めたい場合  呼び出し元は実行時クラスが何かを気にしていない  メソッドがファクトリーを表している場合はこのパターン オブジェクトのライフサイクル <E> List<E> copyList(List<E> src)
  • 81.
    © 2020 SGCorporation 81 まとめ  オブジェクトのライフサイクルを気にしなくていいということで はない  オブジェクトの生成消滅に対する責務をどこが担っているかを表 現する  メソッドがアルゴリムなのか、ファクトリーなのかでも責務が変 わってくる オブジェクトのライフサイクル
  • 82.
    © 2020 SGCorporation 82 ◍ スコープ再考 ◍ 抽象クラスと抽象メソッド ◍ 制御構造再考 ◍ オブジェクトのライフサイクル ◍ 疎結合としてのサービス 目次
  • 83.
    © 2020 SGCorporation 83 突然ですが、MySQLに接続しよう… 疎結合としてのサービス
  • 84.
    © 2020 SGCorporation 84  JDBCドライバ(というかコネクション)を取得する例  😳“jdbc:mysql://〜”を渡すとMySQLのドライバが取得できる! 疎結合としてのサービス Connection conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/testdb", "user", "pass");
  • 85.
    © 2020 SGCorporation 85 JDBCドライバのような機構はどのように実現されているのか? 疎結合としてのサービス
  • 86.
    © 2020 SGCorporation 86 問題5  状態を保存したいが、保存先をプロパティファイル、XML、DB等 で切り替えたい。 疎結合としてのサービス
  • 87.
    © 2020 SGCorporation 87 問題5 解答例の概要 従業員管理  従業員管理簿を外部に保存する。  外部保存の実装は継承で拡張できる。 疎結合としてのサービス サンプルコード:5_service
  • 88.
    © 2020 SGCorporation 88 問題5 解答例の概要  クラス  Book 管理簿  BookHelper 管理簿と保存機能の橋渡し  Persistance 保存機能 疎結合としてのサービス サンプルコード:5_service
  • 89.
    © 2020 SGCorporation 89 問題5解答編  ファクトリメソッドで文字列から作成 疎結合としてのサービス public abstract class Persistance { public static Persistance create(String persist) throws IOException { switch (persist) { case "xml": return new XMLPersistance(); case "db": return new DBPersistance(); default: throw new IOException("No suitable provider found for ” + persist); … サンプルコード:5_service/5_1
  • 90.
    © 2020 SGCorporation 90 問題5解答編  ファクトリメソッドで文字列から作成  😄難しい仕組みがいらない。  😓コンパイル時に実装クラスが必要。 疎結合としてのサービス
  • 91.
    © 2020 SGCorporation 91 問題5解答編  リフレクションで実現  😄仕組みがまあまあ簡潔。  😓インスタンス生成するクラスが公開サービスなのかが不明確。  😓パッケージ名とサービス名が直接結びついてしまう。 疎結合としてのサービス public abstract class Persistance { public static Persistance create(String persist) throws … { return Class.forName(persist).asSubclass(Persistance.class). getDeclaredConstructor().newInstance(); } サンプルコード:5_service/5_2
  • 92.
    © 2020 SGCorporation 92 問題5解答編  サービスで実現 疎結合としてのサービス public interface PersistanceService { boolean acceptName(String name); void read(BookHelper book) throws IOException; void write(BookHelper book) throws IOException; } サンプルコード:5_service/5_3
  • 93.
    © 2020 SGCorporation 93 問題5解答編  サービスで実現 続き  😄仕組みがまあまあ簡潔。  😓インスタンス生成するクラスが公開サービスなのかが不明確。  😓パッケージ名とサービス名が直接結びついてしまう。 疎結合としてのサービス public final class Book { private final PersistanceService service; public static Book createPersistance(String persist) throws IOException { PersistanceService found = findPersistanceService(persist); if (found == null) { throw new IOException("No suitable provider found for " + persist); } return new Book(found); } サンプルコード:5_service/5_3
  • 94.
    © 2020 SGCorporation 94 問題5解答編  サービスで実現 続き 疎結合としてのサービス private static PersistanceService findPersistanceService( String persist) { for (PersistanceService service : ServiceLoader.load(PersistanceService.class)) { if (service.acceptName(persist)) return service; } return null; } サンプルコード:5_service/5_3
  • 95.
    © 2020 SGCorporation 95 問題5解答編  サービスで実現 続き  😄サービスとして公開していることを明示できる。  😄パッケージ名とサービス名を間接的な結びつきにできる。  😓若干、仕組みが面倒。 疎結合としてのサービス 公開するサービスプロバイダー名を記載した META- INF/services/jp.co.sgnet.hrp.skillup202001.hr.spi.PersistanceService を配置 jp.co.sgnet.hrp.skillup202001.hr.spi.XMLPersistanceServiceProvider サンプルコード:5_service/5_3
  • 96.
    © 2020 SGCorporation 96  APIとSPI  API : Application Programming Interface  SPI : Service Provider Interface  APIとSPIのクラス階層は分けておく。  API側の実装クラスが構築時にSPIのオブジェクトをもらって、中 にオブジェクトとして持つ形式。  APIそのものが継承されることを前提としていることは少ないは ず。 疎結合としてのサービス
  • 97.
    © 2020 SGCorporation 97  補足: Java9のモジュール機構  Java9以降、アクセス修飾子とは別にjarの外側に公開する/しな いを制御することができるようになった。 疎結合としてのサービス
  • 98.
    © 2020 SGCorporation 98  補足: Java9のモジュール機構  module-info.javaにクラスの公開/非公開を記載する。 疎結合としてのサービス module local.api { exports local.api; uses local.api.MyService; } 公開側: local.apiパッケージ module local.app { requires local.api; uses local.api.MyService; } 利用側: local.appパッケージ module local.provider { requires local.api; provides local.api.MyService with local.provider.MyServiceProvider1; } 実装側: local.providerパッケージ
  • 99.
    © 2020 SGCorporation 99  補足: Java9のモジュール機構 疎結合としてのサービス private static PersistanceService findPersistanceService( String persist) { for (PersistanceService service : ServiceLoader.load(PersistanceService.class)) { if (service.acceptName(persist)) return service; } return null; }
  • 100.
    © 2020 SGCorporation 100 まとめ  APIとSPIは分けておく  APIは継承を前提としない  SPIは継承を前提とする 疎結合としてのサービス
  • 101.
    © 2020 SGCorporation 101 ◍ Effective Java 第3版 Joshua Bloch (著), 柴田 芳樹 (翻訳) 丸善出版 ◍ APIデザインの極意 Java/NetBeansアーキテクト探究ノート Jaroslav Tulach (著), 柴田 芳樹 (翻訳) インプレス 参考文献
  • 102.
    © 2020 SGCorporation 102 ◍ SGソフトウェア開発ブログ https://blog.sgnet.co.jp ◍ SlideShare公開資料 https://www.slideshare.net/t_ichioka_sg/ こちらもどうぞ
  • 103.
    © 2020 SGCorporation 103 顧客と社員に信頼されるエス・ジー