顯示具有 DesignPattern 標籤的文章。 顯示所有文章
顯示具有 DesignPattern 標籤的文章。 顯示所有文章

2020年3月8日 星期日

【不要仰賴Activity的Android開發】什麼事情都用CustomView解決





一般來說,製作一個這樣的介面會使用一組Activity和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=".MainActivity"
tools:showIn="@layout/activity_main"> 

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" /> 

 <EditText
android:id="@+id/identify"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minEms="10"
android:maxLines="1"
android:hint="ID" />
<EditText
android:id="@+id/password"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minEms="10"
android:maxLines="1"
android:hint="Password" />
<Button
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="OK"/>
</LinearLayout>



public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { 
 super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); 

 final EditText id = findViewById(R.id.identify);
final EditText pw = findViewById(R.id.password); findViewById(R.id.login).setOnClickListener(new View.OnClickListener() { @Override
public void onClick(View view) { 

 Log.d("Hsu", "ID:" + id.getEditableText().toString());
 Log.d("Hsu", "PW:" + pw.getEditableText().toString()); 


 }
); 
 } 
}
(因為不需要,所以OnClickListener的內容只有意思性地Log。)


接下來的做法或許會比較麻煩,但概念上來說會提供程式比較多的擴充升級彈性。

因為它完全不受限於Activity,功能和介面可以隨時彈性重組,方便調整或擴充。

首先,整個Activity會變成只剩下這樣.......


public class MainActivity extends AppCompatActivity { 
 @Override 
 protected void onCreate(Bundle savedInstanceState) {             super.onCreate(savedInstanceState);
 setContentView(R.layout.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"> 

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" /> 

<com.myapplication.InputText
android:id="@+id/identify"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minEms="10"
android:maxLines="1"
android:hint="ID" /> 

<com.myapplication.InputText
android:id="@+id/password"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minEms="10"
android:maxLines="1"
android:hint="Password" /> 

<com.myapplication.LoginButton
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="OK"/> 

</LinearLayout>


可以看到EditText和Button從Android標準元件變成APP專案客制的元件。

「com.myapplication」是專案的設定,有興趣的人可以自己彈性的修改命名,只要是「InputText」和「LoginButton」的路徑位置就好。(這兩個View物件的命名也可以自己彈性調整。)

InputText和LoginButton的內容則在下面。

public class InputText extends EditText { 
 public static InputText ID; 
 public static InputText PW; 

 public InputText(Context context, AttributeSet attrs) { 
 super(context, attrs); 
 String hint = getHint().toString();
 switch (hint){ 

 case "ID": ID = this;
 break;
 case "Password": 

 PW = this;
 break;
 } 

 } 
}



public class LoginButton extends Button implements View.OnClickListener { 
 public LoginButton(Context context, AttributeSet attrs) { 
 super(context, attrs);
 setOnClickListener(this);
 } 


 @Override
 public void onClick(View view) { 

 Log.d("Hsu", "ID:" + InputText.ID.getEditableText().toString());
 Log.d("Hsu", "PW:" + InputText.PW.getEditableText().toString()); 

 } 
}


因為這個介面還是算簡單的,所以這樣做的好處可能無法立刻顯現,要引入一些情境做說明。

這樣做的目的在於快速方便的大量產生擁有登入功能的介面。
登入這件事情越來越複雜,會產生各種登入模式,例如多帳號登入、例如彈出視窗登入、例如重新檢查使用者帳號密碼.....
所以快速的設計跟新增登入功能或許有其必要。

InputText可以確保針對登入的輸入功能可以快速重複利用,譬如「將輸入的內容加密」「將使用者帳號自動帶入ID欄」。
而LoginButton可以延生出各種版本,例如CheckPasswordButton可以用來讓使用者再次輸入密碼做保險性登入,等物件設計好後只要修改XML,將LoginButton換成CheckPasswordButton,一樣不需要修改Activity的任何內容,就可以完成新介面新功能。

用這種方式寫程式,為的是可以同時滿足快速組合出功能並達到模組化的目的。


有些人可以從附上的程式碼中快速找到一些功能上的限制,但其實這些限制都可以藉由簡單幾行程式碼擴充來破解,但以後再寫了。

2016年12月27日 星期二

[Design Pattern]Strategy和泛型

【以下先是碎碎念...可以跳過不看。】

程式碼在概念上經常就只是「演算法」與「資料結構」的具體實現。

但在程式的整體「框架」上,很難從程式碼看出來整個程式的邏輯或概念。

除非大家都使用「演算法」來描述事情...但這不可能,實際上大家還是習慣用具體需求來描述事情,例如「怎麼找出特定檔案」。

但這件需求其實是種「快速比對檔案內容」的演算法,習慣上應該沒有人會用後者來描繪、形容、跟別人溝通,即使是工程師對工程師。(天知道有多少種演算法可以做到「找出特定檔案」這件事。)

所以Design Pattern被發明了!...(可以這樣理解吧?)

可是我發現很多人把DesignPattern當成一種程式碼設計的延伸,大家還是習慣用「具體需求」來理解「何時該使用哪種Pattern」。

【碎碎念完。】



Strategy這個Pattern對我來說是個優化、或簡化程式碼的好工具。

很多習慣函數式開發的工程師經常把程式搞成一大包,一支[.java]檔動輒上萬行是小事。這種程式碼難維護不說,擴充難度更是高。

碰到這種程式碼,一般來說我都會快速的將所有的函數一刀切成兩種:「有傳入參數」和「無傳入參數」兩種。(補充說明:還要先排除這些函數之間是同名異構的「覆載」函數。)

那些「無傳入參數」的函數基本上都是直接修改全域變數...所以就把所有非static型態的全域變數複製一份獨立成一個物件,先稱為「StrategyObj1」好了!(記得在原始程式中宣告一個StrategyObj1的實做物件。)

接著就可以將所有的「無傳入參數」的函數通通獨立出來實做成一個「內部不帶參數、只有函數」的物件/類別。


為什麼「無傳入參數」的函數會用「有傳入參數」的方式實做?

首先是為了方便未來擴充。

假設某功能需要使用者輸入資料一、資料二,例如居住地的縣市和詳細路段,但未來忽然需要擴充為兩組以上的地址,則只需要把資料一和資料二包裝成一個類別,然後宣告兩個物件並分別傳入對應的UI輸入結果,就可以繼續使用同一個Strategy物件函數來處理了。

其次...這可以延續物件導向的風格!

(未完)