技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

javaでのFactory Methodパターン

Factory MethodパターンはTemplate Methodパターンをオブジェクト生成の場面に適用したもので、オブジェクト生成の処理を共通化することができます。
 
今回は、RPGのキャラクター作成を模したサンプルコードを作成してみました。
キャラクターの職業毎にクラスを分けており、クラス毎に微妙にオブジェクト生成時の処理が異なっていますが、各々のクラスのオブジェクト生成処理を共通化することができています。
 
【サンプルコード】
・AbstractJob.java
import java.util.ArrayList;
import java.util.List;

public abstract class AbstractJob {

    protected String playerName;
    protected List<String> skillList = new ArrayList<String>();

    public abstract void setPlayerName(String playerName);
    public abstract String getPlayerName();
    public abstract void setSkill(String skill);
    public abstract List<String> getSkill();

}
 
・Warrior.java
import java.util.List;

public class Warrior extends AbstractJob {

    public void setPlayerName(String playerName) {
        this.playerName = playerName;
    }

    public String getPlayerName() {
        return this.playerName;
    }

    public void setSkill(String skill) {
        this.skillList.add(skill);
    }

    public List<String> getSkill() {
        return this.skillList;
    }

}
 
・NormalPerson.java
import java.util.List;

public class NormalPerson extends AbstractJob {

    public void setPlayerName(String playerName) {
        this.playerName = playerName;
    }

    public String getPlayerName() {
        return this.playerName;
    }

    public void setSkill(String skill) {
        this.skillList.add(skill);
    }

    public List<String> getSkill() {
        return this.skillList;
    }

}
 
・AbstractJobFactory.java
public abstract class AbstractJobFactory {

    public final AbstractJob create(String playerName) {
        AbstractJob abstractJob = createJob(playerName);
        return abstractJob;
    }

    protected abstract AbstractJob createJob(String playerName);

}
 
・WarriorFactory.java
public class WarriorFactory extends AbstractJobFactory {

    protected AbstractJob createJob(String playerName) {
        Warrior warrior = new Warrior();
        warrior.setPlayerName(playerName);
        warrior.setSkill("QuickSlash");
        return warrior;
    }

}
 
・NormalPersonFactory.java
public class NormalPersonFactory extends AbstractJobFactory {

    protected AbstractJob createJob(String playerName) {
        NormalPerson normalPerson = new NormalPerson();
        normalPerson.setPlayerName(playerName);
        return normalPerson;
    }

}
 
・CreateJobMain.java
import java.util.Iterator;

public class CreateJobMain {
    public static void main(String[] args){

        // Factoryメソッドの定義
        WarriorFactory warriorFactory = new WarriorFactory();
        NormalPersonFactory normalPersonFactory = new NormalPersonFactory();

        // Factoryメソッドによりオブジェクトを生成
        AbstractJob warrior = warriorFactory.create("Player1");
        AbstractJob normalPerson = normalPersonFactory.create("Player2");

        // 生成されたオブジェクトの内容を確認
        displayJob(warrior);
        displayJob(normalPerson);

    }

    private static void displayJob(AbstractJob player) {
        System.out.println("[プレイヤー:"+player.getPlayerName()+"]");
        System.out.println("・職業");
        System.out.println(" "+player.getClass().getName());
        System.out.println("・スキル");
        Iterator<String> iterator = player.getSkill().iterator();
        if (!iterator.hasNext()) {
            System.out.println(" None");
        } else {
            while (iterator.hasNext()) {
                String str = iterator.next();
                System.out.println(" "+str);
            }
        }
    }

}
 
【実行結果】
[プレイヤー:Player1]
・職業
 Warrior
・スキル
 QuickSlash
[プレイヤー:Player2]
・職業
 NormalPerson
・スキル
 None

javaでのTemplate Methodパターン

Template Methodパターンは、処理(メソッド)の中で共通している箇所を抽象クラスとして抜き出すことで、重複した記述を排除し、更に処理の流れも規定する手法のことを指します。
手続き型言語でも共通する箇所を子メソッドとして抜き出すことをしますが、子メソッドとして抜き出してしまうとその子メソッドの使い方までは規定できず、子メソッドを誤った使い方をされてしまう可能性が出てきてしまいます。
 
サンプルコードとして、RPGのダメージ計算を模したプログラムを作成してみました。
「ベースのダメージ量に対してランダム化を行い最終的なダメージ量とする」という処理の流れを規定できていることがポイントです。
 
【サンプルコード】
・AbstractDamageCalc.java
import java.util.Random;

public abstract class AbstractDamageCalc {

    public abstract int getDamageBase();

    public final int damageCalc() {
        // ベースのダメージ量に7/8、8/8、9/8の何れかの値をかけてランダム化する
        // 単に共通処理をまとめるだけでなく、共通処理の使い方も指定できる
        // (ランダム化忘れ、ランダム化を2重でする、等を未然に防げる)
        return getDamageBase() * (new Random().nextInt(3) + 7) / 8;
    }
}
 
・PhysicsDamageCalc.java
public final class PhysicsDamageCalc extends AbstractDamageCalc {

    private int baseDamage = 0;

    public PhysicsDamageCalc(int physicsPower) {
        baseDamage = physicsPower / 2; //攻撃力の半分がダメージ
    }

    public int getDamageBase() {
        return baseDamage;
    }

}
 
・MagicDamageCalc.java
public final class MagicDamageCalc extends AbstractDamageCalc {

    private int baseDamage = 0;

    public MagicDamageCalc(int magicID) {
        if (magicID == 0) { //魔法「FireL1」
            baseDamage = 15;
        } else if (magicID == 1) { //魔法「FireL2」
            baseDamage = 60;
        } else if (magicID == 2) { //魔法「FireL3」
            baseDamage = 120;
        } else { //例外処理
            System.out.println("指定された魔法IDは未実装です");
            System.exit(1);
        }
    }

    public int getDamageBase() {
        return baseDamage;
    }

}
 
・DamageCalcMain.java
public class DamageCalcMain {
    public static void main(String[] args){

        int physicsPower = -1;
        int magicID = -1;
        int damage = 0;

        // 引数から物理攻撃力、魔法IDを取得
        if (args.length == 2) {
            physicsPower = Integer.parseInt(args[0]);
            magicID = Integer.parseInt(args[1]);
        }

        // ダメージ計算
        if (physicsPower >= 0 || magicID == -1) { //物理攻撃
            damage = new PhysicsDamageCalc(physicsPower)
                              .damageCalc();
        } else if (physicsPower == -1 || magicID >= 0) { //魔法攻撃
            damage = new MagicDamageCalc(magicID)
                              .damageCalc();
        } else { //例外処理
            System.out.println
                ("第1引数:物理攻撃力、魔法攻撃の場合は-1にしてください");
            System.out.println
                ("第2引数:魔法ID、物理攻撃の場合は-1にしてください");
            System.exit(1);
        }

        System.out.println("ダメージは"+damage);
        System.exit(0);

    }
}
 
【実行結果】 ※第1引数に-1、第2引数に1を指定した場合
ダメージは52

javaでのFacadeパターン

Facadeクラスとは、使い方が複雑になっているクラス群をまとめ、使いやすい形のインターフェースとして外部に提供するクラスのことを指します。
デザインパターンでは、このFacadeクラスを利用するパターンをFacadeパターンと呼びます。
(ちなみに、Facadeは「窓口」を意味する単語です)
 
サンプルコードとして、RPGのダメージ計算を模したプログラムを作成してみました。
RPGのダメージ計算は複雑で、そのRPGに詳しくない人が正しく計算式を実装するのは困難なのですが、Facadeクラスを用いることにより、Facadeクラスの利用者は詳しい知識を持たずとも正しく計算式を実装することが可能となります。
 
【サンプルコード】
・PhysicsBaseDamageCalc.java
public final class PhysicsBaseDamageCalc {

    private int baseDamage = 0;

    public PhysicsBaseDamageCalc(int physicsPower) {
        baseDamage = physicsPower / 2; //攻撃力の半分がダメージ
    }

    public int getBaseDamage() {
        return baseDamage;
    }

}
 
・MagicBaseDamageCalc.java
public final class MagicBaseDamageCalc {

    private int baseDamage = 0;

    public MagicBaseDamageCalc(int magicID) {
        if (magicID == 0) { //魔法「FireL1」
            baseDamage = 15;
        } else if (magicID == 1) { //魔法「FireL2」
            baseDamage = 60;
        } else if (magicID == 2) { //魔法「FireL3」
            baseDamage = 120;
        } else { //例外処理
            System.out.println("指定された魔法IDは未実装です");
            System.exit(1);
        }
    }

    public int getBaseDamage() {
        return baseDamage;
    }

}
 
・DamageRandomize.java
import java.util.Random;

public final class DamageRandomize {

    private int damage = 0;

    public DamageRandomize(int baseDamage) {
        // 7/8、8/8、9/8の何れかの値をかけてランダム化
        damage = baseDamage * (new Random().nextInt(3) + 7) / 8;
    }

    public int getDamage() {
        return damage;
    }
}
 
・DamageCalcFacade.java
public final class DamageCalcFacade {

    private int damage = 0;

    public DamageCalcFacade (int physicsPower,int magicID) {
        int baseDamage = 0;
        if (physicsPower >= 0 || magicID == -1) { //物理攻撃
            baseDamage = new PhysicsBaseDamageCalc(physicsPower)
                              .getBaseDamage();
        } else if (physicsPower == -1 || magicID >= 0) { //魔法攻撃
            baseDamage = new MagicBaseDamageCalc(magicID)
                              .getBaseDamage();
        } else { //例外処理
            System.out.println("不正なダメージ計算です");
            System.exit(1);
        }
        damage = new DamageRandomize(baseDamage).getDamage();
    }

    public int getDamage() {
        return damage;
    }
}
 
・DamageCalcMain.java
public class DamageCalcMain {
    public static void main(String[] args){

        int physicsPower = -1;
        int magicID = -1;
        int damage = 0;

        // 引数から物理攻撃力、魔法IDを取得
        if (args.length == 2) {
            physicsPower = Integer.parseInt(args[0]);
            magicID = Integer.parseInt(args[1]);
        }

        // ダメージ計算→計算式を知らないとこのように実装できない
        /*
        int baseDamage = 0;
        if (physicsPower >= 0 || magicID == -1) { //物理攻撃
            baseDamage = new PhysicsBaseDamageCalc(physicsPower)
                              .getBaseDamage();
        } else if (physicsPower == -1 || magicID >= 0) { //魔法攻撃
            baseDamage = new MagicBaseDamageCalc(magicID)
                              .getBaseDamage();
        } else { //例外処理
            System.out.println("不正なダメージ計算です");
            System.exit(1);
        }
        damage = new DamageRandomize(baseDamage).getDamage();
        */

        // 利用者側で複雑な計算式を考えたくないので、Facadeクラスに任せる
        damage = new DamageCalcFacade(physicsPower,magicID).getDamage();

        System.out.println("ダメージは"+damage);
        System.exit(0);

    }
}
 
【実行結果】 ※第1引数に-1、第2引数に1を指定した場合
ダメージは52

Excelで文字の一括置換を行う方法(テキストエディタ利用)

Excelファイルは複数のxmlファイルを圧縮して構成されています。
解凍することでxmlファイルの形式として開けるようになり、通常のテキストエディタで編集することが可能になります。
これを利用することで、全てのシートに対して、オブジェクト内の文字も含めた文字の一括置換が可能になります。
Excelの機能ではオブジェクト内の文字を検索したり置換したりすることができません)
 
以下、手順です。
 
1.Excel形式のファイルを用意する
f:id:akira2kun:20191205225428j:plain
f:id:akira2kun:20191205225439j:plain

 
2.拡張子を「.xlsx」から「.zip」へ変更する

f:id:akira2kun:20191205225915j:plain
 
3.2のファイルをLhaplus等で解凍する
f:id:akira2kun:20191205230138j:plain
 
4.サクラエディタgrep置換等の機能で文字の一括置換をかける
f:id:akira2kun:20191205230152j:plain

※本文中の文字は xl\sharedStrings.xml に定義されます。
※図形中の文字は xl\drawings\drawing*.xml に定義されます。
※drawing*.xml に関しては、半角文字と全角文字が入れ替わる箇所で、
 xmlファイル上で別々の定義になるので注意が必要です。
 例えば、「hogeほげ」という文字については、
 xmlファイル上で「hoge」と「ほげ」という文字に分割されます。
 
5.Lhaplus等で再度圧縮する
f:id:akira2kun:20191205230202j:plain

※1のファイル名のフォルダの直下で作業を行う必要があることに注意
 
6.圧縮されたファイルについて、拡張子を「.zip」から「.xlsx」へ変更する
f:id:akira2kun:20191205230213j:plain

 
7.6のファイルを開いて置換されたことを確認
f:id:akira2kun:20191205230225j:plain

f:id:akira2kun:20191205230236j:plain
※「~の一部の内容に問題が見つかりました。可能な限り内容を回復しますか?」と聞かれるかもしれませんが、「はい」で良いです。
 
----------------
 
なお、7-Zipを用いれば、解凍や圧縮をコマンドラインから行うことが可能になります。
また、サクラエディタにはコマンドラインからマクロを読みこませる機能もあります。
これらの機能を組み合わせれば、バッチファイルから複数のExcelファイルに対して一括置換を行うことも可能になります。

javaでのスレッド制御(joinとsynchronized)

javaでは、スレッドを立てて処理を並列に行うことができます。
しかし、並列に処理を行う際、処理順を制御しなければならないことがあります。
 
処理順の制御方法として基本的な方法として、joinを使う方法とsynchronizedを使う方法があります。
joinを使うことで、スレッドが終わるのを待つことができます。
また、synchronizedを使うことで、複数のスレッドが同じ処理を同時に行わないように制御することができます。
 
以下は、joinやsynchronizedを使って制御を行うサンプルコードです。
1つのスレッドで5回の処理を行うサンプルであり、そのスレッドを2つ立てます。
また、スレッド間で同一のオブジェクト(staticなオブジェクト)を共有しており、そのオブジェクトでは合計何回処理が行われたのかをカウントしています。
 
制御を行わない場合は、どちらのスレッドが先に処理するのかが定まりません。また、共有オブジェクトでのカウントを2つのスレッドで同時に行うためにカウントも上手く行きません。
joinによる制御を行う場合は、スレッド1の5回の処理が終わってからスレッド2の処理に移る、という制御が可能になります。
synchronizedによる制御を行う場合は、スレッド1とスレッド2が同時に動いても、共有オブジェクトでのカウントは同時には行われず、上手くカウントすることができます。
 
【サンプルコード】
・ThreadMain.java
public class ThreadMain {
    public static void main(String[] args) {

        // スレッドのインスタンスを作成
        ThreadBody test1 = new ThreadBody("スレッド1");
        ThreadBody test2 = new ThreadBody("スレッド2");

        // スレッド1の実行
        test1.start();

        // ①スレッド1が終了するまで待機
        /*
        try {
            test1.join();
        } catch (InterruptedException e) {
            // 例外処理
            e.printStackTrace();
        }
        */

        // スレッド2の実行
        test2.start();
    }
}
 
・ThreadBody.java
public class ThreadBody extends Thread{

    private String threadName = null;
    private static ThreadCounter threadCounter = new ThreadCounter();

    ThreadBody(String threadName){
        this.threadName = threadName;
    }

    public void run(){
        for (int i = 0; i < 5; i++) {
                threadCounter.countUp();
                System.out.println(threadName+"の"+(i+1)+"回目の処理");
        }
    }
}
 
・ThreadCounter.java
public class ThreadCounter {

    private int count;

    public void countUp() {
        // ②複数スレッドで同時実行されないよう同期を取る
        // synchronized (this) {
            // 1000ミリ秒スリープ
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count++;
            System.out.println("処理回数:"+count);
        // }
    }
}
 
【実行結果】
・そのまま実行した場合
処理回数:2
処理回数:2
スレッド1の1回目の処理
スレッド2の1回目の処理
処理回数:3
処理回数:4
スレッド1の2回目の処理
スレッド2の2回目の処理
処理回数:6
処理回数:6
スレッド2の3回目の処理
スレッド1の3回目の処理
処理回数:8
処理回数:8
スレッド2の4回目の処理
スレッド1の4回目の処理
処理回数:10
処理回数:10
スレッド2の5回目の処理
スレッド1の5回目の処理
 
・①のコメントアウトを外した場合(join)
処理回数:1
スレッド1の1回目の処理
処理回数:2
スレッド1の2回目の処理
処理回数:3
スレッド1の3回目の処理
処理回数:4
スレッド1の4回目の処理
処理回数:5
スレッド1の5回目の処理
処理回数:6
スレッド2の1回目の処理
処理回数:7
スレッド2の2回目の処理
処理回数:8
スレッド2の3回目の処理
処理回数:9
スレッド2の4回目の処理
処理回数:10
スレッド2の5回目の処理
 
・②のコメントアウトを外した場合(synchronized)
処理回数:1
スレッド1の1回目の処理
処理回数:2
スレッド2の1回目の処理
処理回数:3
スレッド1の2回目の処理
処理回数:4
スレッド2の2回目の処理
処理回数:5
スレッド1の3回目の処理
処理回数:6
スレッド2の3回目の処理
処理回数:7
スレッド1の4回目の処理
処理回数:8
スレッド2の4回目の処理
処理回数:9
スレッド1の5回目の処理
処理回数:10
スレッド2の5回目の処理

javaでのソケット通信の記事引用

javaでは、C言語と同じようにIPアドレスとポート番号を指定してのサーバ間ソケット通信が可能です。
既に解説やサンプルプログラムが充実していたので、当記事では引用のみ行います。
 
・解説(図解)
ストリーム型通信

http://ecogis.sfc.keio.ac.jp/online/java/2002/12th/socket.htm

 
・サンプルプログラム
Java ソケット通信のサンプル  ITSakura

https://itsakura.com/java-socket

プロポーショナルフォントと等幅フォントの違いと使い分け

コンピュータの文字表示で用いられるフォントは、大きく分けて「プロポーショナルフォント」と「等幅フォント」の2つに分けられます。
 
プロポーショナルフォント
 文字毎に幅が異なるフォント。
 「MS Pゴシック」が例として挙げられる。

等幅フォント
 文字毎に幅の違いが無いフォント。
 「MS ゴシック」が例として挙げられる。
 
------------------
 
見栄えが良いのは「プロポーショナルフォント」の方です。
プレゼン資料等、見栄えで勝負するような場合はこちらのフォントを使うのがお勧めです。
 
しかし、テスト結果のまとめ等、システム開発の資料で用いるのであれば、「等幅フォント」の方がお勧めです。
等幅フォント」でないと、見た目でバイト位置を判断できなくなってしまい、見辛い資料になってしまいます。
(最悪の場合、バグを見落とす原因にもなるかもしれません)
 
実際に「プロポーショナルフォント」と「等幅フォント」を見比べたものがこちらです。
プロポーショナルフォント」だと"hoge""fuga""piyo"が全て異なる幅で表示され、同じ4バイトの文字なのにずれて表示されてしまっているのに対し、「等幅フォント」だとそのようなずれが発生しません。
 
プロポーショナルフォント
f:id:akira2kun:20191124174722j:plain

等幅フォント
f:id:akira2kun:20191124174733j:plain