Help us understand the problem. What is going on with this article?

DecoratorパターンをJavaScriptとJavaのコードを比較して理解する

More than 1 year has passed since last update.

はじめに

詳しいことや他のパターンはデザインパターンをJavaScriptとJavaでの実装を比較して理解するに書いていきます。
JavaScriptの例はJavaのを見て書きました。
クラス型・プロトタイプ型、型付の強弱、アクセス修飾子など特徴の違いなどは活かしていません。
ご了承ください。

Decorator

スポンジケーキが1つあり、クリームを塗ればショートケーキ、それにイチゴを載せればイチゴショートケーキに
チョコを塗ればチョコケーキになる
オブジェクトも似ていて、中心となるスポンジケーキのようなオブジェクトがあり、それに飾りつけとなる機能を一皮一皮かぶせていって、目的にあったオブジェクトに仕上げていく
このような、オブジェクトにどんどんデコレーションをするデザインパターン
decoratorとは「decorate(飾り付け)するもの」という意味

ファイル 2017-01-29 .png

Javaでの実装

クラス図

Decorator2.png

コード

Main.java
public class Main {
    public static void main(String[] args) {
        Display b1 = new StringDisplay("Hello, world.");
        Display b2 = new SideBorder(b1, '#');
        Display b3 = new FullBorder(b2);
        b1.show();
        b2.show();
        b3.show();
        Display b4 = 
            new SideBorder(
                new FullBorder(
                    new FullBorder(
                        new SideBorder(
                            new FullBorder(
                                new StringDisplay("こんにちは。")
                            )
                        , '*')
                    )
                )
            , '/');
        b4.show();
    }
}
Deisplay.java
public abstract class Display {
    public abstract int getColumns();
    public abstract int getRows();
    public abstract String getRowText(int row);
    public final void show() {
        for (int i = 0; i < getRows(); i++) {
            System.out.println(getRowText(i));
        }
    }
}
StringDisplay.java
public class StringDisplay extends Display {
    private String string;
    public StringDisplay(String string) {
        this.string = string;
    }
    public int getColumns() {
        return string.getBytes().length;
    }
    public int getRows() {
        return 1;
    }
    public String getRowText(int row) {
        if (row == 0) {
            return string;
        } else {
            return null;
        }
    }
}
Border.java
public abstract class Border extends Display{
    protected Display display;
    protected Border(Display display) {
        this.display = display;
    }
}
SideBorder.java
public class SideBorder extends Border {
    private char borderChar;
    public SideBorder(Display display, char ch) {
        super(display);
        this.borderChar = ch;
    }
    public int getColumns() {
        return 1 + display.getColumns() + 1;
    }
    public int getRows() {
        return display.getRows();
    }
    public String getRowText(int row) {
        return borderChar + display.getRowText(row) + borderChar;
    }
}
FullBorder.java
public class FullBorder extends Border {
    public FullBorder(Display display) {
        super(display);
    }
    public int getColumns() {
        return 1 + display.getColumns() + 1;
    }
    public int getRows() {
        return 1 + display.getRows() + 1;
    }
    public String getRowText(int row) {
        if (row == 0) {
            return "+" + makeLine('-', display.getColumns()) + "+";
        } else if (row == display.getRows() + 1) {
            return "+" + makeLine('-', display.getColumns()) + "+";
        } else {
            return "|" + display.getRowText(row - 1) + "|";
        }
    }
    private String makeLine(char ch, int count) {
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < count; i++) {
            buf.append(ch);
        }
        return buf.toString();
    }
}

JavaScript

※consoleの仕様なのかわからないのですが、2byteのものを入れるとレイアウトが崩れてしまいます

コード

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Decorator</title>
</head>
<body>
    <script src="Main.js"></script>
    <script src="StringDisplay.js"></script>
    <script src="SideBorder.js"></script>
    <Script src="FullBorder.js"></script>
    <script src="Display.js"></script>
</body>
</html>
Main.js
MAIN = {};
MAIN.init = function() {
    var b1 = new StringDisplay("Hello world.");
    var b2 = new SideBorder(b1, '#');
    var b3 = new FullBorder(b2);
    b1.show();
    b2.show();
    b3.show();
    var b4 = 
        new SideBorder(
            new FullBorder(
                new FullBorder(
                    new SideBorder(
                        new FullBorder(
                            new StringDisplay("Hello")
                        ), '*'
                    )
                )
            ), '/'
        );
    b4.show();
};


window.addEventListener("load", MAIN.init);
StringDisplay.js
var StringDisplay = function(string) {
    this.string = string;
};


StringDisplay.prototype = {
    constructor: "StringDisplay",

    getColumns: function() {
        return this.getBytes(this.string);
    },
    getRows: function() {
        return 1;
    },
    getRowText: function(row) {
        if (row == 0) {
            return this.string;
        } else {
            return null;
        }
    },
    getBytes: function(str) {
        var len = 0;
        str = escape(str);
        for (i = 0; i < str.length; i++, len++) {
            if (str.charAt(i) == "%") {
                if (str.charAt(++i) == "u") {
                    i += 3;
                    len++
                }
                i++;
            }
        }
        return len;
    }
};
SideBorder.js
var SideBorder = function(display, ch) {
    this.display = display;
    this.borderChar = ch;
};

SideBorder.prototype = {
    constructor: "SideBorder",

    getColumns: function() {
        return 1 + this.display.getColumns() + 1;
    },
    getRows: function() {
        return this.display.getRows();
    },
    getRowText(row) {
        return this.borderChar + this.display.getRowText(row) + this.borderChar;
    }
}
FullBorder.js
var FullBorder = function(display) {
    this.display = display;
};

FullBorder.prototype = {
    constructor: "FullBorder",

    getColumns: function() {
        return 1 + this.display.getColumns() + 1;
    },
    getRows: function() {
        return 1 + this.display.getRows() + 1;
    },
    getRowText: function(row) {
        if (row == 0) {
            return "+" + this.makeLine('-', this.display.getColumns()) + "+";
        } else if (row == this.display.getRows() + 1) {
            return "+" + this.makeLine('-', this.display.getColumns()) + "+";
        } else {
            return "|" + this.display.getRowText(row - 1) + "|";
        }
    },
    makeLine: function(ch, count) {
        var stringBuffer = "";
        for (var i = 0; i < count; i++) {
            stringBuffer += '-';
        }
        return stringBuffer;
    }
}
Display.js
StringDisplay.prototype.show = 
SideBorder.prototype.show = 
FullBorder.prototype.show = function() {
    for (var i = 0; i < this.getRows(); i++) {
        console.log(this.getRowText(i));
    }
};

Decoratorパターンの登場人物

Componentの役

機能を追加するときの核になる役
インタフェース(API)だけを定める
サンプルプログラム⇒Display(class)

ConcreateComponentの役

Component役のインタフェース(API)を実装している
サンプルプログラム⇒StringDisplay(class)

Decorator(装飾者)の役

Component役のインタフェース(API)を実装している
そして、Component役を持つ
サンプルプログラム⇒Border(class)

ConcreateDecorator(具体的な装飾者)の役

具体的なDecoratorの役
サンプルプログラム⇒SideBorder(class)
          FullBorder(class)

Decoratorパターンのクラス図

Decorator.png

Decoratorパターンの必要性

Decoratorパターンを使うことにより、具体的な飾り枠(ConcreateDecorator)役をたくさん作って、これらを組み合わせることにより様々な種類のオブジェクトを作成することができる
そして中身を変更することなく機能の追加を行うことができ、動的に機能の追加できるところも特徴である

Decoratorパターンの使い時

再帰的な構造を扱い同一視できるという特徴がCompositeと似ているが、目的が異なる
Compositeは内側に再帰的な構造
Decoratorは外側に再帰的な構造

Compositeと比較.PNG

関連しているパターン

  • Adapterパターン
  • Strategyパターン

参考

増補改訂版Java言語で学ぶデザインパターン入門

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away