純規の暇人趣味ブログ

手を突っ込んで足を洗う

[Bukkitプラグイン制作講座-其之五]コマンド、時々クラス分割

      2016/12/13    HimaJyun

オブジェクト指向って難しいですよね。

使おうと思わなければ継承なんてさっぱりですし、ポリモーフィズム?なんですかそれ?新しいプラスチックか何か?状態です。

継承が分かれば、コマンドのクラス分割も分かるので、今回はこれを紹介してみようと思います。

前回の記事は「[Bukkitプラグイン制作講座-其之四]もっとコマンド!!」です。

今回紹介する記事のコードはGitHubからご覧になれます。

継承ってなぁに?

例えば、次の様なクラスがあるとしましょう。

public class Hoge {}

これを利用するためには次の様にします。

Hoge hoge = new Hoge();

それでは、次の様なクラスがあるとしましょう。

public class Fuga {}

では、次の様な操作は可能でしょうか?

Hoge hoge = new Fuga();

ええ、不可能ですね、何言ってるんだコイツレベルです。

Hoge型宣言しといてFuga型ぶち込むとか大馬鹿者にも程があるってんですよ。

しかし、継承を使うとこれが出来てしまいます。(厳密には出来ると言うのは語弊がありますが)

先程宣言したFugaクラス

public class Fuga {}

これを、以下の様に変更します。

public class Fuga extends Hoge {}

こうする事で、FugaクラスはHogeクラスを受け継いで新たなクラスを宣言している事になります。

すなわち、Fugaクラスは最低限Hogeクラスにあるメソッド(関数)が定義されており、呼び出し可能だとみなされる訳です(実際にFugaクラスに該当のメソッドが定義されていない場合はHogeクラスの物が呼び出されます。)

ですので、以下の操作が可能となります。

Hoge hoge = new Fuga();

と言うより、全てのクラスは暗黙の裡にObjectクラスを継承しています。(なのでObject型には色々ぶち込める訳です)

実際の例

みなさん、ArrayListとかHashMapとか使いますよね?

その時、どうやって宣言していますか?

きっと、大抵の場合は以下の様に宣言しているでしょう。

List<Hoge> list = new ArrayList<Hoge>();

あれ???

List型で宣言したのにArrayList型を代入していますよ?!

これが継承です。

ArrayListクラスではListクラスが継承されています、ですので、List型の変数にArrayList型を入れちゃう事が可能なのです。

コマンドのクラス分割

本文に移る前になが~い説明をしちゃいましたが、ここからが本当の本文です。

ま さ か、メインクラスのonCommand関数にダラダラと処理を書きまくってる奴は居ないですよね?

コマンドはきちんとコマンド用のクラスに分けて処理を行いましょう。

Bukkitにもそれを支援するための機能が用意されています。

メインコマンドのクラス分割

メインコマンドってのは「/hoge fuga」コマンドの「hoge」の部分です。

これはBukkit標準でサポートされているのでとても簡単です。

コマンドを実行するための「Hoge」クラスがあるとしましょう。

public class Hoge {}

これを、次の様に変更します。

public class Hoge implements CommandExecutor {}

きっと、Eclipseなどに「継承された~」とかって言われるはずなので、onCommandメソッドを宣言します。

public class Hoge implements CommandExecutor {
  @Override
  public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    // ここに今まで通りコマンドの処理を書く
    return false;
  }
}

後は、メインクラスのonEnableなどで次の様にして登録してやるだけです。

public class Sample1 extends JavaPlugin {
  @Override
  public void onEnable() {
    //getCommand("コマンド").setExecutor(new クラス());
    getCommand("hoge").setExecutor(new Hoge());
  }
}

とても簡単です。

サブコマンドのクラス分割

さて、ここであなたは「/hoge fuga」の「fuga」の部分を別のクラスに振り分けたいと思いました。

これはBukkitの標準機能ではサポートされていません。

しかし、よく思い出してください、Hogeクラスを継承したFugaクラスはHoge型の変数に入れられるのです。

僕は継承とHashMapを組み合わせてサブコマンドのクラス分割を実現していますのでご紹介致しましょう。

と言っても、やってる事はCommandExecutorと全く同じなのでこれをこのまま利用しましょう。

public class Fuga implements CommandExecutor {
  @Override
  public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    // ここに/hoge fugaコマンドでの処理を書く
    return false;
  }
}

さて、問題は先程のonEnableに書き込んだアレです。

僕はHashMapで似た様な動作が出来る様にしています。

public class Hoge implements CommandExecutor {

	private final Map<String, CommandExecutor> subCommands;
	// 空白を含む事で引数としてあり得ない形にする
	private final String NO_ARGS = "NO ARGS";

	public Hoge() {
		Map<String, CommandExecutor> commands = new HashMap<>();
		// fugaコマンドとしてFugaクラスを実行させる
		commands.put("fuga", new Fuga());
		// 一応変更不可にしておく
		subCommands = Collections.unmodifiableMap(commands);
	}

	@Override
	public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
		return subCommands.get(
				(args.length > 0 && subCommands.containsKey(args[0].toLowerCase(Locale.ENGLISH)))
						? args[0].toLowerCase(Locale.ENGLISH)
						: NO_ARGS)
				.onCommand(sender, command, label, args);
		/*
		// 上の処理はすなわち、以下と同じです(上みたいなやり方は汚いので本当は行けません)
		// デフォルトで呼び出すもの
		String arg = NO_ARGS;
		// 引数があるか
		if(args.length > 0) {
			// 小文字変換
			String arg0 = args[0].toLowerCase(Locale.ENGLISH);
			// 引数のコマンドが用意されているか
			if (subCommands.containsKey(arg0)) {
				// 引数変更
				arg = arg0;
			}
		}
		// 呼び出し
		return subCommands.get(arg).onCommand(sender, command, label, args);
		//*/
	}

}

「ちょっとなにやってんだかわかんないっすね~」、そりゃそうっすよ~

これ、明らかに初心者に説明するための書き方じゃないですからね。

あくまで、我流のサブコマンド分割のやり方を紹介しただけなので、無理してマネしなくても良いでしょう。

なんか関数が呼べなくなった!!

今までメインクラスで実装していたコマンドの処理をそのままクラス分割して上手く行くほど世の中は甘くないです。

(主にgetPlayer()などの)一部の(厳密にはJavaPluginクラスの)関数がそのままでは呼べなくなります。

とは言え、メインクラスのインスタンスがあれば良いので、やってみましょう(と言って伝わる訳がありませんが)

現在の状態は以下の通りだと仮定しましょう。

public class Hoge implements CommandExecutor {
  @Override
  public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    // ここに今まで通りコマンドの処理を書く
    return false;
  }
}
public class Sample1 extends JavaPlugin {
  @Override
  public void onEnable() {
    //getCommand("コマンド").setExecutor(new クラス());
    getCommand("hoge").setExecutor(new Hoge());
  }
}

Hogeクラスをnewする際にSample1(実際は自分の作るプラグインに合わせて下さい)のインスタンスを渡します。

要は、以下の様に変更する訳です。

public class Hoge implements CommandExecutor {

  private Sample1 instance;
  public Hoge(Sample1 instance) {
    this.instance = instance
  }

  @Override
  public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    // ここに今まで通りコマンドの処理を書く
    return false;
  }
}
public class Sample1 extends JavaPlugin {
  @Override
  public void onEnable() {
    //getCommand("コマンド").setExecutor(new クラス());
    getCommand("hoge").setExecutor(new Hoge(this));
  }
}

これで、getPlayerなどが必要な際には「instance.getPlayer("HimaJyun");」等の様にして呼び出す訳です。

これはもう慣れみたいな物なので……慣れですね。

結局、大半のプログラミングは「こうしたい時はこうする」の組み合わせですから。

其之六は「[Bukkitプラグイン制作講座-其之六]特定の出来事(イベント)に反応する」です。

 - プログラミング , ,