2006 年 12 月 28 日 23 時 53 分

ClientInitMessage/ServerInitMessage


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


初期化用のメッセージが出てきたので、
ProtocolVersionMessage や SecurityResultMessage と同様、
Message インタフェースを継承したクラスにしておこう。

========== ClientInit.java ==========

package jp.loafer.rfb.message;

import java.io.IOException;

import jp.loafer.rfb.RFBContext;
import jp.loafer.rfb.io.RFBInputStream;
import jp.loafer.rfb.io.RFBOutputStream;

/**
* ClientInit メッセージクラス。
* @author kes
*/
public class ClientInitMessage implements Message {

    /**
     * 永続化用既定コンストラクタ。
     */
    public ClientInitMessage() {
        shared = true;
    }

    /**
     * ClientInit メッセージを作成。
     * @param shared 共有接続を許可するかどうか。
     */
    public ClientInitMessage(boolean shared) {
        this.shared = shared;
    }

    /**
     * 共有接続を許可するかどうかを取得。
     * @return shared。
     */
    public boolean isShared() {
        return shared;
    }

    /**
     * @see Message#read(RFBContext, RFBInputStream)
     */
    public void read(RFBContext context, RFBInputStream in) throws IOException {
        shared = in.readU8() != 0;
    }

    /**
     * @see Message#write(RFBContext, RFBOutputStream)
     */
    public void write(RFBContext context, RFBOutputStream out) throws IOException {
        out.writeU8(shared ? 1 : 0);
    }

    private boolean shared;

}

========== end of ClientInit.java ==========

ClientInit メッセージには、1 つしかメンバがないため、
その実装は非常に簡単である。

========== ServerInit.java ==========

package jp.loafer.rfb.message;

import java.io.IOException;

import jp.loafer.rfb.RFBContext;
import jp.loafer.rfb.io.RFBInputStream;
import jp.loafer.rfb.io.RFBOutputStream;

/**
* ServerInit メッセージクラス。
* @author kes
*/
public class ServerInitMessage implements Message {

    /**
     * 永続化用既定コンストラクタ。
     */
    public ServerInitMessage() {
        width = 1;
        height = 1;
        format = new PixelFormat();
        name = null;
    }

    /**
     * ServerInit メッセージを作成。
     * @param width 幅。
     * @param height 高さ。
     * @param format 画素形式。
     * @param name セッションの名前。
     */
    public ServerInitMessage(int width, int height, PixelFormat format, String name) {
        if (format == null)
                throw new IllegalArgumentException("format must not be null");
        if (width == 0)
                throw new IllegalArgumentException("Invalid width.");
        if (height == 0)
                throw new IllegalArgumentException("Invalid height.");   
        this.width = width;
        this.height = height;
        this.format = format;
        this.name = name;
    }

    /**
     * 画面の幅を取得。
     * @return 画面の幅。
     */
    public int getWidth() {
        return width;
    }

    /**
     * 画面の高さを取得。
     * @return 画面の高さ。
     */
    public int getHeight() {
        return height;
    }

    /**
     * 画面の画素形式を取得。
     * @return 画面の画素形式。
     */
    public PixelFormat getFormat() {
        return format; // 不変クラスはそのまま公開
    }

    /**
     * 画面の名前を取得。
     * @return 画面の名前。
     */
    public String getName() {
        return name;
    }
   
    /**
     * @see Message#read(RFBContext, RFBInputStream)
     */
    public void read(RFBContext context, RFBInputStream in) throws IOException {
        width = in.readU16();
        height = in.readU16();
        format = in.readMessage(context, PixelFormat.class);
        name = in.readString();

        if (width == 0) throw new IOException("Invalid width.");
        if (height == 0) throw new IOException("Invalid height.");   
    }

    /**
     * @see Message#write(RFBContext, RFBOutputStream)
     */
    public void write(RFBContext context, RFBOutputStream out) throws IOException {
        out.writeU16(width);
        out.writeU16(height);
        out.writeMessage(context, format);
        out.writeString(name);
    }

    private int width;
    private int height;
    private PixelFormat format;
    private String name;

}

========== end of ServerInit.java ==========

ServerInit メッセージはかなり複雑なので、
メッセージに含まれる PIXEL_FORMAT 構造は、
独立したクラスとして設計することにしよう。

現時点ではまだ書いていないが、PIXEL_FORMAT は、
PixelFormat クラスとして作る事にしよう。
そうすると、ServerInitMessage はフィールドとして
PixelFormat のインスタンスを持つ事になる。

さて、一般的なクラスの設計を考える場合、
クラスのフィールドとして持つ別クラスのインスタンスを、
そのまま getter で公開してしまうと、
所有する側のクラスの知らない所で変更される危険がある。
オブジェクトはプリミティブと違い、
値そのものではなく参照がコピーされてしまうからだ。

これに関しては、以前も少し書いた。

■ProtocolVersionMessage クラス
http://mixi.jp/view_diary.pl?id=298902694&owner_id=2300658

ServerInitMessage の実装例の場合は、
getFormat() メソッドで内包フィールドを返しているため、
呼び出した側が、format フィールドが指すインスタンスに、
自由にアクセスができる事になるわけである。

そこで今回も、「不変クラス」を適用することにしよう。

今まで設計してきたクラスは、そのほとんどが
読み取り専用のメソッドのみ持ち、setter を持っておらず、
生成したインスタンスの値は変化しないという特徴を持つ。

唯一の例外は、Message インタフェースの read メソッドだ。
これは永続化のために便宜上用意しているだけであるため、
read を呼びださない紳士協定さえ守れば、
不変クラスとして利用することが可能となる。

PixelFormat も不変クラスとして設計しておけば、
ServerInitMessage が getFormat() メソッドで、
内包している PixelFormat のインスタンスを返しても、
不変クラス故にインスタンスに変更を加えられることはない。

もし、PixelFormat が不変クラスではないならば、
getFormat() では format のクローンを返す必要がある。
もし、getFormat が何度も呼び出される環境なら、
それによりパフォーマンスが低下する恐れがある。

なお、不変クラスの代表例が、String クラスだ。
一度作成した String インスタンスは変更できないため、
何の心配もなくメソッドに引き渡したり、
戻り値にすることができるのである。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.