Java言語で学ぶデザインパターン入門第3版 読書メモ
第1章 Iterator 処理を繰り返す
Iterator パターンってなに
プログラミングにおいてデータの集まり(例えば、リストや配列など)を扱う際に、便利な方法を提供するデザインパターンです。
Iterator パターンが登場する前は、データの集まりを扱う際に、データの種類ごとに異なる方法を使う必要がありました。
Iterator パターンを使わない場合、配列の要素にアクセスするには、インデックスを使った for ループを使います。
つまり、Iterator パターンを使用すると、開発者は特定のデータ構造の詳細について心配することなく、データにアクセスすることができます。
// Iterator パターンを使わない場合:
// 配列の場合
const fruits = [ "apple", "banana", "orange" ];
for (let i = 0; i < fruits.length; i++) {
console.log(fruits[i]);
}
一方、Iterator パターンを使う場合は、Symbol.iterator
を使って配列のイテレータを取得します。そして、whileループを使って、next() メソッドを呼び出すことで、配列の要素を順番に取得します。
// セットの場合
const uniqueNumbers = new Set([ 1, 2, 3, 4, 5 ]);
const setIterator = uniqueNumbers[Symbol.iterator]();
let setItem = setIterator.next();
while (!setItem.done) {
console.log(setItem.value);
setItem = setIterator.next();
}
このように、Iterator パターンを使うことで、データの種類や内部構造に関わらず、統一的な方法でデータにアクセスできるようになります。
構成要素
Iterator(イテレータ)
要素を順番にスキャンしていくためのインターフェース
Javaでは、java.util.Iterator インターフェースが提供されています
Concrete Iterator(具体的なイテレータ)
Iterator<Book> インターフェースを実装するクラス (BookShelfIterator)
集約体の要素を順番にスキャンするためのメソッドを実装します
hasNext(): 次の要素が存在するかどうかを判定します
next(): 次の要素を返します
package Iterator;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class BookShelfIterator implements Iterator<Book> {
private BookShelf bookShelf;
private int index;
public BookShelfIterator(BookShelf bookShelf) {
this.bookShelf = bookShelf;
this.index = 0;
}
@Override
public boolean hasNext() {
return index < bookShelf.getLength();
}
@Override
public Book next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Book book = bookShelf.getBookAt(index);
index++;
return book;
}
}
Aggregate(集約体)
要素の集まりを表すインターフェース
Javaでは、java.lang.Iterable インターフェースが提供されています
package Iterator;
public class Book {
private String name;
public Book(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Concrete Aggregate(具体的な集約体)
Iterable<Book> インターフェースを実装するクラス (BookShelf)
Book オブジェクトの集まりを表します
iterator() メソッドを実装し、BookShelfIterator のインスタンスを返します
package Iterator;
import java.util.Iterator;
public class BookShelf implements Iterable<Book> {
private Book[] books;
private int last = 0;
public BookShelf(int maxsize) {
this.books = new Book[maxsize];
}
public Book getBookAt(int index) {
return books[index];
}
public void appendBook(Book book) {
this.books[last] = book;
last++;
}
public int getLength() {
return last;
}
@Override
public Iterator<Book> iterator() {
return new BookShelfIterator(this);
}
}
maxsize
を超える場合
本棚が 現状の実装では、本棚が maxsize
を超える場合、ArrayIndexOutOfBoundsException
が発生します。
この問題を解決するために、ArrayList
を使って本棚を実装します。
// Iterableインターフェースを実装することで、拡張for文を使えるようになる
public class BookShelf implements Iterable<Book> {
private final List<Book> books;
// 任意の要素数の本棚を作る
public BookShelf(int initialsize) {
this.books = new ArrayList<>(initialsize);
}
// 本棚の指定した位置にある本を取得
public Book getBookAt(int index) {
return books.get(index);
}
// 本棚に本を追加
public void appendBook(Book book) {
books.add(book);
}
// 本棚の長さを取得
public int getLength() {
return books.size();
}
// iterator()メソッドがオーバーライドされていることを明示的に示す
@Override
public Iterator<Book> iterator() {
return new BookShelfIterator(this);
}
}
第2章 Adapter 一皮かぶせて再利用
Adapterパターン
- 既存のクラスを再利用したい場合に、既存のクラスに一皮かぶせるような形で再利用することで、必要とするメソッド群を素早く実装することができる。
- 既存のクラスには、バグがないことが保証されているため、
Adapter
クラスを重点的に調べればよくなる - 既存のクラスを変更せずに、新しいインターフェースに適合させることができる
Adapterパターンの構成要素
- Target(ターゲット): クライアントが使用するインターフェースを定義します。
- Adaptee(アダプティ): 既存のクラスであり、変更せずに再利用したいクラスです。
- Adapter(アダプター): TargetインターフェースとAdapteeクラスの間に位置し、互換性のないインターフェースを適合させるクラスです。
継承を使ったAdapterパターン
Print
クラス)
Target インターフェース(package Adapater.Sample1;
public interface Print {
public abstract void printWeak();
public abstract void printStrong();
}
Banner
クラス)
Adaptee クラス(package Adapater;
public class Banner {
private String string;
public Banner(String string) {
this.string = string;
}
public void showWithParen() {
System.out.println("(" + string + ")");
}
public void showWithAster() {
System.out.println("*" + string + "*");
}
}
Adapter
package Adapater;
import Adapater.Sample1.Banner;
import Adapater.Sample1.Print;
public class PrintBanner extends Banner implements Print {
public PrintBanner(String string) {
super(string);
}
@Override
public void printWeak() {
showWithParen();
}
@Override
public void printStrong() {
showWithAster();
}
}
クライアントコード(Mainクラス)
package Adapater.Sample1;
public class main {
public static void main(String[] args) {
Print p = new PrintBanner("Hello");
p.printWeak();
p.printStrong();
}
}
-
new PrintBanner("Hello")
でBanner
クラスを継承してPrint
インターフェースを実装したPrintBanner
クラスを生成する -
PrintBanner
クラスのprintWeak
メソッドとprintStrong
メソッドは、それぞれshowWithParen
メソッドとshowWithAster
メソッドを呼び出すことができる
委譲を使ったAdapterパターン
委譲を使ったAdapterパターンは、Adapterパターンの一種であり、継承ではなく委譲(delegation)を使用してAdapterクラスを実装する方法です。
委譲
委譲とは、あるオブジェクトが持つ責任の一部を、他のオブジェクトに委ねること(任せること)を指します。委譲を使用すると、オブジェクト間の結合度を下げ、柔軟性と再利用性を高めることができます。
委譲を使ったAdapterパターンの構成要素
委譲を使ったAdapterパターンは、以下の主要な構成要素で構成されています。
- Target(ターゲット): クライアントが使用するインターフェースを定義します。
- Adaptee(アダプティ): 既存のクラスであり、変更せずに再利用したいクラスです。
- Adapter(アダプター):
TargetインターフェースとAdapteeクラスの間に位置し、互換性のないインターフェースを適合させるクラスです。ただし、Adapteeクラスを継承するのではなく、Adapteeクラスのインスタンスを内部に保持します。
Targetインターフェース(Printクラス)
Printクラスは、クライアントが使用するインターフェースを定義しています。このクラスは、printWeak()メソッドとprintStrong()
メソッドを持つ抽象クラスです。
public abstract class Print {
public abstract void printWeak();
public abstract void printStrong();
}
Adapteeクラス(Bannerクラス)
Bannerクラスは、既存のクラスであり、変更せずに再利用したいクラスです。このクラスは、文字列を括弧やアスタリスクで囲んで表示する機能を提供しています。
public class Banner {
private String string;
public Banner(String string) {
this.string = string;
}
public void showWithParen() {
System.out.println("(" + string + ")");
}
public void showWithAster() {
System.out.println("*" + string + "*");
}
}
Adapter(PrintBannerクラス)
public class PrintBanner extends Print {
private Banner banner;
public PrintBanner(String string) {
this.banner = new Banner(string);
}
@Override
public void printWeak() {
banner.showWithParen();
}
@Override
public void printStrong() {
banner.showWithAster();
}
}
クライアントコード(Mainクラス)
public class Main {
public static void main(String[] args) {
Print p = new PrintBanner("Hello");
p.printWeak();
p.printStrong();
}
}
委譲を使用することで、Adapteeクラスとの結合度を下げ、柔軟性と再利用性を高めることができます。