程式結構設計理論(Android) 作者:鄧能財 2019年9月24日 個人簡介 姓名:鄧能財 年齡:26 畢業學校:東華理工大學 院系:理學院 專業:信息與計算科學 郵箱:[email protected] [明德厚學,愛國榮校] 本文的PPT版、以及作為案例的App項目可以從這裡下載: "程式結 ...
程式結構設計理論(Android)
作者:鄧能財
2019年9月24日
個人簡介
姓名:鄧能財
年齡:26
畢業學校:東華理工大學
院系:理學院
專業:信息與計算科學
郵箱:[email protected]
[明德厚學,愛國榮校]
本文的PPT版、以及作為案例的App項目可以從這裡下載:程式結構設計理論(Android)20190924.zip
或者
鏈接:百度網盤-程式結構設計理論(Android)_20190926.zip
提取碼:jmu5
或者
Github-程式結構設計理論(Android)_20190926
目錄
一、android程式中,對界面的訪問與更新
二、Activity訪問Service,Service更新Activity
三、查找的簡化
四、數據體、單純計算
五、閱讀信息量優化
六、功能模塊的硬體層與軟體層,輸入、事件與動機
七、雙向綁定與即時更新
八、內部結構與外部關係的架構模式
九、數據與單純計算所在模塊、數據流
十、作為例子的App
一、android程式中,對界面的訪問與更新
1.android app的界面是樹結構
[如圖-app用戶界面的樹結構]
在android程式中訪問界面,即,在樹的一個節點訪問其他節點。
2.在Activity中某個View訪問其他View
HhhViewGroup hhhViewGroup = ((Activity)getContext()).findViewById(R.id.hhh);
或者
ViewGroup rootView = (ViewGroup)((ViewGroup)((Activity)getContext()).findViewById(android.R.id.content)).getChildAt(0);
HhhViewGroup hhhViewGroup = (HhhViewGroup)FindViewUtil.find(rootView, child -> child instanceof HhhViewGroup);
3.在某個Activity中訪問其他Activity
BbbActivity bbbActivity = CollectionUtil.find(((App)getApplication()).getActivityList(), item -> item instanceof BbbActivity);
其中(App)getApplication()).getActivityList()需要實現一個Activity列表,可以用ActivityLifecycleCallbacks實現;
4.LllEditText的文本改變時,更新KkkViewGroup的方法
lllEditText.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable text) {
// 處理text
}
});
處理text:
CccActivity cccActivity = ((CccActivity)lllEditText.getContext());
FffFragment fffFragment = (FffFragment)CollectionUtil.find(cccActivity.getFragmentManager().getFragments(), item -> item instanceof FffFragment);
KkkViewGroup kkkViewGroup = (KkkViewGroup)fffFragment.getView().findViewById(R.id.kkk);
或者
KkkViewGroup kkkViewGroup = (KkkViewGroup)FindViewUtil.find(fffFragment.getView(), child -> child instanceof KkkViewGroup);
kkkViewGroup.refresh(text.toString());
5.監聽器的設置不外傳
由於任何子View或者Fragment都可以訪問任何其他節點,因此類似下麵的代碼是不應該存在的:
假設CccActivity包含FffFragment,FffFragment包含kkkViewGroup;
public class FffFragment {
public void setXxxOnClickListener(XxxOnClickListener xxxOnClickListener) {
kkkViewGroup.setXxxOnClickListener(xxxOnClickListener);
}
}
然後在CccActivity內執行:
fffFragment.setXxxOnClickListener(() -> {CccActivity.this.doThing(); });
應該這樣實現:
kkkViewGroup.setXxxOnClickListener(() -> {((CccActivity)kkkViewGroup.getContext()).doThing(); });
二、Activity訪問Service,Service更新Activity
1.Activity訪問Service
方法一、bindService()
方法二、維護Service列表
在App中定義ArrayList
XxxService xxxService = CollectionUtil.find(((App)getApplication()).getServiceList(), item -> item instanceof XxxService);
2.在Service中更新Activity
BbbActivity bbbActivity = CollectionUtil.find(((App)getApplication()).getActivityList(), item -> item instanceof BbbActivity);
bbbActivity.refresh(data);
三、查找的簡化
for(Ttt item : list) {
if (item...) {
doThing(item);
break;
}
}
可簡化為:
Ttt item = CollectionUtil.find(list, item...);
doThing(item);
find的實現:
public class CollectionUtil {
public static <T> T find(Collection<T> list, Filter<T> filter) {
for(T object : list) {
if (filter.accept(object)) {
return object;
}
}
return null;
}
public interface Filter<T> {
boolean accept(T object);
}
}
四、數據體、單純計算
1.數據體
定義:可以轉換為字元串而不損失信息的變數或常量是數據體;
依賴硬體的數據都不是數據體;
例如:
C語言指針的值依賴硬體,這個值複製到其他電腦上就沒有意義了,因此不是數據體;
Java Bean對象,可以轉換為json而不損失信息,因為可以轉回去,所以Java Bean對象是數據體;
數字3,可以轉為"3"而不損失信息;
byte[]數組對象是數據體;
android.view.View類的對象不是數據體(待補充);
2.單純計算(Pure Calculation)
定義:以一組數據體作為輸入,以一組數據體作為輸出的計算稱為單純計算;
[result1 result2 result3] = function([param1 param2 param3])
其中function稱為函數體;
3.單純計算的特征
哲理1:程式執行的過程是通信與計算的過程;程式內部的通信是指記憶體到其他硬體之間的通信;
單純計算的特征:
單純計算只在記憶體、CPU執行;不涉及和顯示屏、文件、網路的通信;
單純計算過程中,不應該引用全局變數或者給全局變數賦值,引用全局變數是輸入,給全局變數賦值是輸出;
4.例子
if (validate(input)) {
functionA(input);
}
boolean validate(String input) {
if (input.length() > 5) {
showToast("不能長於5個字元!");
return false;
} else if (!isAlphabet(input)) {
showToast("只能輸入字母!");
return false;
} else {
return true;
}
}
其中,showToast()涉及和顯示屏通信;
可分離出單純計算過程validate():
String result = validate(input);
if (result == null) {
functionA(input);
} else {
showToast(result);
}
String validate(String input) {
String result;
if (input.length() > 5) {
result = "不能長於5個字元!";
} else if (!isAlphabet(input)) {
result = "只能輸入字母!";
} else {
result = null;
}
}
五、閱讀信息量優化
1.模塊與閱讀信息量
閱讀信息量是衡量代碼的簡單與複雜、閱讀的難易程度的一個指標;
A.例子一
public static int function(int a, b, c, d, e, f, g, h) {
a = a + 1;
b = a + b;
c = a + b + c;
d = a + b + c + d;
e = e + 1;
f = e + f;
g = e + f + g;
h = e + f + g + h;
return d + h;
}
劃分之後:
public static int function(int a, b, c, d, e, f, g, h) {
d = functionI(a, b, c, d);
h = functionJ(e, f, g, h);
return d + h;
}
private static int functionI(int a, b, c, d) {
a = a + 1;
b = a + b;
c = a + b + c;
d = a + b + c + d;
return d;
}
private static int functionJ(int e, f, g, h) {
e = e + 1;
f = e + f;
g = e + f + g;
h = e + f + g + h;
return h;
}
閱讀代碼時,需要讀懂一個方法內語句之間的關係;
這裡只簡單的考慮有關或無關這種關係,有關用1表示,無關用0表示;
劃分之前,對於function(),由於A語句"a = a + 1"對a的賦值會影響引用了a的B語句"b = a + b"的執行結果,因此認為A語句與B語句有關,記M(A, B) = M(B, A) = 1;
function()的語句之間的關係如下(R表示return語句):
A B C D E F G H R
A 0 1 1 1 0 0 0 0 0
B 1 0 1 1 0 0 0 0 0
C 1 1 0 1 0 0 0 0 0
D 1 1 1 0 0 0 0 0 1
E 0 0 0 0 0 1 1 1 0
F 0 0 0 0 1 0 1 1 0
G 0 0 0 0 1 1 0 1 0
H 0 0 0 0 1 1 1 0 1
R 0 0 0 1 0 0 0 1 0
由於這個矩陣的各個元素出現0或1的概率是1/2,因此這個矩陣的信息量為
I = 9*9*-log2(p) = 9*9*-log2(1/2) = 81 (bit)
即,function()方法的閱讀信息量是81(bit);
劃分之後,語句之間的關係如下:
function():
I J R
I 0 0 1
J 0 0 1
R 1 1 0
functionI():
A B C D R
A 0 1 1 1 0
B 1 0 1 1 0
C 1 1 0 1 0
D 1 1 1 0 1
R 0 0 0 1 0
functionJ():
E F G H R
E 0 1 1 1 0
F 1 0 1 1 0
G 1 1 0 1 0
H 1 1 1 0 1
R 0 0 0 1 0
這個三個矩陣的信息量為
I = 3*3*-log2(1/2) + 5*5*-log2(1/2) + 5*5*-log2(1/2) = 9 + 25 + 25 = 59 (bit)
即,function()、functionI()、functionJ()這三個方法的閱讀信息量一共是59(bit);
由於81 (bit) > 59 (bit),可見,劃分之後減少了閱讀信息量;
B.例子二:語句的排列順序產生的信息量
a=0; b=1; c=1;
a=b+c;
a=a+b;
output a; // 3
a=0; b=1; c=1;
a=a+b;
a=b+c;
output a; // 2
從這兩段代碼可以得知,如果把語句的順序調換,執行結果就不一樣了;
因此程式中語句的排列順序往往不能改變;
n條語句的排列順序的信息量等於一個全排列的信息量
p = 1/n!
I = -log2(p) = -log2(1/n!) = log2(n!)
當n = 8時,I = log2(8!) = 15.3(bit)
C.例子三:單條語句的信息量
單條語句的信息量包含引用類和方法產生的信息量;
引用類的信息量 = -log2(1/類的總數) = log2(類的總數);
引用方法的信息量 = log2(某個類的方法的總數);
2.避免變數信息重覆
例子如下:
int[] numbers;
int count;
這個兩個變數中count是numbers的總和;
由於count可以通過Math.count(numbers)計算得到,因此count包含的信息與numbers包含的信息重覆;
這時應該去掉count變數,用int count(int[] numbers) {return Math.count(numbers); }這個函數體來代替;
因此有如下的一般結論:
有幾個數據體變數:
DataTypeA dataA;
DataTypeB dataB;
DataTypeC dataC;
如果存在一個函數體functionD,以dataA, dataB作為輸入,以dataC作為輸出;
DataTypeC functionD(DataTypeA dataA, DataTypeB dataB)
即dataC可由dataA, dataB計算出來,就認為dataC包含的信息與dataA, dataB包含的信息重覆;
此時應該去除dataC變數,用DataTypeC functionD(DataTypeA dataA, DataTypeB dataB)來代替;
3.避免邏輯功能重覆
避免邏輯功能重覆有兩種情況:
A.避免應用層代碼的邏輯功能和底層框架的邏輯功能重覆;
B.避免應用層代碼的邏輯功能和應用層代碼的邏輯功能重覆;
例子:
ViewGroup containerView = findViewById(R.id.xxx);
LinearLayout layout = new LinearLayout(mContext);
layout.setLayoutParam(new LayoutParam(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
layout.addView(new TextView(mContext));
containerView.addView(layout);
這段代碼的功能和LayoutInflater.inflate()的功能重覆;
用XML文件實現界面佈局,代碼會更簡潔;
4.避免配置信息重覆
和避免變數信息重覆類似;
有幾個配置數據dataA、dataB、dataC:
如果存在一個函數體functionD,以dataA, dataB作為輸入,輸出等於dataC;
dataC == functionD(dataA, dataB)
即dataC可由dataA, dataB計算出來,就認為dataC包含的信息與dataA, dataB包含的信息重覆;
此時應該去除dataC配置,用functionD(dataA, dataB)來代替;
六、功能模塊的硬體層與軟體層,輸入、事件與動機
1.功能模塊的硬體層與軟體層
App的功能模塊一般包括文件、顯示、網路、聲音;
這裡考慮包含文件、顯示、網路功能模塊的App;
各個功能模塊包括硬體層、軟體框架層、軟體應用層;
這三個功能模塊的分層如下:
模塊 文件 顯示 網路
硬體層 磁碟 屏幕 網卡
軟體框架層 File, SQLite, SharedPreference View,Activity,Fragment HTTPConnection
文件功能的軟體應用層:
File:對文件的增、刪、改、讀(讀取);
SQLite:對資料庫的增刪改查;
SharedPreference:put、get、clear;
顯示功能的軟體應用層:創建與銷毀視圖、切換顯示與隱藏、輸入數據(比如文字)、填充或提取數據、動畫、頁面跳轉;
網路功能的軟體應用層:輸入請求參數、請求並延時、輸出請求結果、消息推送;
2.輸入、事件與動機
事件的定義:由於用戶或某個設備向程式輸入的數據滿足某個條件,引發了監聽器方法的執行,稱之為一次事件;
這三個功能的事件如下:
文件:文件或文件夾改變的事件;
顯示:觸屏事件(包括onClick, onLongClick, onTouch, TextWatcher.onTextChanged等);
網路:獲得推送消息的事件(Http請求與回調視作一次時間稍長的執行,Http回調不視為事件,就像動畫那樣);
哲理2:任何有數據輸入的功能模塊都可能引發事件;程式執行的動機是事件,即事件推動了程式執行;
對於這三個功能:
文件功能中文件或文件夾改變相當於輸入了文件或文件夾名稱以及改變類型,當改變發生在所監聽文件夾內,就會引發事件;
顯示功能輸入了觸摸屏幕的位置數據,當觸摸的點落在設置了OnClickListener的View的區域時,就會引發點擊事件;
網路功能輸入了消息推送的數據;
七、雙向綁定與即時更新
1.雙向綁定與即時更新
雙向綁定的定義:在顯示模塊中,視圖組件與顯示模塊數據相關聯;視圖組件包含的數據變化時,即時更新到顯示模塊數據;顯示模塊數據變化時,即時更新視圖組件;
除了即時更新的方式提取數據,就是臨時從視圖組件提取數據;
2.雙向綁定的應用場景
應用場景:雙向綁定應用於可編輯的列表;
可編輯的列表的列表項視圖一般有編輯框、CheckBox等,或者可添加項、刪除項等;
對於不是列表的視圖組件,可以在任何時候方便地從中提取數據,因此不需要雙向綁定;
對於不可編輯的列表,只需要向列表填充數據,不改變數據;
A.例子一:
列表項視圖中有編輯框,且編輯框里的文字對應列表的數據項的某欄位的值;
因為列表視圖有回收機制,所以這樣的列表是無法隨時從視圖組件的所有項提取數據的;
B.例子二:列表的數據刪除一項,需要調用notifyDatasetChanged()即時更新視圖組件;
八、內部結構與外部關係的架構模式(Internal Structure and External Relations)
目錄
1.內部結構與外部關係的哲理
2.方法的外部關係
3.基礎模塊、及其內部結構與外部關係
4.基礎模塊的單純性與單純化重構
5.不滿足單純性的一般性例子
6.多個基礎模塊包含事件方法時的單純化重構
7.對基礎模塊初始化邏輯的單純化重構
8.對方法進行拆解封裝重構
9.對類的拆解封裝重構
10.事件是程式執行的動機
11.中間類的性質,與外部關係模塊
12.Java桌面應用程式的一般形式
13.Android應用程式的一般形式
1.內部結構與外部關係的哲理
哲理3:任何一個本體都具有內部結構與外部關係,一內一外構成其整體;
例如:一條數據記錄或者一個程式模塊都有內部結構和外部關係;
[如圖-本體的內部結構與外部關係]
2.方法的外部關係
定義:當方法A內,調用兩個或兩個以上其他方法(B1、B2、…Bn)時,方法A就是方法B1、B2、…Bn之間的一種外部關係;
之所以說是“一種”是因為,可能B1、B2、…Bn之間還有方法A2、A3等其他的外部關係;
例子A:
public static void main(String[] args) {
functionI();
functionJ();
}
private static void functionI() {
sentenceA();
sentenceB();
sentenceC();
sentenceD();
}
private static void functionJ() {
sentenceE();
sentenceF();
sentenceG();
sentenceH();
}
functionI()中的語句是functionI()的內部結構;
functionJ()中的語句是functionJ()的內部結構;
同時調用了functionI()和functionJ()的main()方法是functionI()和functionJ()的外部關係;
3.基礎模塊、及其內部結構與外部關係
六.1中所定義的文件、顯示、網路等模塊下麵稱為“基礎模塊”;
基礎模塊具有的六.1中所列舉的功能是基礎模塊的內部結構;
基礎模塊的外部關係的定義:
當一個方法內,調用了兩個或兩個以上基礎模塊的代碼時,這個方法就是這些基礎模塊之間的外部關係(下麵簡稱“外部關係”);
4.基礎模塊的單純性與單純化重構
單純性的定義:某個基礎模塊內部沒有直接調用其他基礎模塊,就稱這個基礎模塊滿足單純性;
結論1:任何基礎模塊之間的直接相互調用都會使基礎模塊失去單純性,因此基礎模塊之間的相互調用必須通過中間類來實現;
例如,A模塊調用B模塊的方法functionBbb(),變為,A模塊調用中間類、中間類調用B模塊的方法functionBbb();
例如,A模塊定義了B模塊的變數mBbb,變為,中間類定義mBbb變數;
基礎模塊的單純化重構:
例子B:
public class KkkActivity {
View vViewA;
View vViewB;
FileManager mFileManager;
...
private void functionL() {
vViewA.setOnClickListener((v) -> {
mFileManager.write("abcdef");
});
}
private void functionM() {
vViewA.setOnClickListener((v) -> {
vViewB.setVisibility(View.GONE);
});
}
}
在例子B中,mFileManager的聲明語句和mFileManager.write("abcdef")語句處在顯示模塊KkkActivity中,使KkkActivity有失單純性;因此需要調進行單純化重構,使KkkActivity保持單純性;
下麵對例子B的顯示模塊進行單純化重構:
對於例子B,其中的
vViewA.setOnClickListener((v) -> {
mFileManager.write("abcdef");
}
這個語句,可以分解為:
View.OnClickListener listener = new View.OnClickListener() {
@Override public void onClick(View v) {
mFileManager.write("abcdef");
}
};
vViewA.setOnClickListener(listener);
這幾個語句調用了
View.OnClickListener listener = new View.OnClickListener() {...};
mFileManager.write("abcdef");
這兩個語句;它們一個屬於顯示模塊、一個屬於文件模塊;
這兩個語句可以提取封裝為一個方法getWriteOnClickListener(),如下:
private View.OnClickListener getWriteOnClickListener() {
View.OnClickListener listener = new View.OnClickListener() {
@Override public void onClick(View v) {
mFileManager.write("abcdef");
}
};
return listener;
}
vViewA.setOnClickListener(getWriteOnClickListener());
因此這兩個語句構成的getWriteOnClickListener()是顯示模塊與文件模塊的外部關係;
與八.2例子A的差異:在這裡,監聽器創建的語句嵌套文件模塊的語句,而不是語句排列;
下麵構建中間類——MiddleClass,使KkkActivity保持單純性:
public class MiddleClass {
FileManager mFileManager;
public View.OnClickListener getWriteOnClickListener() {
View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View v) {
mFileManager.write("abcdef");
}
};
return listener;
}
}
public class KkkActivity {
View vViewA;
View vViewB;
MiddleClass mMiddleClass;
...
private void functionL() {
vViewA.setOnClickListener(mMiddleClass.getWriteOnClickListener());
}
private void functionM() {
vViewA.setOnClickListener((v) -> {
vViewB.setVisibility(View.GONE);
});
}
}
5.不滿足單純性的一般性例子
例子C:
假設mObj1、mObj2、mObj3是屬於基礎模塊XxxModule的變數,RrrModule、SssModule是另外兩個基礎模塊;
class XxxModule {
Type1 mObj1;
Type2 mObj2;
Type3 mObj3;
RrrModule mRrrModule;
SssModule mSssModule;
void functionO() {
mObj1.methodT();
Type4 obj4 = new Type4();
final Type5 finalObj5 = new Type5();
mObj3.setOnPppListener(new OnPppListener() {
@Override public void onPpp(QType1 q1, QType2 q2, QType3 q3) {
mObj1.doThingU(q1);
TypeR1 r1 = Calc.doThingV(q1, q2);
mRrrModule.methodW(r1);
mObj2.methodY(q3);
TypeR2 r2 = Calc.doThingZ(q2, q3, finalObj5);
mSssModule.methodA(r1, r2, new OnDddListener() {
@Override public onDdd(TypeB b) {
mRrrModule.methodC(b);
}
});
}
});
...
}
}
回調方法onPpp()內可引用的對象有q1, q2, q3, finalObj5, mObj1, mObj2, mObj3, mRrrModule, mSssModule;
下麵將例子C的XxxModule單純化:
對於例子C中的
mObj3.setOnPppListener(new OnPppListener() {
@Override public void onPpp(QType1 q1, QType2 q2, QType3 q3) {
mObj1.doThingU(q1);
TypeR1 r1 = Calc.doThingV(q1, q2);
mRrrModule.methodW(r1);
mObj2.methodY(q3);
TypeR2 r2 = Calc.doThingZ(q2, q3, finalObj5);
mSssModule.methodA(r1, r2, new OnDddListener() {
@Override public onDdd(TypeB b) {
mRrrModule.methodC(b);
}
});
}
});
例子C的XxxModule單純化,第一步:
private OnPppListener getAaaOnPppListener(Type5 finalObj5) {
return new OnPppListener() {
@Override ublic void onPpp(QType1 q1, QType2 q2, QType3 q3) {
TypeR1 r1 = xxxMethodE(q1, q2);
mRrrModule.methodW(r1);
TypeR2 r2 = xxxMethodF(q2, q3, finalObj5);
mSssModule.methodA(r1, r2, new OnDddListener() {
@Override public onDdd(TypeB b) {
mRrrModule.methodC(b);
}
});
}
};
}
public TypeR1 xxxMethodE(QType1 q1, QType2 q2) {
mObj1.doThingU(q1);
TypeR1 r1 = Calc.doThingV(q1, q2);
return r1;
}
public TypeR2 xxxMethodF(QType2 q2, QType3 q3, Type5 finalObj5) {
mObj2.methodY(q3);
TypeR2 r2 = Calc.doThingZ(q2, q3, finalObj5);
return r2;
}
mObj3.setOnPppListener(getAaaOnPppListener(finalObj5));
例子C的XxxModule單純化,第二步:
public class MiddleClass {
XxxModule mXxxModule;
RrrModule mRrrModule;
SssModule mSssModule;
public MiddleClass(XxxModule xxxModule) {
mXxxModule = xxxModule;
...
}
public OnPppListener getAaaOnPppListener(Type5 finalObj5) {
return new OnPppListener() {
@Override public void onPpp(QType1 q1, QType2 q2, QType3 q3) {
TypeR1 r1 = mXxxModule.xxxMethodE(q1, q2);
mRrrModule.methodW(r1);
TypeR2 r2 = mXxxModule.xxxMethodF(q2, q3, finalObj5);
mSssModule.methodA(r1, r2, new OnDddListener() {
@Override public onDdd(TypeB b) {
mRrrModule.methodC(b);
}
});
}
};
}
}
class XxxModule {
Type1 mObj1;
Type2 mObj2;
Type3 mObj3;
MiddleClass mMiddleClass;
...
void functionO() {
mObj1.methodT();
Type4 obj4 = new Type4();
final Type5 finalObj5 = new Type5();
mObj3.setOnPppListener(mMiddleClass.getAaaOnPppListener(finalObj5));
...
}
public TypeR1 xxxMethodE(QType1 q1, QType2 q2) {
mObj1.doThingU(q1);
TypeR1 r1 = Calc.doThingV(q1, q2);
return r1;
}
public TypeR2 xxxMethodF(QType2 q2, QType3 q3, Type5 finalObj5) {
mObj2.methodY(q3);
TypeR2 r2 = Calc.doThingZ(q2, q3, finalObj5);
return r2;
}
}
下麵將事件的監聽器的回調方法簡稱為“事件方法”;
當外部關係是由事件方法嵌套其他代碼構成時,稱這個外部關係為“事件外部關係”;
上述例子C中,onPpp()是事件方法;getAaaOnPppListener()是事件外部關係;
由例子C可見:
結論2:在任何一個基礎模塊的事件方法中調用其他基礎模塊的邏輯,必然可以單純化重構為事件外部關係;並且重構之後,這個事件外部關係處於中間類中;
6.多個基礎模塊包含事件方法時的單純化重構
例子D:
假設程式有A, B, C, D四個基礎模塊,並且除D以外,它們都有在事件方法中調用其他基礎模塊的邏輯,即:
A: (paramA)-> {...A, B, C, D}, B: (paramB)-> {...A, B, C, D}, C: (paramC)-> {...A, B, C, D};
根據結論2,單純化重構A之後,(paramA)-> {...}這個事件外部關係處於中間類中;
再單純化重構B之後,(paramB)-> {...}這個事件外部關係也處於中間類中;
再單純化重構C之後,(paramA)-> {...}, (paramB)-> {...}, (paramC)-> {...}這三個事件外部關係都處於中間類中;
根據例子D:
結論3:整個程式在單純化重構之後,所有的事件外部關係都處於中間類中;
7.對基礎模塊初始化邏輯的單純化重構
例子E:
public class AaaActivity {
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
mGpsManager = new GpsManager(mainActivity.getApplicationContext(), this);
String filename = ...;
mFileManager = new FileManager(filename);
mFileManager.open();
}
下麵對顯示模塊AaaActivity進行單純化重構:
public class AaaActivity {
MiddleClass mMiddleClass;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
mMiddleClass = new MiddleClass(this);
}
public class MiddleClass {
public MiddleClass(MainActivity mainActivity) {
mMainActivity = mainActivity;
mGpsManager = new GpsManager(mainActivity.getApplicationContext(), this);
String filename = ...;
mFileManager = new FileManager(filename);
mFileManager.open();
}
}
基礎模塊初始化的外部關係,下麵稱為“初始化外部關係”;
由例子E可見:
結論4:顯示模塊的onCreate()中調用其他基礎模塊初始化的邏輯,可以單純化重構;並且重構之後,所得到的一個初始化外部關係就是中間類的構造方法;
根據結論3與結論4,可得到:
結論5:單純化重構的一般性方法:
各個基礎模塊的初始化的邏輯,必須重構為初始化外部關係,在重構之後,這個初始化外部關係處於中間類中,並且初始化外部關係不是private方法;
在任何一個基礎模塊的事件方法中調用其他基礎模塊的邏輯,必須重構為事件外部關係,在重構之後,這個事件外部關係處於中間類中,並且這個事件外部關係不是private方法;
因此程式單純化重構之後,中間類包含一個初始化外部關係,並且包含所有事件外部關係,它們都不是private方法;
8.對方法進行拆解封裝重構
例子F:
void functionX() {
mXxxModule.methodA();
functionB();
functionF();
}
private void functionB() {
mYyyModule.methodC();
functionD();
}
private void functionD() {
mZzzModule.methodE();
}
private void functionF() {
sentenceG();
sentenceH();
}
對方法進行拆解封裝重構之後:
void functionX() {
mXxxModule.methodA();
mYyyModule.methodC();
mZzzModule.methodE();
sentenceG();
sentenceH();
}
重構之前,functionX()方法調用語句mZzzModule.methodE()形成的棧是:
functionX() > functionB() > functionD() > mZzzModule.methodE();
重構之後,形成的棧是:
functionX() > mZzzModule.methodE();
由例子F,可得到:
結論6:在某個類classA中,對方法methodM內調用的classA中的方法的語句,進行拆解封裝重構後,可以使得methodM內沒有調用classA中的其他方法的語句;
9.對類的拆解封裝重構
定義:對某個類的拆解封裝重構,是要將類中的除了構造方法外的所有private方法,進行拆解封裝到調用它們的方法中,最後類中只剩下public、protected以及沒有修飾符的方法;
如果對程式進行單純化重構,得到中間類,再對中間類進行拆解封裝重構;
根據結論5,程式單純化重構之後,中間類包含一個初始化外部關係,並且包含所有事件外部關係,它們都不是private方法,因此再對中間類進行拆解封裝重構之後,初始化外部關係、所有事件外部關係都不會消失;
10.事件是程式執行的動機
根據哲理2,事件是程式執行的動機,那麼有如下結論:
結論7:假設初始化方法是init(),事件方法是onEvent();對於程式中的任意一個方法functionEee(),程式執行時的方法調用棧為
init() > functionAaa() > functionBbb() ... > functionEee() ... > functionXxx()
或者
onEvent() > functionAaa() > functionBbb() ... > functionEee() ... > functionXxx()
,即init()或者onEvent()處於棧底,而functionEee()處於棧中;
11.中間類的性質,與外部關係模塊
下麵證明,關於中間類性質的,以及關於“外部關係模塊”的概念的結論8;
結論8:程式的單純化重構可以通過創建中間類來實現;並且單純化重構、再對中間類進行拆解封裝重構之後,中間類包含且僅包含基礎模塊的一個初始化外部關係以及基礎模塊的所有事件外部關係;(3點含義,因此上述的中間類從此稱之為“外部關係模塊”);
證明:由結論5可知,單純化重構之後,中間類包含一個初始化外部關係,並且包含所有事件外部關係;
假設,程式單純化重構、再對中間類進行拆解封裝重構之後,中間類中存在一個不是初始化外部關係或事件外部關係的方法A;下麵證明這樣的方法A不存在;(因此中間類僅包含一個初始化外部關係以及事件外部關係)
根據結論7可得,方法A的調用棧的棧底必然為onEvent()或者init(),當棧底為init()時,init()處於中間類中,因此方法A在拆解封裝重構之後就消失了,即這樣的方法A不存在;
當棧底為onEvent()、並且這個onEvent()是事件外部關係的事件方法時,由於事件外部關係處於中間類中,因此方法A在拆解封裝重構之後就消失了,即這樣的方法A不存在;
當棧底為onEvent()、並且這個onEvent()不是事件外部關係的事件方法時,根據結論5(單純化重構的一般性方法)可知,單純化重構不會將它重構到中間類,所以這樣的方法A不存在;
[證明完畢]
12.Java桌面應用程式的一般形式
任何Java桌面應用程式,都可以進行單純化重構、並且拆解封裝重構,得到例子G的這種形式的中間類;
例子G:
public class XxxExternalRelations {
ViewManager mViewManager;
FileManager mFileManager;
GpsManager mGpsManager;
GeocoderManager mGeocoderManager;
public XxxExternalRelations(Object param) {
XxxExternalRelations page = this;
// 在ViewManager的構造方法內調用vMember.setVvvListener(page.getVvvListener());
mViewManager = new ViewManager(page);
// 在FileManager的構造方法內調用fMember.setFffListener(page.getFffListener());
mFileManager = new FileManager(page);
// 在GpsManager 的構造方法內調用gMember.setGggListener(page.getGggListener());
mGpsManager = new GpsManager(page);
mGeocoderManager = new GeocoderManager();
...// 調用mViewManager, mFileManager, mGpsManager, mGeocoderManager進行初始化
}
public VvvListener getVvvListener() {
retrun (vparam) -> {
// 調用mViewManager, mFileManager, HttpUtil, mGpsManager, mGeocoderManager, PureCalculation
DataType inputData = PureCalculation.convert(mViewManager.getInputData();
HttpUtil.login(inputData), new RequestCalback() {
@Override
public void onStart() {
mViewManager.showWaiting();
}
@Override
public void onProgress(float progress) {
mViewManager.updateProgress(progress);
}
@Override
public void onEnd(Reponse data) {
mViewManager.dismissWaiting();
...// 處理data,調用其他模塊
}
});
};
}
public FffListener getFffListener() {
return (fparam) -> {
// 調用mViewManager, mFileManager, HttpUtil, mGpsManager, mGeocoderManager, PureCalculation
};
}
public GggListener getGggListener() {
return (gparam) -> {
// 調用mViewManager, mFileManager, HttpUtil, mGpsManager, mGeocoderManager, PureCalculation
};
}
}
也就是,程式是按照這個步驟執行的:創建模塊、設置監聽器、初始化,然後等待事件的發生來執行其他代碼;
13.Android應用程式的一般形式
任何Android應用程式都有例子H的這種形式;
例子H:
public class ActivityLifecycleCallback {
public void onModulesCreated() { }
public void onResume() { }
public void onPause() { }
public void onDestroy() { }
}
由於Activity的生命周期方法的執行一般是點擊事件導致的,因此ActivityLifecycleCallback視作事件的監聽器;由於它的事件方法內必然會調用除顯示模塊的其他模塊,因此ActivityLifecycleCallback的對象在外部關係模塊創建;
public class BaseExternalRelations {
public ActivityLifecycleCallback getActivityLifecycleCallback() {
return new ActivityLifecycleCallback(){};
}
}
public abstract class BaseActivity<T extends BaseExternalRelations> extends AppCompatActivity {
protected T mExternalRelations;
private ActivityLifecycleCallback mLifecycleCallback;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutResID());
mExternalRelations = createExternalRelations();
findViewAndSetListener();
mLifecycleCallback = mExternalRelations.getActivityLifecycleCallback();
mLifecycleCallback.onModulesCreated();
}
protected abstract int getLayoutResID();
protected abstract T createExternalRelations();
protected abstract void findViewAndSetListener();
...
@Override
protected void onDestroy() {
if (mLifecycleCallback != null) {
mLifecycleCallback.onDestroy();
}
super.onDestroy();
}
}
打開App時,程式執行了Application的創建以及回調onCreate()方法,也執行了第一個Activity的創建、onCreate()方法以及onResume()方法;這種結構形式在Activity的onCreate()中創建外部關係模塊以及初始化監聽器,然後程式靜止,等待事件的發生;
public class MainActivity extends BaseActivity<ExternalRelations> {
private TextView vTextLocationAddress;
private TextView vTextAddSituation;
@Override protected int getLayoutResID() {
return R.layout.activity_main;
}
@Override protected ExternalRelations createExternalRelations() {
return new ExternalRelations(this);
}
@Override protected void findViewAndSetListener() {
vTextLocationAddress = (TextView)findViewById(R.id.vTextLocationAddress);
vTextAddSituation = (TextView)findViewById(R.id.vTextAddSituation);
vTextAddSituation.setOnClickListener(mExternalRelations.getOnAddSituationClickListener());
...
}
public void setLocationAddress(String locationAddress) {
vTextLocationAddress.setText(locationAddress);
}
...
}
public class ExternalRelations extends BaseExternalRelations {
private MainActivity mMainActivity;
private FileManager mFileManager;
public ExternalRelations(MainActivity mainActivity) {
mMainActivity = mainActivity;
String filename = ... + ".txt";
mFileManager = new FileManager(filename);
}
@Override public ActivityLifecycleCallback getActivityLifecycleCallback() {
return new ActivityLifecycleCallback() {
@Override
public void onModulesCreated() { // 當各個模塊都創建完成後,所執行的
mFileManager.open();
...
}
@Override
public void onDestroy() {
mFileManager.close();
...
}
};
}
public View.OnClickListener getOnAddSituationClickListener() {
return (v) -> {
...
};
}
}
九、數據與單純計算所在模塊、數據流
1.數據與單純計算所在模塊
業務數據在外部關係模塊中,業務數據經過單純計算,得到其他基礎模塊能夠直接使用的數據(有時不需要單純計算這一步);
單純計算的邏輯應該放在單獨的一個類中;對單純計算類的調用都在外部關係模塊中;
A.例子一
時間戳在業務數據中是long類型,而顯示模塊能直接使用的時間格式是YYYY-MM-DD,於是需要通過單純計算進行轉化;轉化所得到的稱為顯示模塊數據;
B.例子二
假設要將一個List
然後可以用new OutputStreamWriter(new FileOutputStream(filename), encoding)將Json寫入文件;
然後從文件讀取json,轉換為List
2.數據流
數據流圖形如下:
[如圖-數據與單純計算所屬模塊]
十、作為例子的App
見附件文件:ProgramStructureGPS.20190922.zip,這是一個Android項目的壓縮文件;
本文的PPT版、以及作為案例的App項目可以從這裡下載:程式結構設計理論(Android)20190924.zip
或者
鏈接:百度網盤-程式結構設計理論(Android)_20190926.zip
提取碼:jmu5
或者
Github-程式結構設計理論(Android)_20190926