DEV Community

Cover image for Understanding Uncle Bob’s Component Principles
ANUPAM SINGH
ANUPAM SINGH

Posted on

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-scanner

core-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-scanner

The 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
└── BluetoothScanner

Now every feature that needs BLE scanning must copy the code.

Better Example
Move scanner logic to a separate component.

core-ble-scanner
├── BleScanner
├── ScanManager
└── BluetoothRepository

Now another feature can reuse it.

feature-ble-scanner
feature-device-pairing
feature-device-diagnostics

All 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
BluetoothRepository

All these classes belong to scanning.

They change for the same reason.

Therefore they belong together.

core-ble-scanner
├── BleScanner
├── ScanManager
└── BluetoothRepository

The 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
└── DeviceCard

Now 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
└── ScanManager
feature-ble-scanner
├── ScanScreen
├── DeviceCard
└── DeviceList

Each 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-scanner

How can this happen?

Become a Medium member
Suppose:

_core-ble-scanner
_calls

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-scanner

Only 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:

RecyclerView
Tomorrow:

Jetpack Compose
Next month:

Different design
UI is unstable.

BLE scanning logic changes less often.

BleScanner
ScanManager
BluetoothRepository

are relatively stable.

Therefore:

feature-ble-scanner

core-ble-scanner

is correct.

Not:

core-ble-scanner

feature-ble-scanner

Lesson
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 AndroidBleScanner
Expose an interface.

interface BleScanner {
fun startScan()
fun stopScan()
}

Implementation:

class AndroidBleScanner : BleScanner
Now future implementations can be added.

FakeBleScanner
MockBleScanner
AndroidBleScanner

without 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
└── ViewModel

Dependency direction:

feature-ble-scanner

core-ble-scanner

This 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.

Top comments (0)