程式結構設計理論(Android)

来源:https://www.cnblogs.com/definedone/archive/2019/09/25/11586487.html
-Advertisement-
Play Games

程式結構設計理論(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 mServiceList變數,在XxxService的onCreate()中mServiceList.add(this),在onDestroy()中mServiceList.remove(this);

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類型的對象mList保存到文件,需要將mList轉換為Json字元串,這一步視為單純計算;
然後可以用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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 快速查詢 架構預覽 nav 定義導航鏈接的部分 在頁腳顯示一個站點的導航鏈接,如首頁、服務信息頁面、版權信息頁面等 article 定義文章 裝載顯示一個獨立的文章內容,論壇帖子、新聞、博客文章、用戶評論等,artilce可以嵌套,則內層的artilce對外層的article標簽有隸屬的關係。 se ...
  • vuex(vue狀態管理) 1.先安裝vuex npm install vuex --save 2.在項目的src目錄下創建store目錄,並且新建index.js文件,然後創建vuex實例,引入vue和vuex,創建Vuex.Store實例保存到store ,最後export 導出store 3. ...
  • 前言 的確,有些標題黨了。起因是微信群里,有哥們問我,你是怎麼學習前端的呢?能不能共用一下學習方法。一句話也挺觸動我的,我真的不算是什麼大佬,對於學習前端知識,我也不能說是掌握了什麼捷徑。當然,我個人的學習方法這篇文章已經在寫了,預計這周末會在我個人公眾號發佈。而在此之前,我想展(gong)示(xi ...
  • Navigator 對象屬性 可以在Navigator對象上使用以下屬性: 屬性描述 appCodeName 返回瀏覽器的代碼名稱 appName 返回瀏覽器的名稱 appVersion 返回瀏覽器的版本信息 cookieEnabled 確定是否在瀏覽器中啟用了cookie geolocation ...
  • RESTful 是最流行的 API 設計規範,但是什麼是 RESTful?表現層狀態轉移,這恐怕誰都看不懂。本文用最通俗的說法,講明白什麼是 RESTful。 ...
  • 概述 簡單介紹一下七大設計原則: 1. 開閉原則 :是所有面向對象設計的核心,對擴展開放,對修改關閉 2. 依賴倒置原則 :針對介面編程,依賴於抽象而不依賴於具體 3. 單一職責原則 :一個介面只負責一件事情,只能有一個原因導致類變化 4. 介面隔離原則 :使用多個專門的介面,而不是使用一個總介面 ...
  • 建造者模式主要解決問題: 具備若幹成員,當其中一個成員發生變化,其它成員也隨著發生變化。 這種複雜對象的生成需要使用建造者模式來生成。 建造者設計模式的結構圖: 來源:http://c.biancheng.net/view/1354.html 例子: 街頭籃球角色創建模擬 街頭籃球: 中鋒、前鋒、後 ...
  • 一 QuerySet 可切片 使用Python 的切片語法來限制 記錄的數目 。它等同於SQL 的 和 子句。 不支持負的索引(例如 )。通常, 的切片返回一個新的 —— 它不會執行查詢。 可迭代 惰性查詢 是惰性執行的 —— 創建 不會帶來任何資料庫的訪問。你可以將過濾器保持一整天,直到 需要求值 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...