このアーカイブは同期化されません。 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 インスタンスは変更できないため、
何の心配もなくメソッドに引き渡したり、
戻り値にすることができるのである。