Six design principles of software development
The open closed principle
Open for extensions, closed for modifications. The purpose is that when we need to expand a function, we should not modify the original code, but to achieve this purpose through other methods, in fact, the other methods, is to use interfaces or abstract classes to expand the parts of the code that are prone to change, when the change is needed, Simply derive a new implementation class based on the requirements.
Richter’s substitution principle
Wherever a base class can appear, a subclass must appear. This requires that a subclass implement abstract methods in its parent class, but not override methods already implemented by the parent class. If you arbitrarily modify a method implemented by a parent class, you may introduce unknown errors.
Dependency inversion principle
A high-level module should not depend on a low-level module; both should depend on its abstraction; Abstractions should not depend on details, details should depend on abstractions. Its core idea is: interface oriented programming
Single responsibility
An object, a module function should be a single, should not assume too many responsibilities, that is, expertise.
Interface Isolation Principle
- A client should not rely on interfaces it does not need
- Dependencies between classes should be established on the smallest interface
If a class implements an interface, but the interface has methods it does not need, it needs to split the interface, extract the methods it needs, and form a new interface for the class to implement. This is the interface isolation principle. In short, that is, all methods in an interface are useful to subclasses of their implementation. Otherwise, the interface continues to be subdivided.
Demeter’s rule
Also known as the Least Knowledge Principle, a class should know the Least about other objects.
- From the perspective of a dependent, it depends only on what it should depend on
- From the perspective of the dependent, only the method of exposure is exposed
Principle of composite reuse
Composition should take precedence over inheritance in software design
The six Design Principles of Software Development guide the practice of software development from a higher dimension, providing methodology for developing maintainable, highly extensible software.
Android componentization
A lot of times, when we talk about componentization, somebody talks about modularity. So what’s the difference and connection between modularity and componentization? Some articles or books say:
- Components are divided from the perspective of functions, such as sharing components, routing components, picture loading components.
- Modules are divided from the perspective of business, such as home page module, order module, personal center module.
But you will find that when people talk about componentization on the Internet, they refer to the routing and order modules mentioned above as components. Without being too literal, I’ve agreed in this summary that a component and a module are the same thing.
Going back to our development process, if a project is developing from scratch, it may encounter this situation after two days. There is no unified Dialog control in the code, and then the leader will say :xx, you encapsulate a unified dialog component. After a while, the leader discovers that there is no unified pull-up list control, and then says: XX, you can make a list component…
Two years have passed, more and more code is in the project, more and more people are involved, the original team expanded to multiple teams to develop, more and more bickering things, and the compilation time has also increased from the original 20 seconds to the present 3 minutes. So the joke began:
- My colleagues in other teams changed the code of my class again, resulting in my error
- The login module and personal center have not been touched for one or two years. Could you please remove this part of the code and open aar so that we can speed up compilation
- I mainly do basic UI control support, change the code and download your project, compile, too time-consuming. I just want to debug the UI controls I wrote.
- .
Let’s summarize the pain points in the iterative process of the above projects:
- The lines of business are too tightly coupled, and there is a risk of errors when the development process involves interdependent functions
- Part of the stable function, no iteration for a long time, but because they are in the same library, will lengthen the compilation time
- Some functions want to run separately
- .
All the problems are finally thrown to the leader, so there are a bunch of technical transformation requirements:
- According to the project
Home page
.goods
.The order
.Personal center
.The login
Several business modules to dividemodule
The development,module
Don’t rely on each other, and tighten code permissions so that there are no code conflicts between different teams The login
This stable module, which will not be changed in the near future, can be directly packaged into aar for other teams to introduce and use- Define a separate module named
ui
All the basic UI controls in the whole project are put here - Define a module named
common
Provides some common business resources for the project, which are depended on by other business modules - Define a unified internal route for components to jump to each other
- .
So, we can see that in a heap, after the completion of the technical renovation of the entire project is divided into a number of the module, the function of the each more single, everyone/each team to focus on their work, also a lot less over trifling thing, and every team can according to their own business development actual situation, choose the desired technology stack. With this done, a componentized architecture emerges. Let’s take a closer look at what problems a componentized architecture solves:
- Each component can be either a component or an application and can be packaged separately for debugging
- Communication between components
- Resource conflicts between components
- Jump between components
Switch between module and APP roles
The manifest file
We expect each business submodule to be configured to run separately, and that’s where the manifest configuration comes in. By default, each module has only one manifest file. During the packaging phase, the packaging tool merges the manifest files in each module to form the manifest file for the entire project. For example, for the personal central Module, there is an activity named PersonaMainActivity that is the entry to the application when it runs an app. For this purpose, the corresponding manifest will have code like the following:
<activity android:name=".view.PersonalMainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Copy the code
However, the PersonalMainActivity module must have been introduced as a submodule when the project was released, and it is unlikely that the PersonalMainActivity is the entry point to the entire app. To do this we need to define two different sets of manifest files, one for when the component is running as an app and the other for when the component is running as a module.
In the root directory of the project we define a file config.gradle that defines a variable moduleAsApp. When this value is false, it means that all business modules in the project are dependent on the main Module as components. When this value is true, The representative business submodule can run as a standalone app. This configuration is then imported into each business submodule.
The config. Gradle file:
ext {
moduleAsApp = false;
appId = [
"app" : "com.fred.routerapp"."order" : "com.fred.order"."product" : "com.fred.product"."personal" : "com.fred.personal"
]
packageNameForAPT = appId.app + ".apt";
androidConfig = [
compileSdkVersion: 29,
buildToolsVersion: "29.0.3",
minSdkVersion : 21,
targetSdkVersion : 29,
versionCode : 1,
versionName : "1.0"]}Copy the code
Going back to our personal central Module, use a different manifest file for each case, as follows:
sourceSets {
main {
if (moduleAsApp) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
java {
// Files in the debug directory do not need to be merged into the main project
exclude '**/debug/**'}}}}Copy the code
For module configuration and applicationId
if (moduleAsApp) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
Copy the code
applicationId:
if (moduleAsApp) {
applicationId appId.personal
}
Copy the code
Resource name conflict
Resource name conflicts can occur during componentized development, if you have a price_detail.xml in the product module to display a price-related view and a price_detail.xml in the ORDER module. For this problem, we can use uniform naming method, such as prefix, all these resources in the product module with product_ prefix, all the order module with order_ prefix, so as to avoid certain program. Of course, if only for layout XML files can be configured in gradle file to restrict
android {
...
resourcePrefix "personal". }Copy the code
This approach does not mean that you will manually change your filename at compile time to prefix it with personal_. Just add this configuration, and the compiler will give you a warning if your name is not valid.
For resource naming conflicts, we need to focus not only on layout files, but also names like Color, Anim, dimen, etc
Communication between components
The communication between components here mainly includes two aspects: one is the notification message between businesses. For example, in the order module, when an order is submitted, the shopping cart needs to be notified to refresh the list of goods in the shopping cart. For this type of message notification, we can use message bus such as EventBus and RxBus. Another communication is the underlying data between components. For example, in the order module, users need to decide whether to log in or not when placing an order. From the principle of single responsibility, users’ login information is only available in the personal center module. So how does the personal-centric module provide user-specific data to other modules?
Remember commonModule we mentioned earlier? It can be relied on by all other business submodules, so we define an interface IAccountService in CommonModule
public interface IAccountService {
public boolean isLogin(a); . }Copy the code
The personal center module has its implementation class
public class AccountServiceImpl implements IAccountService {
@Override
public boolean isLogin(a) {
return false; }}Copy the code
In the Common module there is a ServiceManager class, which is the manager of a service and holds an AccountService as follows:
public class ServiceManager {
private ServiceManager(a) {}
private IAccountService accountService;
private static class AppConfigurationHolder {
private static final ServiceManager instance = new ServiceManager();
}
public static ServiceManager getInstance(a) {
return AppConfigurationHolder.instance;
}
public void setAccountService(IAccountService as) {
this.accountService = as;
}
public IAccountService getAccountService(a) {
return this.accountService; }}Copy the code
We expect other modules by calling the ServiceManager. GetInstance () getAccountService () can get the user information service. If so, when will AcccountService be injected? Let’s look again at the architectural dependencies of apps
AccountService
The implementation of this service is in the personal-centric Module, and we are not sure in what scenario the user will invoke this service. For this type of service, it needs to be injected into the app at startup. whileAccountService
It can only be instantiated in the personal-central module. For this purpose, the personal-central module must be able to listen for the initialization time of the Application, i.e., the ApplicationonCreate
methods
Listen for the status of the Application
Define an interface in a common component that is implemented by other business Modules.
public interface AppStateListener {
void onCreate(a);
void onLowMemory(a);
}
Copy the code
The personal center module will have a class that will create an AccountService in onCreate and inject it into the ServiceManager
public class PersonalAppStatusListener implements AppStateListener {
@Override
public void onCreate(a) {
ServiceManager.getInstance().setAccountService(new AccountServiceImpl(AppStateManager.getInstance().getApplication()));
}
@Override
public void onLowMemory(a) {}}Copy the code
In the Common module, there is a class that maintains all child business Modules listening for Application state
public class AppStateManager {
private List<AppStateListener> lifeCycleListenerList;
private AppStateManager(a){}
private Application application;
public void init(Context context) {
this.application = (Application) context;
initAppLifeCycleListener();
}
public void initAppLifeCycleListener(a) {
// Define the class name of the listener for all modules
String [] names = Constants.moduleLifeCycleListener;
if(names ! =null && names.length > 0) {
lifeCycleListenerList = new ArrayList<>();
}
for (int i = 0; i < names.length; i ++) {
try {
Class clazz = Class.forName(names[i]);
if(AppStateListener.class.isAssignableFrom(clazz)) { lifeCycleListenerList.add((AppStateListener) clazz.newInstance()); }}catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch(InstantiationException e) { e.printStackTrace(); }}}public Application getApplication(a) {
return this.application;
}
public void onCreate(a) {
if(lifeCycleListenerList ! =null) {
for(AppStateListener listener : lifeCycleListenerList) { listener.onCreate(); }}}public void onLowMemory(a) {
if(lifeCycleListenerList ! =null) {
for(AppStateListener listener : lifeCycleListenerList) { listener.onLowMemory(); }}}private static class Instance{
public static AppStateManager INSTANCE = new AppStateManager();
}
public static AppStateManager getInstance(a) {
returnAppStateManager.Instance.INSTANCE; }}Copy the code
The AppStateManager method is called in the Application at the appropriate time to distribute the Application state to the sub-business modules
public class App extends Application {
@Override
public void onCreate(a) {
super.onCreate();
AppStateManager.getInstance().onCreate();
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
AppStateManager.getInstance().init(this);
}
@Override
public void onLowMemory(a) {
super.onLowMemory(); AppStateManager.getInstance().onLowMemory(); }}Copy the code
Let’s go through the whole process in chronological order:
- The user starts the app and executes it first
App
theattachBaseContext
methods attachBaseContext
Method is calledAppStateManager
theinit
Method, which puts all the business Module’sAppStateListener
Load it inApp
theonCreate
Method is calledAppStateManager
In the classonCreate
Method, which will eventually execute all business ModulesAppStateListener
theonCreate
Methods, also including personal center module, personal center moduleAppStateListener
In theonCreate
Method createsAccountService
And will beAccountService
Injected into theServicemanager
In the.
After completion of the above steps, each module can be used ServiceManager. GetInstance () getAccountService () to get data on the user’s information.
routing
Because business modules are not interdependent. So the routing configuration can only be added to the Common module, which maintains a large Map to manage the mapping between specific urls and specific activities. When the app starts, initialize the Map. The idea might look something like this:
Define a RouterManager
public class RouterManager {
private Map<String, String> map = new HashMap<>();
private RouterManager instance;
private RouterManager(a) {}
public static RouterManager getInstance(a) {
return InstanceHolder.instance;
}
public void put(String url, String className) {
map.put(url, className);
}
public void startActivity(Activity activity, String url, Intent intentData) {
try {
Intent intent = new Intent(activity, Class.forName(map.get(url)));
intent.putExtras(intentData);
activity.startActivity(intent);
} catch(ClassNotFoundException e) { e.printStackTrace(); }}private static class InstanceHolder {
public static RouterManager instance = newRouterManager(); }}Copy the code
Adding a Mapping
In the example above we saw that each sub-business module has a listener to listen for the onCreate method of the Application. We can add a mapping between the pages of the respective business module and the URL to that listener
public class PersonalAppStatusListener implements AppStateListener {
@Override
public void onCreate(a) {... RouterManager.getInstance().put("personal/login"."com.fred.personal.view.LoginActivity");
RouterManager.getInstance().put("personal/main"."com.fred.personal.view.PersonalMainActivity");
}
@Override
public void onLowMemory(a) {}}Copy the code
use
To use it, we can turn up the home page of the personal center like this
RouterManager.getInstance().startActivity(activity, "personal/main".null);
Copy the code
The above is just a simple explanation of the idea of implementing a router. The disadvantages of this solution are obvious. Every time a new page is added, you need to manually add it to the overall Map, which needs to be maintained by yourself. That’s why you need a routing component like ARoute, and we’ll see how to implement a routing component ourselves.
confusion
Confused configuration In the main Module, each business component keeps a confused configuration file that is not easy to modify and manage.
Setup of development environment
Although componentization is being done, we expect the sub-modules to be configured in the same environment, such as compileSdk, targetSdk and third-party packages. So we define a config.gradle file in the root directory of the project to configure this information
ext {
moduleAsApp = false;
appId = [
"app" : "com.fred.routerapp"."order" : "com.fred.order"."product" : "com.fred.product"."personal" : "com.fred.personal"
]
packageNameForAPT = appId.app + ".apt";
android = [
compileSdkVersion: 28.buildToolsVersion:'29.0.2'.minSdkVersion : 14.targetSdkVersion : 26.versionName : "2.0".versionCode : 20210510,
]
dependVersion = [
rxJava : "2.1.0.".rxAndroid : '2.0.2'.retrofitSdkVersion : '2.3.0.glideTrans : '4.1.0'.glide : '4.9.0'.room : '2.0.0',
]
retrofitDeps = [
"retrofit" : "com.squareup.retrofit2:retrofit:${dependVersion['retrofitSdkVersion']}"."retrofitConverterGson" : "com.squareup.retrofit2:converter-gson:${dependVersion.retrofitSdkVersion}"."retrofitAdapterRxjava2" : "com.squareup.retrofit2:adapter-rxjava2:${dependVersion.retrofitSdkVersion}"
]
rxJavaDeps = [
"rxJava" : "io.reactivex.rxjava2:rxjava:${dependVersion.rxJava}"."rxAndroid": "io.reactivex.rxjava2:rxandroid:${dependVersion.rxAndroid}",
]
glideDeps = [
"glide" : "com.github.bumptech.glide:glide:${dependVersion.glide}".'glideOKhttp' : "com.github.bumptech.glide:okhttp3-integration:${dependVersion.glide}"."glideTrans" : "jp.wasabeef:glide-transformations:${dependVersion.glideTrans}",
]
roomDeps = [
'room-runtime': "androidx.room:room-runtime:${dependVersion.room}".'room-rxjava': "androidx.room:room-rxjava2:${dependVersion.room}"."room-compiler": "androidx.room:room-compiler:${dependVersion.room}"
]
kotlinDeps = [
"kotlin" : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"."kotlin-coroutines" : "Org. Jetbrains. Kotlinx: kotlinx coroutines -- core: 1.3.2." "
]
compactDeps = [
"recyclerview" : 'androidx. Recyclerview: recyclerview: 1.0.0'.'constraintlayout' : 'androidx. Constraintlayout: constraintlayout: 1.1.3'.'cardview' :'androidx. Cardview: cardview: 1.0.0'.'compact' : 'androidx. Appcompat: appcompat: 1.0.0'
]
commonDeps = [
'multidex' : 'androidx. Multidex: multidex: 2.0.0'."okHttp" : 'com. Squareup. Okhttp3: okhttp: 3.12.0'.'eventBus' : 'org. Greenrobot: eventbus: 3.0.0'.'statusBar' : 'com. Jaeger. Statusbarutil: library: 1.5.0'.'gson' : 'com. Google. Code. Gson: gson: 2.3.1'.'wechatShare' : 'com. Tencent. Mm. Opensdk: wechat - SDK - android - without - the mta: 6.6.4'.'bugly' : 'com. Tencent. Bugly: crashreport: 3.2.3'
]
retrofitLibs = retrofitDeps.values()
rxJavaLibs = rxJavaDeps.values()
glideLibs = glideDeps.values()
roomLibs = roomDeps.values()
kotlinLibs = kotlinDeps.values()
compactLibs = compactDeps.values()
commonLibs = commonDeps.values()
}
Copy the code
Apply from “config.gradle” to the build.gradle of the project, and then add the corresponding dependencies such as API rxJavaLibs to the build.gradle of each sub-module. This enables unification of globally dependent versions.
I always think that componentization is not a new technology, but more of a project architecture method. This method can be more convenient to manage the code, so that our code can be integrated in a modular way to meet the needs of specific scenes.
- Use interface – oriented programming to remove interdependencies between components
- When a module can be run independently during development, compilation time is greatly reduced
- When the granularity of the module is small enough, it will have better applicability, such as extracting a shared module and log module, which can be applied to multiple apps
reference
- C.biancheng.net/view/1322.h…
- C.biancheng.net/view/1331.h…
- Blog.csdn.net/rocketeerli…