English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Androidプログラミングデザインパターンの状態パターンの詳細な説明

本文はAndroidプログラミングデザインパターンの状態パターンについての例を説明します。皆さんに参考にしていただくために共有します。以下の通りです:

1. 介绍

状態パターンにおける動作は状態によって決定され、異なる状態に異なる動作があります。状態パターンと戦略パターンの構造はほぼ完全に同じですが、目的や本質はまったく異なります。状態パターンの動作は並行で不可替りですが、戦略パターンの動作は独立してお互いに替えができます。一言で言えば、状態パターンはオブジェクトの動作を異なる状態オブジェクトにパッケージ化し、それぞれの状態オブジェクトには共通の抽象状態基底クラスがあります。状態パターンの意図は、オブジェクトの内部状態が変わったとき、その動作も変わるようにすることです。

二、定義

オブジェクトの内在的な状態が変更されると、その動作を変更することが許可され、そのオブジェクトはそのクラスを変更したように見えます。

三、使用シーン

(1) オブジェクトの動作はその状態に依存し、実行時にその状態に基づいて動作を変更する必要があります。
(2)コードには、オブジェクトの状態に関連する多くの条件文が含まれています。例えば、ある操作には大きな多分岐文(if-elseまたはswitch-case)であり、これらの分岐はオブジェクトの状態に依存しています。

状態パターンは、各条件分岐を独立したクラスに配置し、これにより、オブジェクトの状態をオブジェクトとして扱うことができます。このオブジェクトは他のオブジェクトに依存せずに独立して変化することができ、これにより多態性を通じて過度の、重複するifを排除します。-elseなどの分岐文。

四、状態パターンのUMLクラス図

UMLクラス図:

役割紹介:

Context:環境クラス、顧客が興味を持つインターフェースを定義し、Stateサブクラスのインスタンスを維持し、このインスタンスがオブジェクトの現在の状態を定義します。

State:抽象的な状態クラスまたは状態インターフェース、あるいは複数のインターフェースを定義し、その状態における動作を表します。

ConcreteStateA、ConcreteStateB:具体的な状態クラス、各具体的な状態クラスは抽象のStateインターフェースで定義されたインターフェースを実装し、異なる状態における異なる動作を達成します。

五、簡単な例

以下では、テレビリモコンを例にして、状態パターンの実装を説明します。まず、テレビの状態を電源オン状態と電源オフ状態に簡単に分類し、電源オン状態ではリモコンでチャンネル切り替えや音量調整などの操作ができますが、この時電源オンボタンを繰り返し押しても無効です;また、電源オフ状態ではチャンネル切り替えや音量調整、電源オフは無効な操作で、電源オンボタンを押すことでのみ有効になります。つまり、テレビの内部状態がリモコンの動作を決定しています。

まずは一般的な実装方法を紹介します:

public class TVController {
  //電源オン状態
  private final static int POWER_ON = 1;
  //電源オフ状態
  private final static int POWER_OFF = 2;
  //デフォルト状態
  private int mState = POWER_OFF;
  public void powerOn(){
    if(mState == POWER_OFF){
      System.out.println("テレビが電源オンになりました");
    }
    mState = POWER_ON;
  }
  public void powerOff(){
    if(mState ==POWER_ON){
      System.out.println("テレビが電源オフになりました");
    }
    mState = POWER_OFF;
  }
  public void nextChannel(){
    if(mState ==POWER_ON){
      System.out.println("次のチャンネル");
    }
      System.out.println("電源がオンではありません");
    }
  }
  public void prevChannel(){
    if(mState ==POWER_ON){
      System.out.println("前のチャンネル");
    }
      System.out.println("電源がオンではありません");
    }
  }
  public void turnUp(){
    if(mState ==POWER_ON){
      System.out.println("音量を上げます");
    }
      System.out.println("電源がオンではありません");
    }
  }
  public void turnDown(){
    if(mState ==POWER_ON){
      System.out.println("音量を下げます");
    }
      System.out.println("電源がオンではありません");
    }
  }
}

可以看出,每次执行都会通过判断当前状态来进行操作,部分代码重复。假设状态和功能增加,就会越来越难以维护。这时可以使用状态模式,如下:

テレビの状態インターフェース:

/**
 * テレビの状態インターフェース、テレビの操作関数を定義
 *
 **/
public interface TVState {
  public void nextChannel();
  public void prevChannel();
  public void turnUp();
  public void turnDown();
}

電源オフ状態:

/**
 *
 * 電源オフ状態、操作結果なし
 *
 * */
public class PowerOffState implements TVState{
  @Override
  public void nextChannel() {
  }
  @Override
  public void prevChannel() {
  }
  @Override
  public void turnUp() {
  }
  @Override
  public void turnDown() {
  }
}

電源オン状態:

/**
 *
 * 電源オン状態、操作有効
 *
 * */
public class PowerOnState implements TVState{
  @Override
  public void nextChannel() {
    System.out.println("次のチャンネル");
  }
  @Override
  public void prevChannel() {
    System.out.println("前のチャンネル");
  }
  @Override
  public void turnUp() {
    System.out.println("音量を上げます");
  }
  @Override
  public void turnDown() {
    System.out.println("音量を下げます");
  }
}

電源操作インターフェース:

/**
 * 電源操作インターフェース
 *
 * */
public interface PowerController {
  public void powerOn();
  public void powerOff();
}

テレビリモコン:

/**
 * テレビリモコン
 *
 * */
public class TVController implements PowerController{
  TVState mTVState;
  public void setTVState(TVState mTVState){
    this.mTVState = mTVState;
  }
  @Override
  public void powerOn() {
    setTVState(new PowerOnState());
    System.out.println("電源を入れました");
  }
  @Override
  public void powerOff() {
    setTVState(new PowerOffState());
    System.out.println("電源を切りました");
  }
  public void nextChannel(){
    mTVState.nextChannel();
  }
  public void prevChannel(){
    mTVState.prevChannel();
  }
  public void turnUp(){
    mTVState.turnUp();
  }
  public void turnDown(){
    mTVState.turnDown();
  }
}

呼び出し:

public class Client {
  public static void main(String[] args) {
    TVController tvController = new TVController();
    //電源オン状態を設定します
    tvController.powerOn();
    //次のチャンネル
    tvController.nextChannel();
    //音量を上げます
    tvController.turnUp();
    //電源を切ります
    tvController.powerOff();
    //音量を下げますが、これでは無効です
    tvController.turnDown();
  }
}

出力結果は以下の通りです:

電源を入れました
次のチャンネル
音量を上げます
電源を切りました

この実現では、TVStateインターフェースを抽象化し、テレビの操作をすべて含むこのインターフェースには、電源オン状態(PowerOnState)と電源オフ状態(PowerOffState)という2つの実装クラスがあります。電源オン状態では、電源オン機能のみが無効であり、すなわち既に電源がオンの状態で電源オンボタンを押しても何も反応しません;一方、電源オフ状態では、電源オン機能のみが利用可能で、他の機能はすべて無効です。同じ操作、例えば音量を上げるturnUp関数は、電源オフ状態では無効ですが、電源オン状態ではテレビの音量を上げます。つまり、テレビの内部状態がテレビリモコンの動作に影響を与えています。状態パターンはこれらの行動を状態クラスに封装し、操作を行う際にこれらの機能を状態オブジェクトに転送します。異なる状態には異なる実装があり、これにより多態性を通じて重複や混乱を排除します。-else文、これは状態パターンの核となる部分です。

六、Androidにおける実際の使用

1、ログインシステム、ユーザーがログインしているかどうかによって、イベントの処理方法を判断します。
2、Wi-Fi管理では、異なる状態でWiFiのスキャンリクエストの処理が異なります。

以下では、ログインシステムを例にして、状態パターンの実際の使用方法を説明します:

Android開発では、ログイン画面は非常に一般的であり、状態設計パターンはログイン画面での応用が非常に広範です。ユーザーがログインしている状態とログインしていない状態で、逻辑操作が異なる場合があります。例えば、最も一般的なケースとして、新浪微博を遊ぶ場合、ユーザーがログインしている状態でしかコメントやリツイートの操作を行うことができます;ユーザーがログインしていない状態でリツイートやコメントの操作を行う場合は、ログイン画面に移動してログインを行った後に操作を行う必要があります。したがって、この2つの異なる状況に対処するためには、状態設計パターンを使用してこの例を設計するのが最適です。

1、状態基底クラス

前回説明した状態設計パターンの原理は多態性であり、ここではUserStateインターフェースを使用してこの基底クラスを表現し、リツイートやコメントの操作を含む状態を含む、以下のようになります:

public interface UserState {
  /**
   * リダイレクト操作
   * @param context
   */
  public void forword(Context context);
  /**
   * コメント操作
   * @param context
   */
  public void commit(Context context);
}

2、ユーザーのログインしていない状態とログインしている状態の実装クラスLoginStateとLogoutState;以下のようになります:

LoginState.javaの中で、ユーザーはリツイートやコメントの操作ができます。

public class LoginState implements UserState{
  @Override
  public void forword(Context context) {
    Toast.makeText(context, "リツイート成功", Toast.LENGTH_SHORT).show();
  }
  @Override
  public void commit(Context context) {
    Toast.makeText(context, "コメント成功", Toast.LENGTH_SHORT).show();
  }
}

LogoutState.javaの中で、ユーザーがログインしていない状態では操作を行わせることができず、ログイン画面に遷移してログインを行った後に操作を行う必要があります。

public class LogoutState implements UserState{
  /**
   * 転送を行うには、ログインするまでにログイン画面に遷移する必要があります
   */
  @Override
  public void forword(Context context) {
    gotoLohinActivity(context);
  }
  /**
   * コメントを書き込むには、ログインするまでにログイン画面に遷移する必要があります
   */
  @Override
  public void commit(Context context) {
    gotoLohinActivity(context);
  }
  /**
   * 画面遷移操作
   * @param context
   */
  private void gotoLohinActivity(Context context){
    context.startActivity(new Intent(context, LoginActivity.class));
  }
}

3、操作役割LoginContext

ここでのLoginContextは、状態パターンのContext役割であり、ユーザーの操作オブジェクトおよび管理オブジェクトです。LoginContextは、状態の変更に応じて、状態オブジェクトに操作を委譲し、その中で状態が変更されると、LoginContextの行動も変更されます。LoginContextのコードは以下の通りです:*以下:

注意事項:

ここでは、LoginContextがユーザーの状態を一つだけ管理するために単例パターンを使用しています;

public class LoginContext {
  //ユーザーの状態はデフォルトで未ログイン状態です
  UserState state = new LogoutState();
  private LoginContext(){};//プライベートなコンストラクタ、外部からnewを使用してオブジェクトを取得することを避けるために
  //単例パターン
  public static LoginContext getInstance(){
   return SingletonHolder.instance;
  }
  /**
  *静的コードブロック
  */
  private static class SingletonHolder{
   private static final LoginContext instance = new LoginContext();
  }
  public void setState(UserState state){
    this.state = state;
  }
  //転送
  public void forward(Context context){
    state.forward(context);
  }
  //コメント
  public void commit(Context context){
    state.commit(context);
  }
}

4、画面表示

LoginActivity.java、この画面ではログイン操作を実行し、ログイン成功後、LoginContext.getInstance().setState(new LoginState());をログイン状態に設定します。MainActivityではログイン状態の操作が実行されます。つまり、転送やコメントの投稿ができます;

public class LoginActivity extends Activity implements OnClickListener{
  private static final String LOGIN_URL = "http://10.10.200.193:8080/Day01/servlet/LoginServlet";
  private EditText et_username;
  private EditText et_password;
  private Button btn_login;
  private String username;
  private String password;
  private KJHttp http;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_login);
    initView();
    initData();
  }
  private void initView() {
    et_username = (EditText) findViewById(R.id.et_username);
    et_password = (EditText) findViewById(R.id.et_password);
    btn_login = (Button) findViewById(R.id.btn_login);
    btn_login.setOnClickListener(LoginActivity.this);
  }
  private void initData() {
    http = new KJHttp();
  }
  /**
   * ログイン操作を実行
   *
   * @param username2
   * @param password2
   */
  protected void sendLogin(String username2, String password}}2) {
    HttpParams params = new HttpParams();
    params.put("username", "user1");
    params.put("password", "123456");
    http.post(LOGIN_URL, params, new HttpCallBack() {
      @Override
      public void onSuccess(String t) {
        if ("2"00".equals(t)) {
          //ログイン状態に設定
          LoginContext.getInstance().setState(new LoginState());
          startActivity(new Intent(LoginActivity.this,MainActivity.class));
          finish();
          Toast.makeText(LoginActivity.this, "ログイン成功", Toast.LENGTH_SHORT).show();
        }
      }
    });
  }
  @Override
  public void onClick(View v) {
    switch (v.getId()) {
    case R.id.btn_login:
      username = et_username.getEditableText().toString().trim();
      password = et_password.getEditableText().toString().trim();
      if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
        Toast.makeText(LoginActivity.this, "ユーザー名またはパスワードが空です", Toast.LENGTH_SHORT).show();
        return;
      }
      sendLogin(username, password);
      break;
    }
  }
}

MainActivity.java、ユーザーがログイン成功した後、シェアやコメントをクリックしたときは、ログイン状態での操作を実行します。ユーザーがログアウトした場合、LoginContextの状態を未ログイン状態に設定します;LoginContext.getInstance().setState(new LogoutState());この場合、シェアやコメント操作をクリックしたときはユーザーログイン画面に遷移します。

public class MainActivity extends Activity {
  private Button btn_forward;
  private Button btn_commit;
  private Button btn_logout;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initView();
    initListener();
  }
  private void initView() {
    btn_forward = (Button) findViewById(R.id.btn_forward);
    btn_commit = (Button) findViewById(R.id.btn_commit);
    btn_logout = (Button) findViewById(R.id.btn_logout);
  }
  private void initListener() {
    //リダイレクト操作
    btn_forward.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        //LoginContext内のリダイレクト関数を呼び出します
        LoginContext.getInstance().forward(MainActivity.this);
      }
    });
    //コメント操作
    btn_commit.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        //LoginContext内のリダイレクト関数を呼び出します
        LoginContext.getInstance().commit(MainActivity.this);
      }
    });
    //ログアウト操作
    btn_logout.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        //ログアウト状態に設定します。
        LoginContext.getInstance().setState(new LogoutState());
      }
    });
  }
}

七、まとめ

状態パターンの鍵は、同じ行動に対して異なる状態で異なる応答を行うことです。これは、ifやelseを一つの状態にまとめることです。-多態を通じて実現されたelseの具体的な例です。-elseを使用する-caseの形式で異なる状態に応じて判定を行い、状態Aの場合は方法Aを実行し、状態Bの場合は方法Bを実行しますが、この実装はロジックが連結されやすく、エラーが発生しやすくなります。状態パターンを使用することで、このような「見た目が悪い」ロジック処理を効果的に排除できます。もちろん、ifやelseやswitchがどのように使用されるかに関わらず、-elseの部分はすべて状態パターンで再構築すべきであり、パターンの使用は、その状況と解決したい問題を考慮する必要があります。特定のシナリオに適している場合のみ、対応するパターンを使用することをお勧めします。

利点:

特定の状態に関連するすべての行動を一つの状態オブジェクトにまとめ、特定の状態に関連するコードをより良い方法で構成し、複雑な状態判定を構造的な状態クラス族に変換します。これにより、コードの膨張を避けながら拡張性と保守性を確保します。

欠点:

状態パターンの使用は、システムクラスとオブジェクトの数を増加させることになります。

Androidに関するさらに詳しい内容に興味を持つ読者は、このサイトの特集を確認してください:《Android開発入門と進階教程》、《Androidデバッグ技術とよくある問題の解決方法集》、《Android基本コンポーネントの使用法総説》、《AndroidビューViewの技術的まとめ》、《Androidレイアウトlayoutの技術的まとめ》および《Androidコントロールの使用法総説》

この記事で述べたことが皆様のAndroidプログラムデザインに役立つことを願っています。

声明:この記事の内容はインターネットからネットワークで提供されています。著作権はそれぞれの所有者に帰属します。コンテンツはインターネットユーザーによって自発的に貢献し、アップロードされました。このウェブサイトは所有権を持ちません。人工的な編集は行われていません。また、関連する法的責任を負いません。著作権侵害を疑う内容が見つかった場合は、以下のメールアドレスまでご連絡ください:notice#oldtoolbag.com(メールを送信する際には、#を@に変更してください。侵害を報告し、関連する証拠を提供してください。一旦確認されると、このサイトは即座に侵害された内容を削除します。)

おすすめ