Understanding Uncle Bob’s Component Principles
Many developers learn Uncle Bob’s Component Principles from theory but struggle to understand where they apply in real projects.
Let’s take a simple Android BLE Scanner application as an example.
Imagine our project has two modules:
core-ble-scanner
feature-ble-scannercore-ble-scanner
Contains:
- BLE scanning logic
- Scan manager
- Bluetooth APIs
- Device discovery logic
feature-ble-scanner
Contains:
- Scan screen
- RecyclerView
- Compose UI
- Device list display
- User interactions
Architecture:
feature-ble-scanner
↓
core-ble-scannerThe UI depends on the scanner logic.
1. Reuse/Release Equivalence Principle (REP)
Idea
If something can be reused, it should be packaged as a reusable component.
Bad Example
BLE scanning code is written directly inside the UI module.
feature-ble-scanner
├── ScanScreen
├── DeviceList
└── BluetoothScannerNow every feature that needs BLE scanning must copy the code.
Better Example
Move scanner logic to a separate component.
core-ble-scanner
├── BleScanner
├── ScanManager
└── BluetoothRepositoryNow another feature can reuse it.
feature-ble-scanner
feature-device-pairing
feature-device-diagnosticsAll use the same scanner component.
Lesson
Reusable code should live in its own reusable component.
2. Common Closure Principle (CCP)
Idea
Things that change together should stay together.
Example
Suppose Android introduces a new BLE scanning API.
What changes?
BleScanner
ScanManager
BluetoothRepositoryAll these classes belong to scanning.
They change for the same reason.
Therefore they belong together.
core-ble-scanner
├── BleScanner
├── ScanManager
└── BluetoothRepositoryThe UI doesn’t change.
So it should stay separate.
Lesson
Keep BLE logic together because it changes together.
3. Common Reuse Principle (CRP)
Idea
Don’t force modules to depend on things they don’t need.
Bad Example
core-ble-scanner
├── BleScanner
├── ScanManager
├── ScanScreen
├── DeviceAdapter
└── DeviceCardNow another feature only needs scanning.
But it must also depend on:
- Compose UI
- RecyclerView
- UI resources
which it doesn’t need.
Better Example
core-ble-scanner
├── BleScanner
└── ScanManagerfeature-ble-scanner
├── ScanScreen
├── DeviceCard
└── DeviceListEach module depends only on what it needs.
Lesson
Separate reusable logic from UI.
4. Acyclic Dependencies Principle (ADP)
Idea
Dependencies should not form circles.
Bad Example
feature-ble-scanner
↓
core-ble-scanner
↓
feature-ble-scannerHow can this happen?
Get Anuanu’s stories in your inbox
Join Medium for free to get updates from this writer.
Suppose:
core-ble-scannercalls
ScanScreen.showError()Now the core module depends on the feature module.
And the feature module already depends on core.
We created a cycle.
Better Example
feature-ble-scanner
↓
core-ble-scannerOnly one direction.
Core never knows about UI.
Lesson
Core modules should not depend on feature modules.
5. Stable Dependencies Principle (SDP)
Idea
Depend on stable things.
Example
UI changes frequently.
Maybe today:
RecyclerViewTomorrow:
Jetpack ComposeNext month:
Different designUI is unstable.
BLE scanning logic changes less often.
BleScanner
ScanManager
BluetoothRepositoryare relatively stable.
Therefore:
feature-ble-scanner
↓
core-ble-scanneris correct.
Not:
core-ble-scanner
↓
feature-ble-scannerLesson
Unstable UI should depend on stable business logic.
6. Stable Abstractions Principle (SAP)
Idea
Stable modules should expose abstractions.
Example
Instead of exposing implementation:
class AndroidBleScannerExpose an interface.
interface BleScanner {
fun startScan()
fun stopScan()
}Implementation:
class AndroidBleScanner : BleScannerNow future implementations can be added.
FakeBleScanner
MockBleScanner
AndroidBleScannerwithout changing consumers.
Lesson
Stable components should expose interfaces, not concrete implementations.
Final Architecture
core-ble-scanner
│
├── BleScanner
├── AndroidBleScanner
├── ScanManager
├── BluetoothRepository
└── Models
feature-ble-scanner
│
├── ScanScreen
├── DeviceList
├── DeviceCard
└── ViewModelDependency direction:
feature-ble-scanner
↓
core-ble-scannerThis architecture follows all six of Uncle Bob’s Component Principles:
- Reusable scanner logic is isolated
- BLE-related changes stay together
- UI is not forced on other features
- No circular dependencies
- UI depends on stable logic
- Stable logic exposes abstractions
And that’s exactly why many modern Android projects separate core modules from feature modules. This isn’t just about keeping code organized — it makes the application easier to maintain, test, and extend as new BLE features are added.




