1, Application scenario and case description
We often play games in our spare time to relax and relieve pressure. In many games, heroes can make different actions to face the enemy according to different physical strength.
Heroes can perform rest, defense, ordinary attack and skill attack. The physical strength consumed or recovered is different, for example:
- Rest: recover the physical strength to the maximum, and there is no need to rest when the physical strength is the maximum;
- Defense: consume 10 points of physical strength. You can't defend without physical strength;
- Ordinary attack: consume 15 points of physical strength. You can't attack without physical strength;
- Skill attack: consume 20 points of physical strength. Skill attack cannot be carried out without physical strength;
2, Case analysis and problem solving
If we write these operation logic in the same class - Hero class as the method of hero, the following problems will occur:
- In the later stage, if the operation logic (such as the change of consumed physical strength value) changes, the whole hero class needs to be modified;
- In the later stage, if you want to add operations (such as adding magic attacks), you need to modify the whole hero class;
- Only a lot of if else can be stacked. The code is hard to maintain and has poor readability;
So I decided to use state mode to solve this problem. Advantages of status mode in this scenario:
- Using a class to encapsulate a state (operation) of an object, it is easy to add a new state (operation);
- In the state mode, there is no need for a large number of conditional judgment statements in the Context - Hero class. The state presented by the Context instance becomes clearer and easier to understand;
- Using the state mode allows the user program to easily switch the state (operation) of the Context - Hero instance;
- Using the state mode will not make the internal state inconsistent in the instance of Context Hero;
- When the state object has no instance variable, each instance of Context Hero class can share a state object;
I abstract the hero's operations into states. The four operations correspond to four different specific states:
- RestState
- Defense state
- NormalAttackState
- SkillAttackState
3, Each role description and UML diagram
Role description:
- Context: Hero class
- Abstract State: State abstract class
- Concrete State: RestState, DefenseState, NormalAttackState and SkillAttackState classes
UML diagram:
IV Operation effect
- Generated apk
- Icon after installation
-
Operation results
-
Take a break:
-
Defense:
-
Common attacks:
-
Skill attack:
-
5, Program complete source code
1. Environment (CONTEXT)
In this design, the environmental role is Hero class, and the code is as follows:
Hero.java
package com.yuebanquan.statepattern; import android.content.Context; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.Gravity; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import pl.droidsonroids.gif.GifImageView; public class Hero extends AppCompatActivity implements View.OnClickListener { State state; //current state State lastState; //Previous status private State restState; //Resting state private State defenseState; //Defensive state private State normalAttackState; //Normal attack state private State skillAttackState; //Skill attack status private int MP; //Hero's physical strength int MAXMP = 100; //Maximum physical strength int MINMP = 0; //Minimum physical strength //Declare the components of the interface TextView t_MP; TextView t_state; Button b_rest; Button b_defense; Button b_normalAttack; Button b_skillAttack; GifImageView doingGif; //Declare a Toast ToastShow toast = new ToastShow(this); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(MAXMP); } /************* * Initialize Hero *************/ public void init(int MP) { this.MP = MP; //Initialize various states restState = new RestState(this); defenseState = new DefenseState(this); normalAttackState = new NormalAttackState(this); skillAttackState = new SkillAttackState(this); //Heroes default to rest state = restState; lastState = restState; //Binding interface components t_MP = (TextView) findViewById(R.id.t_MP); t_state = (TextView) findViewById(R.id.t_state); b_rest = (Button) findViewById(R.id.b_rest); b_defense = (Button) findViewById(R.id.b_defense); b_normalAttack = (Button) findViewById(R.id.b_normalAttack); b_skillAttack = (Button) findViewById(R.id.b_skillAttack); doingGif = (GifImageView) findViewById(R.id.doingGif); http://www.biyezuopin.vip //Set up listeners for several buttons b_rest.setOnClickListener(this); b_defense.setOnClickListener(this); b_normalAttack.setOnClickListener(this); b_skillAttack.setOnClickListener(this); } //monitor @Override public void onClick(View v) { switch (v.getId()) { case R.id.b_rest: setLastState(getState()); setState(getRestState()); state.toDo(); break; case R.id.b_defense: setLastState(getState()); setState(getDefenseState()); state.toDo(); break; case R.id.b_normalAttack: setLastState(getState()); setState(getNormalAttackState()); state.toDo(); break; case R.id.b_skillAttack: setLastState(getState()); setState(getSkillAttackState()); state.toDo(); break; } } void midToast(String str, int showTime) { Toast toast = Toast.makeText(Hero.this, str, showTime); TextView v = (TextView) toast.getView().findViewById(android.R.id.message); toast.show(); } //Various getter s and setter s public int getMP() { return MP; } public void setMP(int MP) { this.MP = MP;http://www.biyezuopin.vip t_MP.setText(String.valueOf(getMP())); } public void setState(State state) { this.state = state; } public State getState() { return state; } public State getLastState() { return lastState; } public void setLastState(State lastState) { this.lastState = lastState; } public State getRestState() { return restState; } public State getDefenseState() { return defenseState; } public State getNormalAttackState() { return normalAttackState; } public State getSkillAttackState() { return skillAttackState; } }
2. Abstract STATE
For this problem, abstract State is an abstract class of State, and the code is as follows:
State.java
package com.yuebanquan.statepattern; public abstract class State { public abstract void toDo(); }
3. Concrete State
For this problem, there are four specific state roles, namely RestState, DefenseState, NormalAttackState and SkillAttackState. The codes are as follows:
RestState.java
package com.yuebanquan.statepattern; import android.widget.Toast; public class RestState extends State { Hero hero; RestState(Hero hero) { this.hero = hero; } public void toDo() { if (hero.getMP() == hero.MAXMP) { //Physical strength is full //hero.midToast("full physical strength, no need to rest", Toast.LENGTH_SHORT); hero.toast.toastShow("Physical strength is full, no need to rest"); hero.setState(hero.getLastState()); } else { //Enter a resting state hero.setMP(hero.MAXMP); //Physical strength is set to a maximum of 100 hero.setState(hero.getRestState()); hero.t_state.setText("Resting state"); hero.doingGif.setImageResource(R.drawable.rest); //hero.midToast("rest, physical strength reaches the upper limit", Toast.LENGTH_SHORT); hero.toast.toastShow("Rest and your physical strength reaches the upper limit"); } } }
DefenseState.java
package com.yuebanquan.statepattern; import android.widget.Toast; public class DefenseState extends State { Hero hero; DefenseState(Hero hero) { this.hero = hero; } public void toDo() { if (hero.getMP() < (hero.MINMP + 10)) { //Insufficient physical strength //hero.midToast("insufficient physical strength, unable to defend", Toast.LENGTH_SHORT); hero.toast.toastShow("Insufficient physical strength, unable to defend"); hero.setState(hero.getLastState()); } http://www.biyezuopin.vip else { //Enter a defensive state hero.setMP(hero.getMP() - 10); //Defence stamina - 10 hero.setState(hero.getDefenseState()); hero.t_state.setText("Defensive state"); hero.doingGif.setImageResource(R.drawable.defense); //hero.midToast("defend, physical strength - 10", Toast.LENGTH_SHORT); hero.toast.toastShow("Defense, physical strength-10"); } } }
NormalAttackState.java
package com.yuebanquan.statepattern; import android.widget.Toast; public class NormalAttackState extends State { Hero hero; NormalAttackState(Hero hero) { this.hero = hero; } public void toDo() { if (hero.getMP() < (hero.MINMP + 15)) { //Insufficient physical strength //hero.midToast("insufficient physical strength, unable to attack normally", Toast.LENGTH_SHORT); hero.toast.toastShow("Insufficient physical strength, unable to attack normally"); hero.setState(hero.getLastState()); } else { //Enter normal attack state hero.setMP(hero.getMP() - 15); //Normal attack stamina - 15 hero.setState(hero.getNormalAttackState()); hero.t_state.setText("Normal attack state"); hero.doingGif.setImageResource(R.drawable.normal_attack); //hero.midToast("issue normal attack, physical strength - 15", Toast.LENGTH_SHORT); hero.toast.toastShow("Normal attack, stamina-15"); } } }
SkillAttackState.java
package com.yuebanquan.statepattern; import android.widget.Toast; public class SkillAttackState extends State { Hero hero; SkillAttackState(Hero hero) { this.hero = hero; } public void toDo() { if (hero.getMP() < (hero.MINMP + 20)) { //Insufficient physical strength //hero.midToast("insufficient physical strength, unable to attack skills", Toast.LENGTH_SHORT); hero.toast.toastShow("Insufficient physical strength, unable to attack skill"); hero.setState(hero.getLastState()); } else { //Enter skill attack state hero.setMP(hero.getMP() - 20); //Skill attack stamina - 20 hero.setState(hero.getSkillAttackState()); hero.t_state.setText("Skill attack status"); hero.doingGif.setImageResource(R.drawable.skill_attack); //hero.midToast("issue skill attack, physical strength - 20", Toast.LENGTH_SHORT); hero.toast.toastShow("Skill attack, physical strength-20"); } } }
4. Other categories
Expand a Toast show class to solve the Toast display delay problem of Android. The code is as follows:
Toast.java
package com.yuebanquan.statepattern; import android.content.Context; import android.widget.Toast; public class ToastShow { private Context context; //Prompt information in this window private Toast toast = null; //Used to judge whether Toast has been executed public ToastShow(Context context) { this.context = context; } public void toastShow(String text) { if(toast == null) { toast = Toast.makeText(context, text, Toast.LENGTH_SHORT); //Normal execution } else { toast.setText(text); //Used to overwrite the prompt information that has not disappeared before } toast.show(); } }
5. Android layout file
The layout file is located in app/src/main/res/layout, and the code is as follows:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".Hero"> <!--seize a seat--> <TextView android:layout_width="match_parent" android:layout_height="100dp" /> <!--Hero name--> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hero name:" android:textSize="30sp"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Moon half spring" android:textSize="30sp"/> </LinearLayout> <!--Physical strength value--> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Physical strength:" android:textSize="30sp"/> <TextView android:id="@+id/t_MP" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="100" android:textSize="30sp"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="/100" android:textSize="30sp"/> </LinearLayout> <!--state--> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Status:" android:textSize="30sp"/> <TextView android:id="@+id/t_state" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Resting state" android:textSize="30sp"/> </LinearLayout> <!--<!–seize a seat–>--> <!--<TextView--> <!--android:layout_width="match_parent"--> <!--android:layout_height="100dp" />--> <pl.droidsonroids.gif.GifImageView android:id="@+id/doingGif" android:layout_width="200dp" android:layout_height="200dp" android:layout_gravity="center" android:src="@drawable/rest"/> <Button android:id="@+id/b_rest" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Take a break" android:textSize="30sp" /> <Button android:id="@+id/b_defense" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Defend" android:textSize="30sp"/> <Button android:id="@+id/b_normalAttack" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Common attack" android:textSize="30sp"/> <Button android:id="@+id/b_skillAttack" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Skill attack" android:textSize="30sp"/> </LinearLayout>
6. Android program declaration file
The program declaration file is located in app/src /, and the code is as follows:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.yuebanquan.statepattern"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".Hero"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
7. Android build gradle
The location is app /, specify the generated apk name and the introduced module, and the code is as follows:
build.gradle
apply plugin: 'com.android.application' android { compileSdkVersion 28 defaultConfig { applicationId "com.yuebanquan.statepattern" minSdkVersion 15 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } android.applicationVariants.all { variant -> variant.outputs.all { // Specify the generated apk file name here outputFileName = "Big homework(State mode).apk" } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'com.android.support:design:28.0.0' implementation 'com.android.support:support-vector-drawable:28.0.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' //Display GifView module implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.7' }