Java言語で学ぶデザインパターン入門
プログラミングの書き方にはパターンがある。本書では、そのパターンをデザインパターンと呼び、よく使われる23個のデザインパターンに名前を与え、カタログとして整理している。デザインパターンはプログラムを新しい観点から見直し、再利用しやすく、機能拡張しやすいソフトウェアを作るための有益な技法である。
プログラムを部品として再利用するために、
- どのような機能が拡張される可能性があるか?
- その機能拡張を行うときに修正が必要になるのはどのクラスか?
- 修正が不要なのはどのクラスか
といった観点でデザインパターンを考える。
UML について
UML(Unified Modeling Language)とは仕様や設計を文書化したりするための表現方法である。
クラス図
クラスやインスタンス、インターフェースなどの静的な関係を表現したもの。
つまりは、時間によって変化しない。
参照:https://www.itsenka.com/contents/development/uml/class.html
クラスの名前→フィールドの名前→メソッドの名前の順番に書かれる。
abstractクラス、abstractメソッドは斜字体、staticフィールドは下線が書かれる。
アクセス制御を表現する場合は、メソッドやフィールドの名前の前に記号をつける。
記号 | アクセス制御 |
---|---|
+ | public |
- | private |
# | protected |
~ | 同じパッケージ内からのみアクセス可能 |
シーケンス図
プログラムが動くときにどのメソッドがどういう順番で実行されるか、どのような事象がどういう順番で起きるかを表現したもの。
つまりは、時間に従って変化する。
上の図では、上にある2つの長方形がインスタンスである。
インスタンスの下方向に伸びている線をライフラインと呼ぶ。ライフラインはインスタンスが存在する間だけ存在する。
Serverの下の細長い長方形はそのオブジェクトが活動中であることを示す。
デザインパターンについて
本書では23個のデザインパターンが整理されているが、本記事では私が今行っている作業で使えそうなデザインパターンを3つ紹介しようと思う。
Template Method(具体的な処理をサブクラスにまかせる)
スーパークラスにテンプレートとなるメソッドが定義されており、そのメソッドの定義の中では抽象メソッドが使われている。
そのため、スーパークラスのプログラムを読んだだけでは最終的にどのような処理になるか分からない。
抽象メソッドを具体的に実装するのはサブクラスである。
このように、スーパークラスで処理の枠組みを定め、サブクラスでその具体的内容を定めるようなデザインパターンをTemplate Methodパターンと呼ぶ。
例:文字や文字列を5回繰り返して表示する
Template Methodパターンを表現するクラスは以下の3つである。
①AbstractDisplayインターフェース(抽象クラスの役)
テンプレートメソッドを実装。
public abstract class AbstractDisplay { // 抽象クラスAbstractDisplay
public abstract void open(); // サブクラスに実装をまかせる抽象メソッド(1) open
public abstract void print(); // サブクラスに実装をまかせる抽象メソッド(2) print
public abstract void close(); // サブクラスに実装をまかせる抽象メソッド(3) close
public final void display() { // この抽象クラスで実装しているメソッドdisplay(テンプレートメソッド)
open(); // まずopenして…
for (int i = 0; i < 5; i++) { // 5回printを繰り返して…
print();
}
close(); // …最後にcloseする。これがdisplayメソッドで実装されている内容。
}
}
②CharDisplayインターフェース(具象クラスの役)
抽象メソッドを具体的に実装。
public class CharDisplay extends AbstractDisplay { // CharDisplayは、AbstractDisplayのサブクラス。
private char ch; // 表示すべき文字。
public CharDisplay(char ch) { // コンストラクタで渡された文字chを、
this.ch = ch; // フィールドに記憶しておく。
}
public void open() { // スーパークラスでは抽象メソッドだった。ここでオーバーライドして実装。
System.out.print("<<"); // 開始文字列として"<<"を表示する。
}
public void print() { // printメソッドもここで実装する。これがdisplayから繰り返して呼び出される。
System.out.print(ch); // フィールドに記憶しておいた文字を1個表示する。
}
public void close() { // closeメソッドもここで実装。
System.out.println(">>"); // 終了文字列">>"を表示。
}
}
③StringDisplay クラス(具象クラスの役)
抽象メソッドを具体的に実装。
public class StringDisplay extends AbstractDisplay { // StringDisplayも、AbstractDisplayのサブクラス。
private String string; // 表示するべき文字列。
private int width; // バイト単位で計算した文字列の「幅」。
public StringDisplay(String string) { // コンストラクタで渡された文字列stringを、
this.string = string; // フィールドに記憶。
this.width = string.getBytes().length; // それからバイト単位の幅もフィールドに記憶しておいて、後で使う。
}
public void open() { // オーバーライドして定義するopenメソッド。
printLine(); // このクラスのメソッドprintLineで線を引いている。
}
public void print() { // printメソッドは、
System.out.println("|" + string + "|"); // フィールドに記憶しておいた文字列の前後に"|"をつけて表示。
}
public void close() { // closeメソッドは、
printLine(); // openと同じくprintLineメソッドで線を引いている。
}
private void printLine() { // openとcloseから呼ばれるprintLineメソッドだ。privateなので、このクラスの中だけで使われる。
System.out.print("+"); // 枠の角を表現する"+"マークを表示。
for (int i = 0; i < width; i++) { // width個の"-"を表示して、
System.out.print("-"); // 枠線として用いる。
}
System.out.println("+"); // 枠の角を表現する"+"マークを表示。
}
}
Iterator(一つ一つ数え上げる)
Iterator(反復子)とは、何かがたくさん集まっているときに、それを順番に指示していき、全体をスキャンしていく処理である。
Javaではfor文は以下のように書く
for (int i = 0; i < arr.length; i++){
System.out.println(arr[i]);
}
このようにiを増やしていくと、配列arrの要素全体を最初からスキャンできる。
ここで使われている変数iの動きを抽象化し、一般化したものをIteratorパターンと呼ぶ。
例:本棚の中に本を入れ、その本の名前を順番に表示
Iteratorパターンを表現するクラスは以下の4つである。
①Aggregateインターフェース(集合体の役)
Iterator役を作り出すインターフェースを定める役である。数え上げを行うものの集合体を表す。
public interface Aggregate {
public abstract Iterator iterator();
}
②Iteratorインターフェース(反復子の役)
Iteratorは要素を順番にスキャンしていくインターフェースを定める役。要素の数え上げを行うもの、ループ変数のような役割を果たすもの。
public interface Iterator {
public abstract boolean hasNext(); // 次の要素があるか調べる
public abstract Object next(); // 次の要素を得る
}
③BookShelfクラス(具体的な集合体の役)
Aggregate役が定めたインターフェースを実際に実装する役。
public class BookShelf implements Aggregate {
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;
}
public Iterator iterator() {
return new BookShelfIterator(this);
}
}
④BookShelfIteratorクラス(具体的な反復子の役)
Iterator役(BookShelf)が定めたインターフェースを実際に実装する役。
public class BookShelfIterator implements Iterator {
private BookShelf bookShelf;
private int index;
public BookShelfIterator(BookShelf bookShelf) {
this.bookShelf = bookShelf;
this.index = 0;
}
public boolean hasNext() {
if (index < bookShelf.getLength()) {
return true;
} else {
return false;
}
}
public Object next() {
Book book = bookShelf.getBookAt(index);
index++;
return book;
}
}
Facade(シンプルな窓口)
大きなプログラムを使って処理を行うためには、関係し合っているたくさんのクラスを適切に制御しなければならない。
そのため、その処理を行うための窓口を用意しておけば、たくさんのクラスを制御しなくてもその窓口に対して要求を出すだけで良い。
例:ユーザのWebページを作成
Facadeパターンを表現するクラスは以下の4つである。
①Databaseクラス
システムを構成しているその他大勢の役。データベース名を指定してそれに対応したPropertiesを作成する。
Facade役を呼び出すことはないため、Facade役を意識しなくて良い。
package pagemaker;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
public class Database {
private Database() { // インスタンスは作らない
}
public static Properties getProperties(String dbname) { // データベース名からPropertiesを得る
String filename = dbname + ".txt";
Properties prop = new Properties();
try {
prop.load(new FileInputStream(filename));
} catch (IOException e) {
System.out.println("Warning: " + filename + " is not found.");
}
return prop;
}
}
②HtmlWriterインターフェース
インスタンス生成時にwriterを与え、そのwriterに対してHTMLを出力。
package pagemaker;
import java.io.Writer;
import java.io.IOException;
public class HtmlWriter {
private Writer writer;
public HtmlWriter(Writer writer) { // コンストラクタ
this.writer = writer;
}
public void title(String title) throws IOException { // タイトルの出力
writer.write("<html>");
writer.write("<head>");
writer.write("<title>" + title + "</title>");
writer.write("</head>");
writer.write("<body>\n");
writer.write("<h1>" + title + "</h1>\n");
}
public void paragraph(String msg) throws IOException { // 段落の出力
writer.write("<p>" + msg + "</p>\n");
}
public void link(String href, String caption) throws IOException { // リンクの出力
paragraph("<a href=\"" + href + "\">" + caption + "</a>");
}
public void mailto(String mailaddr, String username) throws IOException { // メールアドレスの出力
link("mailto:" + mailaddr, username);
}
public void close() throws IOException { // 閉じる
writer.write("</body>");
writer.write("</html>\n");
writer.close();
}
}
③PageMakerクラス(Facadeの役)
システムを構成しているその他大勢の役のシンプルな窓口となる。
このクラスで定義されているpublicなメソッドmakeWelcomePageにメールアドレスと出力ファイル名を指定するだけでWebページが生成される。
package pagemaker;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class PageMaker {
private PageMaker() { // インスタンスは作らない
}
public static void makeWelcomePage(String mailaddr, String filename) {
try {
Properties mailprop = Database.getProperties("maildata");
String username = mailprop.getProperty(mailaddr);
HtmlWriter writer = new HtmlWriter(new FileWriter(filename));
writer.title("Welcome to " + username + "のページへようこそ");
writer.paragraph(username + "'s page!");
writer.paragraph("メールまっていますね。");
writer.mailto(mailaddr, username);
writer.close();
System.out.println(filename + " is created for " + mailaddr + " (" + username + ")");
} catch (IOException e) {
e.printStackTrace();
}
}
}
②Mainクラス(依頼人の役)
Facadeパターンを利用する役。
import pagemaker.PageMaker;
public class Main {
public static void main(String[] args) {
PageMaker.makeWelcomePage("hyuki@hyuki.com", "welcome.html");
}
}
まとめ
インターフェースでプログラムのアウトラインを作成するのはとても大切である。
いきなり具体的なクラスだけを使ってしまうと、クラス間の結合が強くなってしまい、部品として再利用することが難しくなってしまう。
結合を弱め、クラスを部品として再利用しやすくするために、抽象クラスやインターフェースを使用する。