一般的に既存のクラスに機能を拡張するには、継承したクラスを作成します。
しかしこの方法だと継承元クラスが固定されるため、既存クラスが複数あった場合に
既存クラスごとに継承したクラスを作成しなければなりません。
interface Component {
public void operation();
}
class ConcreteComponent1 implements Component {
public void operation() {
System.out.println("ConcreteComponent1");
}
}
class ConcreteComponent2 implements Component {
public void operation() {
System.out.println("ConcreteComponent2");
}
}
class AdditionalConcreteComponent1 extends ConcreteComponent1 {
public void operation() {
super.operation();
System.out.println("AdditionalConcreteComponent");
}
}
class AdditionalConcreteComponent2 extends ConcreteComponent2 {
public void operation() {
super.operation();
System.out.println("AdditionalConcreteComponent");
}
}
Decorator パターンでは継承を使用せず、委譲を使って動的に委譲元クラスを差し替えることで機能を拡張します。
abstract class Decorator implements Component {
protected Component component;
}
class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component original) {
this.component = original;
}
public void operation() {
this.component.operation();
System.out.println("ConcreteDecorator");
}
}
void client() {
System.out.println("// 継承による機能拡張");
new AdditionalConcreteComponent1().operation();
new AdditionalConcreteComponent2().operation();
System.out.println("// 委譲による機能拡張");
new ConcreteDecorator(new ConcreteComponent1()).operation();
new ConcreteDecorator(new ConcreteComponent2()).operation();
}
実行結果:// 継承による機能拡張
ConcreteComponent1
AdditionalConcreteComponent
ConcreteComponent2
AdditionalConcreteComponent
// 委譲による機能拡張
ConcreteComponent1
ConcreteDecorator
ConcreteComponent2
ConcreteDecorator
サンプルプログラムは、CSV, XML から DOM を構築するパーサに、Decorator でソートする機能を追加しています。
出力結果:
// CSV -> URL でソート
root:
entry: name: bing URL: http://www.bing.com/
entry: name: Google URL: http://www.google.co.jp/
entry: name: Yahoo URL: http://www.yahoo.co.jp/
// XML -> name でソート
root:
entry: name: apple price: 100
entry: name: banana price: 200
entry: name: orange price: 50
package design_pattern;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Decorator
* GoF, Code Complete
* オブジェクトに動的に機能を追加します。継承より柔軟な機能拡張を提供します。
*
* 出力結果:
* <pre>
* // CSV -> URL でソート
* root:
* entry: name: bing URL: http://www.bing.com/
* entry: name: Google URL: http://www.google.co.jp/
* entry: name: Yahoo URL: http://www.yahoo.co.jp/
* // XML -> name でソート
* root:
* entry: name: apple price: 100
* entry: name: banana price: 200
* entry: name: orange price: 50
* </pre>
*
* @author 2015/05/26 matsushima
*/
public class DecoratorSample {
/**
* main
*
* @param args
*/
public static void main(String[] args) {
try {
new DecoratorSample().client();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Client
*/
void client() throws IOException {
String csvText =
"name,URL\n"
+ "Google,http://www.google.co.jp/\n"
+ "Yahoo,http://www.yahoo.co.jp/\n"
+ "bing,http://www.bing.com/\n"
;
String xmlText =
"<entry><name>apple</name><price>100</price></entry>"
+ "<entry><name>orange</name><price>50</price></entry>"
+ "<entry><name>banana</name><price>200</price></entry>"
;
System.out.println("// CSV -> URL でソート");
Parser_Component c1 = new NameSortedParser_ConcreteDecorator(
new CsvParser_ConcreteComponent(), "URL");
Node n1 = c1.parse(new StringReader(csvText));
printNode(System.out, n1, "");
System.out.println("// XML -> name でソート");
Parser_Component c2 = new NameSortedParser_ConcreteDecorator(
new XmlParser_ConcreteComponent(), "name");
Node n2 = c2.parse(new StringReader(xmlText));
printNode(System.out, n2, "");
}
/**
* ノードを出力。
*/
void printNode(PrintStream writer, Node node, String indent) {
if (node.getChildNodes().isEmpty()) {
writer.print(node.getText() + " ");
} else {
String r2 = (2 == indent.length() ? "\n" + indent : indent.trim());
String r0 = (0 == indent.length() ? "\n" : "");
writer.print(r2 + node.getText() + ": ");
for (Node child: node.getChildNodes()) {
printNode(writer, child, indent + " ");
}
writer.print(r0);
}
}
/** ノード。 */
public class Node {
private String text;
private LinkedList<Node> children = new LinkedList<>();
public Node(String text) { this.text = text; }
public String getText() { return this.text; }
public Node appendChild(Node node) { this.children.add(node); return node; }
public List<Node> getChildNodes() { return this.children; }
}
// Component ///////////////////////////////////////
/** パーサのインターフェース。 */
public interface Parser_Component {
public Node parse(Reader in) throws IOException;
}
/** CSV パーサ。 */
public class CsvParser_ConcreteComponent implements Parser_Component {
public Node parse(Reader in) throws IOException {
Node root = new Node("root");
String[] names = null;
BufferedReader reader = new BufferedReader(in);
for (String line; null != (line = reader.readLine()); ) {
String[] cols = line.split(",");
if (null == names) {
names = cols;
} else {
Node entry = root.appendChild(new Node("entry"));
for (int i = 0; i < Math.min(names.length, cols.length); ++ i) {
entry.appendChild(new Node(names[i]))
.appendChild(new Node(cols[i]));
}
}
}
return root;
}
}
/** XML パーサ。 */
public class XmlParser_ConcreteComponent implements Parser_Component {
private final Pattern p1 = Pattern.compile("\\s*<\\s*([^>/]*)\\s*>\\s*");
private final Pattern p2 = Pattern.compile("\\s*</\\s*([^>]*)\\s*>\\s*");
public Node parse(Reader in) throws IOException {
// 入力データ読み込み
StringBuilder sb = new StringBuilder();
char[] buf = new char[1024];
for (int read; -1 != (read = in.read(buf)); ) {
sb.append(buf, 0, read);
}
// ノードごとのパース処理
Node root = new Node("root");
this.parseImpl(root, sb, 0, p1.matcher(sb), p2.matcher(sb));
return root;
}
private int parseImpl(Node node, CharSequence text, int index, Matcher m1, Matcher m2) {
while (index < text.length()) {
int i1 = m1.find(index) ? m1.start() : text.length();
int i2 = m2.find(index) ? m2.start() : text.length();
if (i1 < i2) { // element
Node childElement = new Node(m1.group(1));
node.appendChild(childElement);
index = this.parseImpl(childElement, text, m1.end(), m1, m2);
} else { // text
String value = text.subSequence(index, i2).toString().trim();
if (!value.isEmpty()) {
node.appendChild(new Node(value));
}
index = i2 < text.length() ? m2.end() : text.length();
break;
}
}
return index;
}
}
// Decorator ///////////////////////////////////////
/** ソートするパーサのデコレータの抽象クラス。 */
public abstract class SortedParser_Decorator implements Parser_Component {
protected Parser_Component parser;
protected SortedParser_Decorator(Parser_Component original) {
this.parser = original;
}
}
/** 項目でソートするパーサのデコレータ。 */
public class NameSortedParser_ConcreteDecorator extends SortedParser_Decorator {
private String sortKey;
protected NameSortedParser_ConcreteDecorator(Parser_Component original, String sortKey) {
super(original);
this.sortKey = sortKey;
}
public Node parse(Reader in) throws IOException {
Node node = this.parser.parse(in);
// ソートする
Collections.sort(node.getChildNodes(), new Comparator<Node>() {
public int compare(Node o1, Node o2) {
return findText(o1, sortKey).compareTo(findText(o2, sortKey));
}
});
return node;
}
private String findText(Node node, String name) {
if (null != node.getChildNodes()) {
if (name.equals(node.getText())) {
return node.getChildNodes().get(0).getText();
}
for (Node child: node.getChildNodes()) {
String text = findText(child, name);
if (null != text) {
return text;
}
}
}
return null;
}
}
}
0 件のコメント:
コメントを投稿