Mastering Android Module Design: Cohesion, Coupling, and Component Principles
This article explains how to organize Android Gradle modules using component design principles—covering cohesion, coupling, module splitting, encapsulation, and integration—while relating them to SOLID and providing concrete examples, formulas, and best‑practice guidelines.
Component Design Principles Overview
In Android a component corresponds to a Gradle module that groups business‑related files. Proper modularisation aims for high cohesion (related code stays together) and low coupling (modules depend only on stable abstractions).
1. Cohesion Principles
Common Closure Principle (CCP)
Classes that change for the same reason should be placed in the same module. A module therefore has a single reason to change, mirroring the SRP at the module level.
Common Reuse Principle (CRP)
Classes that are reused together belong to the same module, while unrelated classes must not be forced into the same module. This reduces unnecessary recompilation and aligns with the ISP for modules.
Reuse/Release Equivalency Principle (REP)
The granularity of reuse should match the granularity of release. When a module is published as a binary library, its version should be managed as a single unit. Using a Bill of Materials (BOM) simplifies version alignment, e.g.:
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-moshi:2.9.0"Gradle’s BOM support allows adding dependencies without specifying versions explicitly.
2. Coupling Principles
Acyclic Dependencies Principle (ADP)
Modules must not form dependency cycles. Gradle detects cycles at compile time. Typical resolutions are:
Extract a new common module to break the cycle.
Apply the Dependency Inversion Principle (depend on abstractions instead of concrete modules).
Stable Dependencies Principle (SDP)
Dependencies should point from unstable to stable modules. Stability is measured by Instability : I = fanOut / (fanIn + fanOut) Fan‑in : number of modules that depend on this module (higher → more responsibility).
Fan‑out : number of modules this module depends on (higher → less independent).
Lower I indicates a more stable module.
Stable Abstractions Principle (SAP)
Stable modules should be more abstract. Abstractness A is calculated as:
A = Na / Nc Nc: total classes in the module. Na: number of abstract classes or interfaces.
Balancing I and A yields three zones:
Zone of Pain : high stability, low abstractness – hard to change and extend.
Zone of Useless : high instability, high abstractness – rarely used.
Main Sequence : ideal balance where A + I ≈ 1.
3. Module Splitting Strategies
Layer‑based Splitting
Divides the codebase into presentation, domain, and data layers. Easy to adopt but often violates cohesion and coupling because a single feature may span all layers, leading to large monolithic modules.
Feature‑based Splitting
Creates a separate module for each feature. Advantages:
Isolated changes and clear project purpose.
Independent team ownership.
Parallel Gradle compilation reduces build time.
Drawbacks include reduced reusability and potential violations of SDP/SAP, which can lead to large feature modules.
Domain‑based (Component) Splitting
Separates UI modules from domain‑data component modules. Shared code (e.g., networking utilities) lives in a shared directory.
interface PDPNavigator {
// adapt for fragments, Navigation Component, Compose, etc.
fun navigateToCart(activity: Activity)
fun navigateToWishlist(activity: Activity)
}The app module implements these interfaces, providing navigation glue:
class AppNavigator : PDPNavigator, WishlistNavigator, CartNavigator {
override fun navigateToCart(activity: Activity) { /* ... */ }
override fun navigateToWishlist(activity: Activity) { /* ... */ }
}4. Encapsulation of Modules
Expose only the API needed by other modules as public; keep the rest internal. Prefer Gradle implementation over api to avoid leaking transitive dependencies.
For Dagger modules, mark them internal because they are used only for code generation within the same module.
// Dagger module for a Wishlist Component
@Module
@InstallIn(SingletonComponent::class)
internal object WishlistComponentModule {
@Provides
fun provideAddToWishlistUseCase(impl: AddToWishlistUseCaseImpl): AddToWishlistUseCase = impl
// ... other providers
}
// Dagger module for a Wishlist UI module
@Module
@InstallIn(ActivityComponent::class)
internal object WishlistUIModule {
@Provides
fun provideSomeDependency(impl: SomeDependencyImpl): SomeDependency = impl
// ... other providers
}5. Integration Layer
The app module acts as the integration layer. It contains:
Glue code that connects feature modules.
Framework initialization (e.g., Dagger, logging).
Navigation logic between UI modules.
Provision of common dependencies such as database or network clients.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
