菜鳥談デザインモード---観察者モード

13615 ワード

プログラミングの扉に足を踏み入れたばかりで、2つの菜鳥が越えにくい扉を知っています.アルゴリズムと設計モードです.アルゴリズムはどの分野がどのような問題を解決するために使われているのかを見なければならない.複雑な問題ほど、アルゴリズムは自然に複雑になる.設計モデルについては、プログラミング分野における知恵と経験の結晶であり、プログラマーの天性はより簡単に問題を解決したいからだ.残念なことに、この経験は菜鳥が最初から学べるものではなく、RPGゲームの神級装備のように、キャラクター自体にも一定のレベルと能力を持っていなければ使用できない.しかし、すべての大きなものは小さい頃から蓄積され、多くの思考、多くの試み、多くの練習、多くの訓練、レベルは自然に向上します.
オブザーバモードの出発意図は簡単です.オブジェクト間の一対の多依存性を定義します.これにより、オブジェクトの状態が変化すると、すべての依存者が通知を受け取り、自動的に更新されます.
依存はオブジェクト向けプログラミングにおけるオブジェクト間の重要な関係である.オブジェクト依存性が必要ですが、オブジェクト間の依存度を最小限に抑える必要があります.ここではこの話題を詳しく話すのに適していません.この話題に対してデザインモデルがたくさんあるからです.オブザーバーモードの意図は一目瞭然です.複数のオブジェクトが同じオブジェクトの参照を持ち、そのオブジェクトの状態が変化すると、これらのオブジェクトがメッセージを受信し、所有しているオブジェクトの状態を自動的に更新することを望んでいます.これは放送のようなもので、番組を購読しているお客様に番組メッセージを絶えず送信しています.
オブザーバーモードのポイントは放送されるオブジェクトであり,我々はテーマと呼ぶ.トピックは、まずステータスのあるオブジェクトでなければなりません.状態とは,オブジェクト向けプログラミングにおいてカプセル化されたデータを指す.トピックを観察者に放送するには、次の条件が必要です.
      1.観察者がトピックを購読するか、またはトピックの購読をキャンセルすることを許可します.
      2.トピックの変更は、トピックを購読しているすべてのオブザーバーにタイムリーに送信されることを確認する必要があります.
Javaは観察者モードに内蔵されたサポートがあり、もちろん、私たちも自分で観察者モードを実現することができます.
まず簡単な例から話します.
仮に私たちが今軍用ロボットを開発しているとしたら、ガンダムのようです.私たちは今、いろいろな武器に対する反応をテストしたい試験機を持っています.ここのテーマは様々な武器です.私たちはこのようにすることができます.
public class Enemy extends Observable {
    public Enemy() {
    }
    
    //    
    public void attack() {
        setChanged();
        notifyObservers();
    }

    //       
    public void setAttackMethod(Attack attack) {
        this.mAttackNumber = attack.getAttackNumber();
        this.mMethod = attack.attackMethod();
        attack();
    }
    
    //       
    public int getAttackNumber() {
        return mAttackNumber;
    }

    //       
    public String getMethod() {
        return mMethod;
    }

    private int mAttackNumber = 0;;
    private String mMethod;
}

これはいろいろな武器を使うことができる敵で、それぞれの攻撃方式には自分の番号と説明があり、私たちの試験機がこの攻撃方式を識別し、対応するのに便利です.どのテーマもObservableクラスから継承しなければならないのは興味深いものですが、なぜベースクラスから継承することを選んだのでしょうか.できれば、インタフェースを実装することを選択すると、私たちの拡張性が高くなります.特に、私たちのテーマ自体がすでにクラスのサブクラスである場合、これも私たちが観察者モードを実現する必要がある主な原因です.
注目すべきは、attack()にはsetChanged()とnotifyObservers()の2つの重要な方法がある.
私たちが前に提案した条件に戻ります.トピックの変更は、トピックを購読しているすべての観察者にタイムリーに送信されることを確保する必要があります.このような条件を実現するには、2つのステップに分けることができます.
(1)setChanged()によりトピックステータスが変更された.この方法は、観察者たちにいつ通知するかを選択することができます.まず、マーカーとしてブール値を設定することができ、デフォルトはfalseであり、観察者に通知する必要があると判断した場合、マーカーをtrueに設定し、setChanged()を呼び出すことができます.
(2)notifyObservers()はその名の通り,観察者たちに知らせることである.ここで問題があるのは、観察者たちがどのようにこの通知を受信し、変化した状態を得たのかということです.観察者たちは,トピックが直接データをプッシュするか,パラメータ付きnotifyObservers(Object arg)メソッドにデータをオブジェクトとして渡すかの2つの通知を受信する方法がある.いったいどんな方式を採用すればいいのだろうか.実際には、パラメータを持たないnotifyObservers()は、観察者たちが直接トピックにデータを要求する必要があるので、私のコードにはgetterメソッドが2つあり、Robotクラスにも対応する呼び出しがあることがわかります.パラメータ付きであれば、まず説明しなければなりませんが、そのargはRobotのupdate()のargです!argを直接使用することができます(もちろん、対応するデータ型に強制的に変換することが前提です).
私がなぜパラメータを持たない方法を採用したのかというと、私がプッシュするデータが2つあるからです.攻撃番号と攻撃説明です.もちろん、私は辞書を使ってこの問題を解決することができますが、主に私の設計がよくなく、攻撃方式の説明はRobotに完全に置くことができますが、簡単な例として、許してください.しかし、プッシュする必要があるデータが1つ以上になると、パラメータを持たない方法を考えなければなりません.トピックは、対応するgetterメソッドを提供する必要があります.
次は攻撃方法です.
public interface Attack {
    String attackMethod();

    int getAttackNumber();
}


public class RocketAttack implements Attack {
    @Override
    public String attackMethod() {
        return mMethod;
    }

    @Override
    public int getAttackNumber() {
        return 2;
    }

    private String mMethod = "          ";
}


public class FireAttack implements Attack {
    @Override
    public String attackMethod() {
        return mMethod;
    }

    @Override
    public int getAttackNumber() {
        return 0;
    }

    private String mMethod = "         ";
}


public class LightAttack implements Attack {
    @Override
    public String attackMethod() {
        return mMethod;
    }

    @Override
    public int getAttackNumber() {
        return 1;
    }

    private String mMethod = "           ";
}

次は私たちの観察者たちを実現します.
public class Robot implements Observer {
    private Observable mObservable;
    private int number = 0;
    private String mVersion;

    public Robot(Observable observable, String version) {
        this.mObservable = observable;
        this.mVersion = version;
        mObservable.addObserver(this);
    }
    
    //         
    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof Enemy) {
            Enemy enemy = (Enemy) o;
            this.number = enemy.getAttackNumber();
            if (number == 0) {
                System.out.println("  :" + mVersion + "," + enemy.getMethod()
                        + "
" + mVersion + ": " + "
"); } else if (number == 1) { System.out.println(" :" + mVersion + "," + enemy.getMethod() + "
" + mVersion + ": " + "
"); } else if (number == 2) { System.out.println(" :" + mVersion + "," + enemy.getMethod() + "
" + mVersion + ": !" + "
"); } } } }

このクラスのポイントは、トピックの参照を持ち、そのトピックに自分を登録することです.
テストを始めることができます!
public class RobotTest {
    public static void main(String[] args) {
        Attack[] randomAttacks = { new FireAttack(), new LightAttack(),
                new RocketAttack(), };
        Enemy enemy = new Enemy();
        Robot robot1 = new Robot(enemy, "   1 ");
        Robot robot2 = new Robot(enemy, "   2 ");
        int randomNumber = 0;
        for (int i = 0; i < 5; i++) {
            randomNumber = new Random().nextInt(3);
            enemy.setAttackMethod(randomAttacks[randomNumber]);
        }
} }

敵はランダム数に応じてランダムな攻撃を行います.私たちのRobotは異なる攻撃方法によって異なる措置を取らなければなりません.テストの結果は以下の通りです.
(1)敵:ロボット2号、私はロケット砲であなたのロボット2号を攻撃しています:私はすぐに逃げます!
敵:ロボット1号、私はロケット砲であなたのロボット1号を攻撃しています:私はすぐに逃げます!
(2)敵:ロボット2号、火炎で攻撃しているロボット2号:水で防御する
敵:ロボット1号、火炎で攻撃してるロボット1号:水で防御する
(3)敵:ロボット2号、ビームサーベルで攻撃中ロボット2号:光盾で防御する
敵:ロボット1号、ビームサーベルで攻撃してるロボット1号:光盾で防御する
(4)敵:ロボット2号、ビームサーベルで攻撃中ロボット2号:光盾で防御する
敵:ロボット1号、ビームサーベルで攻撃してるロボット1号:光盾で防御する
(5)敵:ロボット2号、私はロケット砲であなたのロボット2号を攻撃しています:私はすぐに逃げます!
敵:ロボット1号、私はロケット砲であなたのロボット1号を攻撃しています:私はすぐに逃げます!
うん、テストの結果は悪くない.このロボットは今前線に派遣して戦うことができるようになった.
最初の条件を覚えていますか.また、オブジェクトのトピック購読のキャンセルを実現する必要があります.このような動作は、1つの方法で実現することができる.
enemy.deleteObserver(robot2);

これでrobot 2のテストをキャンセルできます.他のモードと同様に、オブザーバーモードはオブジェクト間の依存性を低減するのに役立ちます.トピックは、オブザーバーが1つのインタフェース(Observer)を実装していることを知っているだけで、誰であるかを知る必要はありません.上記の例は、Observerインタフェースが実装され、トピックが購読されている限り、他のオブジェクトにも適用できます.また、オブザーバーモードを使用すると、複数のオブジェクトが同時にデータを持つのではなく、1つのトピックでデータを保存する必要があります.
Javaはオブザーバーモードの組み込みサポートに役立ちましたが、その限界も明らかです.setChanged()はprotectedです.これにより、Observableからトピックを継承する必要があります.上記のように,トピックは別のベースクラスのサブクラスである可能性が高いが,この場合どうすればよいのだろうか.メソッドには、Observableインタフェースを自分で実装するか、Observableオブジェクトを1つ所有し、そのオブジェクトに対するメソッドの呼び出しに変換する2つがあります.
どちらが便利なのかは、具体的な使用環境次第です.