姓名:邓能财
[明德厚学,爱国荣校]
本文的PPT版、以及作为案例的App项目可以从这里下载:程序结构设计理论(Android)20190924.zip
一、android程序中,对界面的访问与更新
[如图-app用户界面的树结构]
在android程序中访问界面,即,在树的一个节点访问其他节点。
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);
BbbActivity bbbActivity = CollectionUtil.find(((App)getApplication()).getActivityList(), item -> item instanceof BbbActivity);
其中(App)getApplication()).getActivityList()需要实现一个Activity列表,可以用ActivityLifecycleCallbacks实现;
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());
由于任何子View或者Fragment都可以访问任何其他节点,因此类似下面的代码是不应该存在的:
public class FffFragment { public void setXxxOnClickListener(XxxOnClickListener xxxOnClickListener) { kkkViewGroup.setXxxOnClickListener(xxxOnClickListener); } }
然后在CccActivity内执行:
fffFragment.setXxxOnClickListener(() -> {CccActivity.this.doThing(); });
应该这样实现:
kkkViewGroup.setXxxOnClickListener(() -> {((CccActivity)kkkViewGroup.getContext()).doThing(); });
方法一、bindService()
XxxService xxxService = CollectionUtil.find(((App)getApplication()).getServiceList(), item -> item instanceof XxxService);
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 staticT find(Collection list, Filter filter) { for(T object : list) { if (filter.accept(object)) { return object; } } return null; } public interface Filter { boolean accept(T object); } }
定义:可以转换为字符串而不损失信息的变量或常量是数据体;
定义:以一组数据体作为输入,以一组数据体作为输出的计算称为单纯计算;
[result1 result2 result3] = function([param1 param2 param3])
其中function称为函数体;
哲理1:程序执行的过程是通信与计算的过程;程序内部的通信是指内存到其他硬件之间的通信;
单纯计算的特征:
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()涉及和显示屏通信;
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; } }
阅读信息量是衡量代码的简单与复杂、阅读的难易程度的一个指标;
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; }
阅读代码时,需要读懂一个方法内语句之间的关系;
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);
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
从这两段代码可以得知,如果把语句的顺序调换,执行结果就不一样了;
p = 1/n! I = -log2(p) = -log2(1/n!) = log2(n!)
当n = 8时,I = log2(8!) = 15.3(bit)
C.例子三:单条语句的信息量
例子如下:
int[] numbers; int count;
这个两个变量中count是numbers的总和;
因此有如下的一般结论:
有几个数据体变量:
DataTypeA dataA; DataTypeB dataB; DataTypeC dataC;
如果存在一个函数体functionD,以dataA, dataB作为输入,以dataC作为输出;
DataTypeC functionD(DataTypeA dataA, DataTypeB dataB)
即dataC可由dataA, dataB计算出来,就认为dataC包含的信息与dataA, dataB包含的信息重复;
避免逻辑功能重复有两种情况:
例子:
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()的功能重复;
和避免变量信息重复类似;
dataC == functionD(dataA, dataB)
即dataC可由dataA, dataB计算出来,就认为dataC包含的信息与dataA, dataB包含的信息重复;
App的功能模块一般包括文件、显示、网络、声音;
模块 文件 显示 网络
文件功能的软件应用层:
显示功能的软件应用层:创建与销毁视图、切换显示与隐藏、输入数据(比如文字)、填充或提取数据、动画、页面跳转;
事件的定义:由于用户或某个设备向程序输入的数据满足某个条件,引发了监听器方法的执行,称之为一次事件;
这三个功能的事件如下:
哲理2:任何有数据输入的功能模块都可能引发事件;程序执行的动机是事件,即事件推动了程序执行;
对于这三个功能:
双向绑定的定义:在显示模块中,视图组件与显示模块数据相关联;视图组件包含的数据变化时,即时更新到显示模块数据;显示模块数据变化时,即时更新视图组件;
除了即时更新的方式提取数据,就是临时从视图组件提取数据;
应用场景:双向绑定应用于可编辑的列表;
A.例子一:
B.例子二:列表的数据删除一项,需要调用notifyDatasetChanged()即时更新视图组件;
1.内部结构与外部关系的哲理
哲理3:任何一个本体都具有内部结构与外部关系,一内一外构成其整体;
[如图-本体的内部结构与外部关系]
定义:当方法A内,调用两个或两个以上其他方法(B1、B2、…Bn)时,方法A就是方法B1、B2、…Bn之间的一种外部关系;
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()的内部结构;
六.1中所定义的文件、显示、网络等模块下面称为“基础模块”;
基础模块的外部关系的定义:
单纯性的定义:某个基础模块内部没有直接调用其他基础模块,就称这个基础模块满足单纯性;
基础模块的单纯化重构:
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的显示模块进行单纯化重构:
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");
这两个语句;它们一个属于显示模块、一个属于文件模块;
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()是显示模块与文件模块的外部关系;
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); }); } }
例子C:
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;
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可见:
例子D:
A: (paramA)-> {...A, B, C, D}, B: (paramB)-> {...A, B, C, D}, C: (paramC)-> {...A, B, C, D};
根据结论2,单纯化重构A之后,(paramA)-> {...}这个事件外部关系处于中间类中;
{...}这个事件外部关系也处于中间类中;
{...}, (paramB)-> {...}, (paramC)-> {...}这三个事件外部关系都处于中间类中;
例子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(); } }
基础模块初始化的外部关系,下面称为“初始化外部关系”;
根据结论3与结论4,可得到:
例子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,可得到:
定义:对某个类的拆解封装重构,是要将类中的除了构造方法外的所有private方法,进行拆解封装到调用它们的方法中,最后类中只剩下public、protected以及没有修饰符的方法;
如果对程序进行单纯化重构,得到中间类,再对中间类进行拆解封装重构;
根据哲理2,事件是程序执行的动机,那么有如下结论:
init() > functionAaa() > functionBbb() ... > functionEee() ... > functionXxx()
或者
onEvent() > functionAaa() > functionBbb() ... > functionEee() ... > functionXxx()
,即init()或者onEvent()处于栈底,而functionEee()处于栈中;
下面证明,关于中间类性质的,以及关于“外部关系模块”的概念的结论8;
证明:由结论5可知,单纯化重构之后,中间类包含一个初始化外部关系,并且包含所有事件外部关系;
任何Java桌面应用程序,都可以进行单纯化重构、并且拆解封装重构,得到例子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 }; } }
也就是,程序是按照这个步骤执行的:创建模块、设置监听器、初始化,然后等待事件的发生来执行其他代码;
任何Android应用程序都有例子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 BaseActivityextends 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{ 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) -> { ... }; } }
业务数据在外部关系模块中,业务数据经过单纯计算,得到其他基础模块能够直接使用的数据(有时不需要单纯计算这一步);
A.例子一
数据流图形如下:
[如图-数据与单纯计算所属模块]
见附件文件:ProgramStructureGPS.20190922.zip,这是一个Android项目的压缩文件;
20190924.zip