主頁 > 軟體設計 > 程式結構設計理論(Android)

程式結構設計理論(Android)

2020-09-15 16:45:04 軟體設計

程式結構設計理論(Android)

作者:鄧能財

2019年9月24日

個人簡介

姓名:鄧能財
年齡:26
畢業學校:東華理工大學
院系:理學院
專業:資訊與計算科學
郵箱:[email protected]

[明德厚學,愛國榮校]

本文的PPT版、以及作為案例的App專案可以從這里下載:程式結構設計理論(Android版)_20191108.zip
或者
鏈接:百度網盤-程式結構設計理論(Android版)_20191108.zip
提取碼:xsu3
或者
Github-程式結構設計理論(Android版)_20191108.zip

目錄

一、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

在App中維護一個ArrayList<WeakReference>串列,可以用ActivityLifecycleCallbacks實作,在其中執行

mActivityList.add(new WeakReference<Activity>(activity));

添加元素;
執行

WeakReference<Activity> thisActivity = CollectionUtil.find(((App)getApplication()).getActivityList(), item -> item.get() != null && item.get() == activity);
mActivityList.remove(thisActivity);

移出元素;
訪問一個Activity:

BbbActivity bbbActivity = CollectionUtil.find(((App)getApplication()).getActivityList(), item -> item.get() != null && item.get() instanceof BbbActivity).get();
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<WeakReference> mServiceList變數,在XxxService的onCreate()中

mServiceList.add(new WeakReference(this))

在onDestroy()中

WeakReference<Service> thisService = CollectionUtil.find(((App)getApplication()).getServiceList(), item -> item.get() != null && item.get() == this);
mServiceList.remove(thisService);

訪問Service:

XxxService> xxxService = CollectionUtil.find(((App)getApplication()).getServiceList(), item -> item.get() != null && item.get() instanceof XxxService).get();

2.在Service中更新Activity

BbbActivity bbbActivity = CollectionUtil.find(((App)getApplication()).getActivityList(), item -> item.get() != null && item.get() instanceof BbbActivity).get();
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 = https://www.cnblogs.com/definedone/p/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版)_20191108.zip
或者
鏈接:百度網盤-程式結構設計理論(Android版)_20191108.zip
提取碼:xsu3
或者
Github-程式結構設計理論(Android版)_20191108.zip

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/48380.html

標籤:架構設計

上一篇:Springboot vue.js html 跨域 前后分離 Activiti6 作業流 集成代碼生成器 shiro 權限

下一篇:Nexus-在專案中使用Maven私服,Deploy到私服、上傳第三方jar包、在專案中使用私服jar包

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more