Apache Kafkaインストールからチュートリアルまで

Apache Kafkaとは?

Apache Kafka is an open-source stream processing platform developed by the Apache Software Foundation written in Scala and Java. The project aims to provide a unified, high-throughput, low-latency platform for handling real-time data feeds. *1

Apache Kafkaは、ScalaJavaで書かれたApacheソフトウェア財団によって開発されたオープンソースのストリーム処理プラットフォームである。このプロジェクトは、リアルタイムのデータフィードを処理するための、高スループット、低レイテンシのプラットフォームを提供することを目指している。

What is Kafka good for?

It gets used for two broad classes of application:

  • Building real-time streaming data pipelines that reliably get data between systems or applications

  • Building real-time streaming applications that transform or react to the streams of data

Kafkaを使うことで

  • システムまたはアプリケーション間で確実にデータを取得するリアルタイムのストリーミングデータパイプラインの構築が可能
  • データストリームを変換または反応するリアルタイムのストリーミングアプリケーションを構築が可能

GitHub

github.com

Installing Kafka

Download the code

$ wget http://ftp.riken.jp/net/apache/kafka/0.10.2.0/kafka_2.11-0.10.2.0.tgz
$ tar -zxf kafka_2.11-0.10.2.0.tgz
$ cd kafka_2.11-0.10.2.0

Install java

$ yum search openjdk
$ yum install java-1.8.0-openjdk-devel

Tutorial

Kafkaのコマンドラインクライアントから、メッセージをKafkaクラスタに送信する。

Start the server

# start ZooKeeper server
$ bin/zookeeper-server-start.sh config/zookeeper.properties
# start Kafka server
$ bin/kafka-server-start.sh config/server.properties

Create a topic

$ bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
# see the topic
$ bin/kafka-topics.sh --list --zookeeper localhost:2181

Send messages

$ bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test

上記コマンド実行後、以下のメッセージをコンソールに続けて入力する。

$ bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test
This is a message
This is another message

Receive messages(start a consumer)

メッセージ送信したクライアントとは別のクライアントを立ち上げ、以下のコマンドを実行する

$ bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning

すると、メッセージ送信で入力したメッセージが受信される。

$ bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning
This is a message
This is another message

references

導入事例など

RabbitMQインストールからPub/Sub機能チュートリアルまで

RabbitMQとは

RabbitMQ(ラビットエムキュー)は、Advanced Message Queuing Protocol(AMQP)を使用した、オープンソースのメッセージ指向ミドルウェアである。 RabbitMQ Serverは、Erlang言語で記述されており、クラスタリングとフェイルオーバーのためにOpen Telecom Platformフレームワークで構築されている。*1

GitHub

github.com

Install Erlang

RabbitMQはErlangが使われているため、まずErlangをインストールする。

$ wget http://packages.erlang-solutions.com/erlang-solutions-1.0-1.noarch.rpm
$ rpm -Uvh erlang-solutions-1.0-1.noarch.rpm
$ yum install erlang
# Verify your installation of Erlang
$ erl
Erlang/OTP 20 [RELEASE CANDIDATE 2] [erts-9.0] [source] [64-bit] [smp:1:1] [ds:1:1:10] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V9.0  (abort with ^G)
1>

Install RabbitMQ

$ wget http://www.rabbitmq.com/releases/rabbitmq-server/v3.6.10/rabbitmq-server-3.6.10-1.el7.noarch.rpm
$ rpm --import https://www.rabbitmq.com/rabbitmq-signing-key-public.asc
$ yum install rabbitmq-server-3.6.10-1.el7.noarch.rpm

start the RabbitMQ server

$ systemctl start rabbitmq-server.service
$ systemctl enable rabbitmq-server.service

# check the status of RabbitMQ
$ rabbitmqctl status

# Enable and use the RabbitMQ management console
$ rabbitmq-plugins enable rabbitmq_management
$ chown -R rabbitmq:rabbitmq /var/lib/rabbitmq/

add an user

  • {username}: new user’s name
  • {pw} : the new user’s password
$ rabbitmqctl add_user #{username} #{pw}
$ rabbitmqctl set_user_tags #{username} administrator
$ rabbitmqctl set_permissions -p / #{username} ".*" ".*" ".*"

visit the RabbitMQ Management tool via web browser

http://[your-rabbitMQ-server-IP]:15672/

問題なければ以下のログイン画面が表示される。

f:id:tic40:20170626183636p:plain

先ほど作成したusername, passwordの組み合わせでログインすることができる。

ここからTutorial

rubyで Pub/Sub を試す。以下の流れ。

  1. クライアント(Subscriber)1, 2は、"logs" チャネルをSubscribeする。
  2. クライアント3(Publisher)から"logs" チャネルへメッセージをpublishする。
  3. クライアント1,2 でクライアント3から発信されたメッセージを受信できることを確認する。

install bunny via gem

$ yum install rubygems
$ gem install bunny --version ">= 2.6.4"

create a receive_logs.rb

#!/usr/bin/env ruby
# encoding: utf-8

require "bunny"

conn = Bunny.new(:automatically_recover => false)
conn.start

ch  = conn.create_channel
x   = ch.fanout("logs")
q   = ch.queue("", :exclusive => true)

q.bind(x)

puts " [*] Waiting for logs. To exit press CTRL+C"

begin
  q.subscribe(:block => true) do |delivery_info, properties, body|
    puts " [x] #{body}"
  end
rescue Interrupt => _
  ch.close
  conn.close

  exit(0)
end

create emit_log.rb

#!/usr/bin/env ruby
# encoding: utf-8

require "bunny"

conn = Bunny.new(:automatically_recover => false)
conn.start

ch   = conn.create_channel
x    = ch.fanout("logs")

msg  = ARGV.empty? ? "Hello World!" : ARGV.join(" ")

x.publish(msg)
puts " [x] Sent #{msg}"

conn.close

クライアント1, 2それぞれを購読状態にする

$ ruby receive_logs.rb
 [*] Waiting for logs. To exit press CTRL+C

クライアント3(Publisher) からメッセージを送信する

$ ruby emit_log.rb
 [x] Sent Hello World!
$ ruby emit_log.rb 'this is test message!'
 [x] Sent this is test message!

すると、クライアント2, 3の画面上に、メッセージが表示(受信)される

$ ruby receive_logs.rb
 [*] Waiting for logs. To exit press CTRL+C
 [x] Hello World!
 [x] this is test message!

references

和キッチンかんなのかき氷を食べる

世田谷区にある「和kitchen かんな 」のかき氷がとても人気だという話を聞いたので行ってきた。

twitter.com

よく午後に店外に行列を作っているのを見かける。ここはかき氷専門店ではなく、キッチンと銘打っているいるように、食事も提供している。(しかし行列はほぼ女子しかいないのでみんなかき氷目当ての様子)

待つのは嫌なので土曜の午前中、開店直後に行ってきた。

11時オープンで11時15分頃に店に到着。すると、すでに6人程店内の階段に行列を作っていた。

待っていると、かき氷ですか?食事ですか?と聞かれる。かき氷と答えた。

定番人気のティラミスかき氷を頼んだ。950円。

罪深い#ティラミスかき氷 #kakigori

味は、かき氷というかティラミスでは?という感じだった。見た目かなりボリュームがあるんだけど、かき氷がふわふわなので、1人でも全然食べ切れる。そりゃ人気でるよな。

PHP7でデザインパターン入門23/23 Interpreterパターン

Interpreter パターンとは

Interpreter パターンの基本的な考えは、定義された種類の問題を素早く解くために、ドメインに特化した言語を実装することである。

Interpreter パターン - Wikipedia

Interpreterパターンでは、プログラムが解決しようとしている問題を簡単な「ミニ言語」で表現する。 通訳(interpreter)の役目を果たすプログラムを作ることで、そのミニ言語を理解し、ミニプログラムを解釈・実行する。

example

ミニ言語の命令

  • go: 前へ1ブロック進め
  • right: 右を向け
  • left: 左を向け

ミニ言語の構文

以下のように命令の初めて終わりを program と end で囲む

program go end 

例えば回れ右をするミニプログラムは以下のようになる。

program go right right go end

また、repeat N … end 構文を使うことでN回の繰り返し処理ができるものとする。

program repeat 4 go right end end

サンプルプログラムは、上記のミニ言語を構文解析する。ミニプログラムを構文木としてメモリ上に作り上げる処理が構文解析。 サンプルプログラムでは、この構文木を構築するまでを扱う。

ミニ言語の文法として、BNF*1を使う。

Node 抽象クラス

構文木の各部分(ノード)を構成する最上位のクラス。parseメソッドのみ宣言している。このparseは「構文解析という処理を行う」ためのメソッドである。引数のContextは構文解析を行っている「状況」を表すクラス。

<?php
abstract class Node
{
    abstract public function parse(Context $context);
}

ProgramNodeクラス

構文解析の際の処理単位をトークンと呼ぶ。skipTokenメソッドはprogramというトークンがなかったら、ParseExceptionを投げる。

<?php
// <program> ::= program <command list>
class ProgramNode extends Node
{
    private $commandListNode;

    public function parse(Context $context)
    {
        $context->skipToken("program");
        $this->commandListNode = new CommandListNode();
        $this->commandListNode->parse($context);
    }
    public function __toString(): string
    {
        return $this->commandListNode
            ? "[program {$this->commandListNode}]"
            : "";
    }
}

CommandListNodeクラス

<?php
// <command list> ::= <command>* end
class CommandListNode extends Node
{
    private $list = [];

    public function parse(Context $context)
    {
        while (true) {
            if ($context->currentToken() === null) {
                throw new ParseException("Missing 'end'");
            }
            if ($context->currentToken() === "end") {
                $context->skipToken("end");
                break;
            }
            $commandNode = new CommandNode();
            $commandNode->parse($context);
            $this->list[] = $commandNode;
        }
    }

    public function __toString(): string
    {
        return $this->list
            ? "[" . join(" ", $this->list) . "]"
            : "[]";
    }
}

CommandNodeクラス

<?php
// <command> ::= <repeat command> | <primitive command>
class CommandNode extends Node
{
    private $node;

    public function parse(Context $context)
    {
        if ($context->currentToken() === "repeat") {
            $this->node = new RepeatCommandNode();
            $this->node->parse($context);
        } else {
            $this->node = new PrimitiveCommandNode();
            $this->node->parse($context);
        }
    }

    public function __toString(): string
    {
        return $this->node ?? "";
    }
}

RepeatCommandNodeクラス

<?php
// <repeat command> ::= repeat <number> <command list>
class RepeatCommandNode extends Node
{
    private $number;
    private $commandListNode;

    public function parse(Context $context)
    {
        $context->skipToken("repeat");
        $this->number = $context->currentNumber();
        $context->nextToken();
        $this->commandListNode = new CommandListNode();
        $this->commandListNode->parse($context);
    }
    public function __toString(): string
    {
        return $this->commandListNode
            ? "[repeat {$this->number} {$this->commandListNode}]"
            : "";
    }
}

PremitiveCommandNodeクラス

<?php
// <primitive command> ::= go | right | left
class PrimitiveCommandNode extends Node
{
    private $name;

    public function parse(Context $context)
    {
        $this->name = $context->currentToken();
        $context->skipToken($this->name);
        if ($this->name !== "go"
            && $this->name !== "right"
            && $this->name !== "left") {
            throw new ParseException("{$this->name} is undefined");
        }
    }

    public function __toString(): string
    {
        return $this->name ?? "";
    }
}

Contextクラス

<?php
class Context
{
    private $token;
    private $currentToken;

    public function __construct(string $text)
    {
        $this->token = preg_split("/[\s,]+/", $text);
        $this->nextToken();
    }

    public function nextToken(): string
    {
        $this->currentToken = ($this->currentToken === null)
            ? current($this->token)
            : next($this->token);

        return $this->currentToken;
    }

    public function currentToken(): string
    {
        return $this->currentToken;
    }

    public function skipToken(string $token)
    {
        if ($token !== $this->currentToken) {
            throw new ParseException("Warning: {$token} is expected, but {$this->currentToken} is found.");
        }
        $this->nextToken();
    }

    public function currentNumber(): int
    {
        return intval($this->currentToken);
    }
}

ParseExceptionクラス

<?php
class ParseException extends Exception
{
    public function __construct(string $msg)
    {
        parent::__construct($msg);
    }
}

Main

<?php
$f = file(__DIR__ . "/Program.txt", FILE_IGNORE_NEW_LINES);
if (empty($f)) {
    die('file read error.');
}

try {
    foreach ($f as $v) {
        echo "text = \"" . $v . "\"" . PHP_EOL;
        $node = new ProgramNode();
        $node->parse(new Context($v));
        echo "node = " . $node . PHP_EOL;
    }
} catch (\Exception $e) {
    echo $e->getMessage();
}

実行結果

$ php Main.php
text = "program end"
node = [program []]
text = "program go end"
node = [program [go]]
text = "program go right go right go right go right end"
node = [program [go right go right go right go right]]
text = "program repeat 4 go right end end"
node = [program [[repeat 4 [go right]]]]
text = "program repeat 4 repeat 3 go right go left end right end end"
node = [program [[repeat 4 [[repeat 3 [go right go left]] right]]]]

ソースコード

github.com

役割

AbstractExpression役

構文木のノードに共通のインターフェースを定める役。 サンプルコードにおけるNodeクラス。

TerminalExpression役

BNFのターミナルエクスプレッションに対応する役。 サンプルコードにおけるPremitiveCommandNode

NonterminalExpresion役

BNFのノンターミナルエクスプレッションに対応する役 サンプルコードにおけるProgramNode, CommandNode, RepeatCommandNode, CommandListNodeクラス。

Context役

インタプリタ構文解析を行うための情報を提供する役。 サンプルコードにおけるContextクラス。

Client役

サンプルコードにおけるMain.php

関連するパターン

デザインパターンのまとめ

今更ながら、改めてデザインパターンを学習して良かった。

  • 抽象クラス、インターフェースの役割
  • 継承と委譲
  • 交換可能性
  • コードの再利用性

実際にデザインパターンを言語化してまとめることで、以前より設計に自信を持てるようになった。具体的には、デザインの目的、思想を言葉で説明できるようになり、より設計に対して強い根拠を持てるようになった。

このデザインパターン本学習は一旦終わり。

とても楽しく学習できた。参考にした本「Java言語で学ぶデザインパターン入門」の著者、結城浩に感謝。

関連パターン

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

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

RedisインストールからPub/Sub機能を使うまで

Redisとは?

Redisは、データ構造サーバーを実装するオープンソースソフトウェアプロジェクトである。いわゆるNoSQLデータベースの一つであり、Redis Labs(英語版)がスポンサーとなって開発されている。ネットワーク接続されたインメモリデータベースでかつキー・バリュー型データベース(英語版)であり、オプションとして永続性を持つ。*1

一言で説明すると、Redisはオープンソースインメモリデータストア。データベースやキャッシュ、メッセージブローカーとして利用されている。*2

github.com

Pub/Sub (Publish/Subscribe) とは?

日本語では出版-購読型モデルとも呼ばれる。

非同期メッセージングパラダイムの一種であり、メッセージの送信者(出版側)が特定の受信者(購読側)を想定せずにメッセージを送るようプログラムされたものである。出版されたメッセージにはクラス分けされ、購読者に関する知識を持たない。購読側は興味のあるクラスを指定しておき、そのクラスに属するメッセージだけを受け取り、出版者についての知識を持たない。出版側と購読側の結合度が低いため、スケーラビリティがよく、動的なネットワーク構成に対応可能である。*3

Installing Redis

$ wget http://download.redis.io/redis-stable.tar.gz
$ tar xvzf redis-stable.tar.gz
$ cd redis-stable
$ make

make完了後、redis-stable/src/ に様々な実行可能ファイルが生成される。 今回は、redis-server, redis-cli を利用する。

  • redis-server is the Redis Server itself.
  • redis-sentinel is the Redis Sentinel executable (monitoring and failover).
  • redis-cli is the command line interface utility to talk with Redis.
  • redis-benchmark is used to check Redis performances.
  • redis-check-aof and redis-check-dump are useful in the rare event of corrupted data files.

Pub/Sub機能を試す

  1. Redis サーバを起動する
  2. クライアント1 で、channel ‘foo’ を subscribe する
  3. クライアント2 で、subscribeされている channel ‘foo’ に対して message ‘hello world!’ を publish する

step1. start redis server

$ ./src/redis-server
                _._
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis 3.2.9 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: xxxx
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |           http://redis.io
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'
              `-.__.-'

でっかいロゴが出てきた。

step2. クライアント1で channel ‘foo’ を subscribe する

[client 1]

$ ./scr/redis-cli
127.0.0.1:6379> SUBSCRIBE foo
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "foo"
3) (integer) 1

step3. クライアント2 で、message ‘hello world!’ を publish する

[client 2]

$ ./src/redis-cli
127.0.0.1:6379> PUBLISH foo 'hello world!'
(integer) 1
127.0.0.1:6379>

publishすると、client 1のスクリーン上に以下のようにメッセージ ‘hello world!’ が表示される

[client 1]

1) "message"
2) "foo"
3) "hello world!"

references

refered to the Redis Quick Start – Redis

PHP7でデザインパターン入門22/23 Commandパターン

Command パターンとは

動作を表現するオブジェクトを示す。Command オブジェクトは、動作とそれに伴うパラメータをカプセル化したものである。

Command パターン - Wikipedia

クラスが仕事を行う時、自分のクラスや他のクラスのメソッドを呼び出す。メソッドを呼び出した結果はオブジェクトの状態に反映されるが、履歴はどこにも残らない。 こんなとき、仕事の「命令」を表現するクラスがあれば便利。行いたい仕事を「メソッドを呼び出す」という動的な処理として表現するのではなく、命令を表すクラスのインスタンスという1個の「もの」として表現することができるからである。履歴を管理したいときは、そのインスタンスの集まりを管理すればいいことになる。命令をの集まりを保存しておけば、同じ命令の再実行もできる。

デザインパターンではこのような「命令」にCommandパターンという名前をつけている。CommandはEventと呼ばれる場合もある。

example

サンプルコードとして、wikiJavaサンプルコードを引用する。

Consider a “simple” switch. In this example we configure the Switch with two commands: to turn the light on and to turn the light off. A benefit of this particular implementation of the command pattern is that the switch can be used with any device, not just a light. The Switch in the following C# implementation turns a light on and off, but the Switch’s constructor is able to accept any subclasses of Command for its two parameters. For example, you could configure the Switch to start an engine.

Commandインターフェース

<?php
interface Command
{
    public function execute();
    public function __toString(): string;
}

MySwitchクラス

<?php
class MySwitch
{
    private $history = [];

    public function storeAndExecute(Command $cmd)
    {
        $this->history[] = $cmd;
        $cmd->execute();
    }

    public function displayHistory()
    {
        foreach($this->history as $k => $v) {
            echo "{$k}: {$v}" . PHP_EOL;
        }
    }
}

Lightクラス

<?php
class Light
{
    public function turnOn()
    {
        echo "The light is on" . PHP_EOL;
    }

    public function turnOff()
    {
        echo "The light is off" . PHP_EOL;
    }
}

FlipUpCommandクラス

<?php
class FlipUpCommand implements Command
{
    private $theLight;

    public function FlipUpCommand(Light $light)
    {
        $this->theLight = $light;
    }

    public function execute()
    {
        $this->theLight->turnOn();
    }

    public function __toString() : string
    {
        return get_class($this);
    }
}

FlipDownCommandクラス

<?php
class FlipDownCommand implements Command
{
    private $theLight;

    public function FlipDownCommand(Light $light)
    {
        $this->theLight = $light;
    }

    public function execute()
    {
        $this->theLight->turnOff();
    }

    public function __toString(): string
    {
        return get_class($this);
    }
}

Main

<?php
$lamp = new Light();
$switchUp = new FlipUpCommand($lamp);
$switchDown = new FlipDownCommand($lamp);
$mySwitch = new MySwitch();

for($i = 0; $i < 10; $i++) {
    if (boolval(rand(0, 1))) {
        $mySwitch->storeAndExecute($switchUp);
    } else {
        $mySwitch->storeAndExecute($switchDown);
    }
    usleep(50000); // wait 0.5 sec
}
echo PHP_EOL . "=== command history ===" . PHP_EOL;
$mySwitch->displayHistory();

実行結果

$ php Main.php
The light is on
The light is off
The light is off
The light is off
The light is off
The light is off
The light is on
The light is on
The light is off
The light is off

=== command history ===
0: FlipUpCommand
1: FlipDownCommand
2: FlipDownCommand
3: FlipDownCommand
4: FlipDownCommand
5: FlipDownCommand
6: FlipUpCommand
7: FlipUpCommand
8: FlipDownCommand
9: FlipDownCommand

ソースコード

github.com

役割

Command役

命令のインターフェースを定義する役。 サンプルコードにおけるCommandインターフェース。

ConcreteCommand役

Command役のインターフェースを実装している役。 サンプルコードにおけるFlipUpCommandクラス、FlipDownCommandクラス。

Receiver役

Command役が命令を実行するときに対象となる役。命令の受け取り手とも呼べる。 サンプルコードにおけるLightクラス。

Client役

ConcreteCommand役を生成し、その際にReceiver役を割り当てる役。 サンプルコードにおけるMain.php

Invoker役

命令の実行を開始する役。Command役で定義されているインターフェースを呼び出す役となる。 サンプルコードにおけるMySwitchクラス。

まとめ

「命令」をオブジェクトとして表現することで、履歴をとったり再実行を行ったりすることが可能となる。

関連パターン

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

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

PHP7でデザインパターン入門21/23 Proxyパターン

Proxy パターンとは

Proxy(プロキシ、代理人)とは、大まかに言えば、別の物のインタフェースとして機能するクラスである。その「別の物」とは何でもよく、ネットワーク接続だったり、メモリ上の大きなオブジェクトだったり、複製がコスト高あるいは不可能な何らかのリソースなどである。

Proxy パターン - Wikipedia

proxy(代理人)というのは、仕事を行うべき本人の代わりとなる人。本人でなくてもできるような仕事をまかせるために代理人を立てる。 オブジェクト指向では、「本人」も「代理人」もオブジェクトとなる。忙しくてその仕事ができない本人オブジェクトの代わりに、代理人オブジェクトが(ある程度)仕事をこなしてやることになる。

example

ここで作るサンプルプログラムは、「名前つきのプリンタ」。このプリンタは画面に文字を表示する。

まずPrinterProxyクラスのインスタンス(代理人)を生成する。そのインスタンスには「Alice」という名前をつけ、その名前を表示する。その後「Bob」という名前に変更し、その名前を表示する。名前の設定のや取得では、まだ本物のPrinterクラスのインスタンス(本人)は生成していない。名前の設定と取得という部分は、PrinterProxyクラスが代理で行う。最後にPrintメソッドを呼び出して、実際にプリントを行う段階になってはじめて、PrinterProxyクラスはPrinterクラスのインスタンスを生成する

ここでは、Printerクラスのインスタンス生成にとても時間がかかるという前提で、プログラムを作成している。コンストラクタからheavyJobメソッドを呼び出し、わざと「重い仕事」をさせている。

Printableクラス

Printer、PrinterProxyクラスの共通インターフェース

<?php
interface Printable
{
    public function setPrinterName(string $name);
    public function getPrinterName(): string;
    public function print(string $string);
}

PrinterProxyクラス

名前つきのプリンタを表すクラス(代理人)

<?php
class PrinterProxy implements Printable
{
    private $name;
    private $real;
    public function __construct(string $name)
    {
        $this->name = $name;
    }
    public function setPrinterName(string $name)
    {
        if ($this->real !== null) {
            $this->real->setPrinterName($name);
        }
        $this->name = $name;
    }
    public function getPrinterName(): string
    {
        return $this->name;
    }
    public function print(string $string)
    {
        $this->realize();
        $this->real->print($string);
    }
    private function realize()
    {
        if ($this->real === null) {
            $this->real = new Printer($this->name);
        }
    }
}

Printerクラス

名前つきのプリンタを表すクラス(本人)

<?php
class Printer implements Printable
{
    private $name;
    public function __construct(string $name)
    {
        $this->name = $name;
        $this->heavyJob("Printerのインスタンス(" . $this->name . ")を生成中");
    }
    public function setPrinterName(string $name)
    {
        $this->name = $name;
    }
    public function getPrinterName(): string
    {
        return $this->name;
    }
    public function print(string $string)
    {
        echo "=== {$this->name} ===" . PHP_EOL;
        echo $string;
    }
    private function heavyJob(string $msg)
    {
        echo $msg;
        for ($i = 0; $i < 5; $i++) {
            sleep(1);
            echo ".";
        }
        echo "完了." . PHP_EOL;
    }
}

Main

<?php
$p = new PrinterProxy("Alice");
echo "名前は現在" . $p->getPrinterName() . "です。" . PHP_EOL;
$p->setPrinterName("Bob");
echo "名前は現在" . $p->getPrinterName() . "です。" . PHP_EOL;
$p->print("Hello, world.");

実行結果

$ php Main.php
名前は現在Aliceです。
名前は現在Bobです。
Printerのインスタンス(Bob)を生成中.....完了.
=== Bob ===
Hello, world.#

ソースコード

github.com

役割

Subject役

Proxy役とRealSubject役を同一視するためのインターフェースを定める。 サンプルコードにおけるPrintableインターフェース

Proxy役

Proxy役はClient役からの要求をできるだけ処理する。もし自分だけで処理できなかったら、Proxy役はRealSubject役に仕事を任せる。 Proxy役は、ほんとうにRealSubject役が必要になってからRealSubject役を生成する。Proxy役はSubject役で定められているインターフェースを実装している。 サンプルコードにおけるPrinterProxyクラス。

RealSubject役

代理人のProxy役で手に負えなくなったときに登場するのが、本人のRealSubject役。 サンプルコードにおけるPrinterクラス。

Client役

Proxyパターンの利用者。 サンプルコードにおけるMain。

まとめ

  • 代理人を使うことで処理を高速化する
  • HTTPプロキシ
    • これもProxyパターンに当てはめて考えることができる。WebブラウザがClient、HTTPプロキシがProxy、WebサーバがRealSubject役。

関連パターン

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

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