docs(architecture): add design pattern UML diagrams

This commit is contained in:
5803024019 2026-05-21 09:11:49 +07:00
parent 31df3bd590
commit 5865951153
15 changed files with 1122 additions and 0 deletions

View File

@ -0,0 +1,94 @@
' ============================================================
' WALKGUIDE — DESIGN PATTERNS (GoF)
' Flutter × Spring Boot × In-Device AI
' Pattern 1 of 7: BUILDER (Creational)
' ============================================================
@startuml WalkGuide_01_Builder
skinparam monochrome false
skinparam shadowing false
skinparam defaultFontName Arial
skinparam defaultFontSize 12
skinparam roundCorner 10
skinparam ArrowColor #555555
skinparam ArrowThickness 1.2
skinparam class {
BackgroundColor #FAFAFA
BorderColor #AAAAAA
HeaderBackgroundColor #E8E8E8
FontColor #222222
StereotypeFontColor #666666
AttributeFontColor #333333
}
skinparam package {
BackgroundColor #F5F5F5
BorderColor #888888
FontColor #333333
FontStyle bold
}
skinparam note {
BackgroundColor #FFFDE7
BorderColor #F9A825
FontColor #444444
FontSize 11
}
' ============================================================
' PATTERN 1 — BUILDER (Creational)
' ============================================================
package "① Builder Pattern [Creational]" #EEF6EE {
interface "UserBuilder\n<<Builder>>" as UserBuilder {
+ email(String) : UserBuilder
+ password(String) : UserBuilder
+ displayName(String) : UserBuilder
+ uniqueUserId(String) : UserBuilder
+ fcmToken(String) : UserBuilder
+ role(Role) : UserBuilder
+ build() : User
}
class "User\n<<Entity>>" as User {
- id : Long
- email : String
- password : String
- displayName : String
- uniqueUserId : String
- fcmToken : String
- role : Role
- createdAt : Timestamp
+ {static} builder() : UserBuilder
}
class "FcmService\n<<Service>>" as FcmService {
+ sendPushNotification(userId, payload)
+ sendSosAlert(guardianId, lat, lng)
- buildMessage(token, title, body) : Message
}
class "AuthService\n<<Service>>" as AuthService {
+ register(RegisterRequest) : AuthDataResponse
+ login(LoginRequest) : AuthDataResponse
- buildAuthResponse(user, tokens) : AuthDataResponse
}
UserBuilder ..> User : <<creates>>
User ..> UserBuilder : <<returns>>
FcmService ..> "Firebase\nMessage.builder()" : uses
AuthService ..> "AuthDataResponse\n.builder()" : uses
}
note bottom of UserBuilder
Lombok @Builder pada User.java
Memisahkan konstruksi objek kompleks
dari representasinya. Optional fields
(displayName, uniqueUserId, fcmToken)
tidak perlu constructor overloading.
end note
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -0,0 +1,110 @@
' ============================================================
' WALKGUIDE — DESIGN PATTERNS (GoF)
' Flutter × Spring Boot × In-Device AI
' Pattern 2 of 7: SINGLETON (Creational)
' ============================================================
@startuml WalkGuide_02_Singleton
skinparam monochrome false
skinparam shadowing false
skinparam defaultFontName Arial
skinparam defaultFontSize 12
skinparam roundCorner 10
skinparam ArrowColor #555555
skinparam ArrowThickness 1.2
skinparam class {
BackgroundColor #FAFAFA
BorderColor #AAAAAA
HeaderBackgroundColor #E8E8E8
FontColor #222222
StereotypeFontColor #666666
AttributeFontColor #333333
}
skinparam package {
BackgroundColor #F5F5F5
BorderColor #888888
FontColor #333333
FontStyle bold
}
skinparam note {
BackgroundColor #FFFDE7
BorderColor #F9A825
FontColor #444444
FontSize 11
}
' ============================================================
' PATTERN 2 — SINGLETON (Creational)
' ============================================================
package "② Singleton Pattern [Creational]" #EEF0FF {
class "GetIt\n<<ServiceLocator>>" as GetIt {
- {static} _instance : GetIt
+ {static} instance : GetIt
+ registerSingleton<T>(T)
+ get<T>() : T
}
class "TtsService\n<<Singleton>>" as TtsService {
- {static} _instance : TtsService
- _flutterTts : FlutterTts
+ speak(String text)
+ speakImmediate(String text)
+ stop()
+ setLanguage(String lang)
}
class "SttService\n<<Singleton>>" as SttService {
- {static} _instance : SttService
- _speechToText : SpeechToText
+ startListening()
+ stopListening()
+ onResult : Stream<String>
}
class "YoloDetector\n<<Singleton>>" as YoloDetector {
- {static} _instance : YoloDetector
- _interpreter : Interpreter
- _labels : List<String>
+ loadModel()
+ detect(CameraImage) : List<DetectionResult>
+ isRunning : bool
}
class "WebSocketService\n<<Singleton>>" as WebSocketService {
- {static} _instance : WebSocketService
- _stompClient : StompClient
+ connect(String token)
+ subscribe(String destination)
+ disconnect()
}
class "AgoraService\n<<Singleton>>" as AgoraService {
- {static} _instance : AgoraService
- _engine : RtcEngine
+ joinChannel(token, channel, uid)
+ leaveChannel()
+ muteLocalAudio(bool)
}
GetIt --> TtsService : registers &\nmanages
GetIt --> SttService : registers &\nmanages
GetIt --> YoloDetector : registers &\nmanages
GetIt --> WebSocketService : registers &\nmanages
GetIt --> AgoraService : registers &\nmanages
}
note right of GetIt
injection_container.dart:
sl.registerSingleton<TtsService>(TtsService())
sl.registerSingleton<YoloDetector>(YoloDetector())
sl.registerSingleton<WebSocketService>(WebSocketService())
Satu instance untuk seluruh lifecycle app.
end note
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -0,0 +1,97 @@
' ============================================================
' WALKGUIDE — DESIGN PATTERNS (GoF)
' Flutter × Spring Boot × In-Device AI
' Pattern 3 of 7: FACADE (Structural)
' ============================================================
@startuml WalkGuide_03_Facade
skinparam monochrome false
skinparam shadowing false
skinparam defaultFontName Arial
skinparam defaultFontSize 12
skinparam roundCorner 10
skinparam ArrowColor #555555
skinparam ArrowThickness 1.2
skinparam class {
BackgroundColor #FAFAFA
BorderColor #AAAAAA
HeaderBackgroundColor #E8E8E8
FontColor #222222
StereotypeFontColor #666666
AttributeFontColor #333333
}
skinparam package {
BackgroundColor #F5F5F5
BorderColor #888888
FontColor #333333
FontStyle bold
}
skinparam note {
BackgroundColor #FFFDE7
BorderColor #F9A825
FontColor #444444
FontSize 11
}
' ============================================================
' PATTERN 3 — FACADE (Structural) — Flutter
' ============================================================
package "③ Facade Pattern [Structural]" #FFF8E1 {
class "VoiceCommandHandler\n<<Facade>>" as VoiceCommandHandler {
- _ttsService : TtsService
- _sttService : SttService
- _router : GoRouter
- _walkGuideBloc : WalkGuideBloc
- _sosBloc : SosBloc
- _notifBloc : NotificationBloc
+ processText(String command) : void
- _matchCommand(String) : VoiceCommandKey?
- _executeCommand(VoiceCommandKey) : void
}
class "WalkGuideBloc\n<<Client>>" as WalkGuideBlocFacade {
+ onVoiceCommand(String text)
}
class "GuardianDashboardService\n<<Facade>>" as GuardianDashboardService {
- _locationService : LocationService
- _activityService : ActivityLogService
- _sosService : SosService
- _notifService : NotificationService
+ getDashboard(guardianId) : DashboardResponse
}
class "SttService " as SttServiceFacade <<Subsystem>>
class "TtsService " as TtsServiceFacade <<Subsystem>>
class "GoRouter\n<<Router>>" as GoRouterFacade <<Subsystem>>
class "SosBloc " as SosBlocFacade <<Subsystem>>
class "LocationService\n<<Service>>" as LocationService <<Subsystem>>
class "ActivityLogService\n<<Service>>" as ActivityService <<Subsystem>>
class "SosService\n<<Service>>" as SosServiceFacade <<Subsystem>>
WalkGuideBlocFacade --> VoiceCommandHandler : processText()
VoiceCommandHandler --> SttServiceFacade : delegates
VoiceCommandHandler --> TtsServiceFacade : delegates
VoiceCommandHandler --> GoRouterFacade : delegates
VoiceCommandHandler --> SosBlocFacade : delegates
GuardianDashboardService --> LocationService : aggregates
GuardianDashboardService --> ActivityService : aggregates
GuardianDashboardService --> SosServiceFacade : aggregates
}
note right of VoiceCommandHandler
Client hanya panggil processText("start walkguide")
tanpa perlu tahu kompleksitas di baliknya:
matching phrase → execute command → TTS feedback
→ route navigation → BLoC event dispatch
end note
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -0,0 +1,101 @@
' ============================================================
' WALKGUIDE — DESIGN PATTERNS (GoF)
' Flutter × Spring Boot × In-Device AI
' Pattern 4 of 7: REPOSITORY / PROXY (Structural)
' ============================================================
@startuml WalkGuide_04_Repository_Proxy
skinparam monochrome false
skinparam shadowing false
skinparam defaultFontName Arial
skinparam defaultFontSize 12
skinparam roundCorner 10
skinparam ArrowColor #555555
skinparam ArrowThickness 1.2
skinparam class {
BackgroundColor #FAFAFA
BorderColor #AAAAAA
HeaderBackgroundColor #E8E8E8
FontColor #222222
StereotypeFontColor #666666
AttributeFontColor #333333
}
skinparam package {
BackgroundColor #F5F5F5
BorderColor #888888
FontColor #333333
FontStyle bold
}
skinparam note {
BackgroundColor #FFFDE7
BorderColor #F9A825
FontColor #444444
FontSize 11
}
' ============================================================
' PATTERN 4 — REPOSITORY / PROXY (Structural)
' ============================================================
package "④ Repository Pattern [Structural — Proxy]" #FFF3E0 {
interface "WalkGuideRepository\n<<interface>>" as WalkGuideRepo {
+ startSession() : Either<Failure, void>
+ stopSession() : Either<Failure, void>
+ logObstacle(ObstacleLogRequest) : Either<Failure, void>
+ getObstacleLogs() : Either<Failure, List<ObstacleLog>>
}
interface "ActivityLogRepository\n<<interface>>" as ActivityRepo {
+ getLogs(page) : Either<Failure, List<ActivityLog>>
+ savePending(ActivityLog) : Either<Failure, void>
+ syncPending() : Either<Failure, void>
}
class "WalkGuideRepositoryImpl\n<<Proxy>>" as WalkGuideRepoImpl {
- _remoteDataSource : WalkGuideRemoteDataSource
- _localDataSource : WalkGuideLocalDataSource
- _connectivity : ConnectivityPlus
+ startSession() : Either<Failure, void>
+ logObstacle(req) : Either<Failure, void>
- _isOnline() : bool
}
class "ActivityLogRepositoryImpl\n<<Proxy>>" as ActivityRepoImpl {
- _remoteDataSource : ActivityRemoteDataSource
- _localDataSource : ActivityLocalDataSource
+ getLogs(page) : Either<Failure, List>
+ syncPending() : Either<Failure, void>
}
class "WalkGuideRemoteDataSource\n<<Remote>>" as RemoteDSWalk {
+ startSession() : void
+ logObstacle(req) : void
' POST /api/v1/user/obstacle
}
class "WalkGuideLocalDataSource\n<<SQLite/Drift>>" as LocalDSWalk {
+ cacheObstacle(ObstacleLog) : void
+ getPendingLogs() : List<ObstacleLog>
' Drift ORM — offline first
}
WalkGuideRepo <|.. WalkGuideRepoImpl : implements
ActivityRepo <|.. ActivityRepoImpl : implements
WalkGuideRepoImpl --> RemoteDSWalk : online path
WalkGuideRepoImpl --> LocalDSWalk : offline path
}
note bottom of WalkGuideRepoImpl
Proxy memutuskan sumber data:
if (isOnline) → fetch remote API
else → baca/tulis SQLite (Drift)
Domain layer tidak tahu perbedaannya.
dartz Either<Failure, Data> untuk typed error.
end note
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -0,0 +1,101 @@
' ============================================================
' WALKGUIDE — DESIGN PATTERNS (GoF)
' Flutter × Spring Boot × In-Device AI
' Pattern 5 of 7: OBSERVER (Behavioral)
' ============================================================
@startuml WalkGuide_05_Observer
skinparam monochrome false
skinparam shadowing false
skinparam defaultFontName Arial
skinparam defaultFontSize 12
skinparam roundCorner 10
skinparam ArrowColor #555555
skinparam ArrowThickness 1.2
skinparam class {
BackgroundColor #FAFAFA
BorderColor #AAAAAA
HeaderBackgroundColor #E8E8E8
FontColor #222222
StereotypeFontColor #666666
AttributeFontColor #333333
}
skinparam package {
BackgroundColor #F5F5F5
BorderColor #888888
FontColor #333333
FontStyle bold
}
skinparam note {
BackgroundColor #FFFDE7
BorderColor #F9A825
FontColor #444444
FontSize 11
}
' ============================================================
' PATTERN 5 — OBSERVER (Behavioral)
' ============================================================
package "⑤ Observer Pattern [Behavioral]" #F3E5F5 {
abstract class "Bloc<Event, State>\n<<Subject>>" as BlocSubject {
# stateController : StreamController<State>
+ {abstract} on<E>(EventHandler)
+ add(Event event)
+ emit(State state)
+ stream : Stream<State>
}
class "WalkGuideBloc\n<<ConcreteSubject>>" as WalkGuideBlocObs {
+ on<StartWalkGuide>(_onStart)
+ on<StopWalkGuide>(_onStop)
+ on<CameraFrameReceived>(_onFrame)
+ on<ObstacleDetected>(_onObstacle)
- _yoloDetector : YoloDetector
- _ttsService : TtsService
- _hapticService : HapticService
}
class "BlocBuilder<WalkGuideBloc, WalkGuideState>\n<<Observer / Widget>>" as BlocBuilderWidget {
+ builder(ctx, state) : Widget
' Rebuilds UI on every state emission
}
class "BlocListener<WalkGuideBloc, WalkGuideState>\n<<Observer / SideEffect>>" as BlocListenerWidget {
+ listener(ctx, state) : void
' Side effects: TTS, haptic, navigation
}
class "WebSocketService\n<<Subject>>" as WebSocketObs {
- _stompClient : StompClient
+ subscribe(destination) : Stream<dynamic>
+ onLocationUpdate : StreamController
+ onSosAlert : StreamController
+ onNotification : StreamController
}
class "GuardianMapScreen\n<<Observer>>" as GuardianMapObs {
+ onLocationUpdate(LocationData)
' Updates flutter_map markers in real-time
}
BlocSubject <|-- WalkGuideBlocObs : extends
WalkGuideBlocObs --> BlocBuilderWidget : emits state\n(rebuilds UI)
WalkGuideBlocObs --> BlocListenerWidget : emits state\n(side effects)
WebSocketObs --> GuardianMapObs : notifies\nlive location
}
note left of WebSocketObs
Guardian subscribe topic:
/topic/location/{userId} — live map
/queue/sos/{guardianId} — SOS alert
/queue/notif/{userId} — notifications
STOMP over SockJS (stomp_dart_client)
end note
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -0,0 +1,99 @@
' ============================================================
' WALKGUIDE — DESIGN PATTERNS (GoF)
' Flutter × Spring Boot × In-Device AI
' Pattern 6 of 7: STRATEGY (Behavioral)
' ============================================================
@startuml WalkGuide_06_Strategy
skinparam monochrome false
skinparam shadowing false
skinparam defaultFontName Arial
skinparam defaultFontSize 12
skinparam roundCorner 10
skinparam ArrowColor #555555
skinparam ArrowThickness 1.2
skinparam class {
BackgroundColor #FAFAFA
BorderColor #AAAAAA
HeaderBackgroundColor #E8E8E8
FontColor #222222
StereotypeFontColor #666666
AttributeFontColor #333333
}
skinparam package {
BackgroundColor #F5F5F5
BorderColor #888888
FontColor #333333
FontStyle bold
}
skinparam note {
BackgroundColor #FFFDE7
BorderColor #F9A825
FontColor #444444
FontSize 11
}
' ============================================================
' PATTERN 6 — STRATEGY (Behavioral)
' ============================================================
package "⑥ Strategy Pattern [Behavioral]" #E8F5E9 {
interface "ObstacleAlertStrategy\n<<Strategy>>" as AlertStrategy {
+ alert(DetectionResult result) : void
}
class "TtsOnlyStrategy\n<<ConcreteStrategy>>" as TtsOnly {
- _ttsService : TtsService
+ alert(result) : void
' TTS: "Caution! {label} {direction}. {distance}."
}
class "TtsWithHapticStrategy\n<<ConcreteStrategy>>" as TtsHaptic {
- _ttsService : TtsService
- _hapticService : HapticService
+ alert(result) : void
' TTS + vibration pattern
}
class "HapticOnlyStrategy\n<<ConcreteStrategy>>" as HapticOnly {
- _hapticService : HapticService
+ alert(result) : void
' Vibration only (silent environment)
}
class "ObstacleAnalyzer\n<<Context>>" as ObstacleAnalyzerCtx {
- _strategy : ObstacleAlertStrategy
- _aiConfig : AiConfig
+ setStrategy(ObstacleAlertStrategy)
+ analyze(List<DetectionResult>) : void
+ prioritize(results) : DetectionResult
+ calculateDirection(bbox) : Direction
}
class "AiConfig\n<<Config>>" as AiConfigStrategy {
+ alertMode : AlertMode
+ confidenceThreshold : double
+ maxInferenceFps : int
+ enabledLabels : List<String>
}
AlertStrategy <|.. TtsOnly : implements
AlertStrategy <|.. TtsHaptic : implements
AlertStrategy <|.. HapticOnly : implements
ObstacleAnalyzerCtx --> AlertStrategy : uses
ObstacleAnalyzerCtx ..> AiConfigStrategy : reads alertMode\nto select strategy
}
note right of ObstacleAnalyzerCtx
Guardian dapat ganti strategy dari dashboard:
alertMode = TTS_ONLY | TTS_HAPTIC | HAPTIC_ONLY
PUT /api/v1/guardian/ai-config
Strategy dipilih runtime tanpa ubah kode utama.
end note
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View File

@ -0,0 +1,137 @@
' ============================================================
' WALKGUIDE — DESIGN PATTERNS (GoF)
' Flutter × Spring Boot × In-Device AI
' Pattern 7 of 7: CHAIN OF RESPONSIBILITY (Behavioral)
' ============================================================
@startuml WalkGuide_07_ChainOfResponsibility
skinparam monochrome false
skinparam shadowing false
skinparam defaultFontName Arial
skinparam defaultFontSize 12
skinparam roundCorner 10
skinparam ArrowColor #555555
skinparam ArrowThickness 1.2
skinparam class {
BackgroundColor #FAFAFA
BorderColor #AAAAAA
HeaderBackgroundColor #E8E8E8
FontColor #222222
StereotypeFontColor #666666
AttributeFontColor #333333
}
skinparam package {
BackgroundColor #F5F5F5
BorderColor #888888
FontColor #333333
FontStyle bold
}
skinparam note {
BackgroundColor #FFFDE7
BorderColor #F9A825
FontColor #444444
FontSize 11
}
' ============================================================
' PATTERN 7 — CHAIN OF RESPONSIBILITY (Behavioral)
' ============================================================
package "⑦ Chain of Responsibility [Behavioral]" #FCE4EC {
' --- Flutter: Dio Interceptor Chain ---
abstract class "Interceptor\n<<Handler>>" as DioInterceptor {
+ onRequest(options, handler)
+ onResponse(response, handler)
+ onError(error, handler)
}
class "AuthInterceptor\n<<ConcreteHandler>>" as AuthInterceptor {
- _secureStorage : FlutterSecureStorage
+ onRequest(options, handler)
' Attach Bearer token ke setiap request
+ onError(error, handler)
' Jika 401: refresh token → retry request
}
class "ErrorInterceptor\n<<ConcreteHandler>>" as ErrorInterceptor {
+ onError(error, handler)
' Map HTTP error codes → Failure domain objects
' 401 → UnauthorizedFailure
' 404 → NotFoundFailure
' 500 → ServerFailure
}
class "LogInterceptor\n<<ConcreteHandler>>" as LogInterceptorFlutter {
+ onRequest(options, handler)
+ onResponse(response, handler)
+ onError(error, handler)
' Print request/response untuk debugging
}
class "ApiClient\n<<Dio>>" as ApiClient {
- _dio : Dio
+ get(url) : Response
+ post(url, data) : Response
+ put(url, data) : Response
' Interceptors dieksekusi berurutan
}
' --- Backend: Spring Security Filter Chain ---
abstract class "OncePerRequestFilter\n<<Handler>>" as SpringFilter {
+ {abstract} doFilterInternal(req, res, chain)
+ doFilter(req, res, chain)
}
class "JwtAuthFilter\n<<ConcreteHandler>>" as JwtAuthFilter {
- _jwtUtil : JwtUtil
- _userDetailsService : UserDetailsService
+ doFilterInternal(req, res, chain)
' Extract JWT → validate → set SecurityContext
' Jika invalid: 401 Unauthorized langsung
}
class "SecurityConfig\n<<Config/Handler>>" as SecurityConfigChain {
+ securityFilterChain(http) : SecurityFilterChain
' /auth/** → permitAll
' /api/v1/** → authenticated
' ROLE_GUARDIAN vs ROLE_USER routing
}
class "Controller\n<<Endpoint>>" as ControllerChain {
+ handleRequest(request) : ResponseEntity
' Request tiba hanya jika lolos semua filter
}
DioInterceptor <|-- AuthInterceptor : extends
DioInterceptor <|-- ErrorInterceptor : extends
DioInterceptor <|-- LogInterceptorFlutter : extends
ApiClient --> AuthInterceptor : chain[0]
AuthInterceptor --> ErrorInterceptor : passes to\nchain[1]
ErrorInterceptor --> LogInterceptorFlutter : passes to\nchain[2]
SpringFilter <|-- JwtAuthFilter : extends
JwtAuthFilter --> SecurityConfigChain : passes to
SecurityConfigChain --> ControllerChain : passes to\n(if authorized)
}
note bottom of ApiClient
Flutter — Dio interceptor chain:
HTTP Request → AuthInterceptor → ErrorInterceptor → LogInterceptor → Response
Setiap handler bisa: proses, modifikasi, atau blok request.
AuthInterceptor otomatis refresh JWT jika expired (401).
end note
note bottom of JwtAuthFilter
Backend — Spring Security filter chain:
HTTP Request → JwtAuthFilter → SecurityConfig → Controller
JwtAuthFilter extract & validasi Bearer token setiap request.
Jika invalid → langsung 401 tanpa lanjut ke controller.
end note
@enduml

383
ooad-docs/CLASS_MAPPING.md Normal file
View File

@ -0,0 +1,383 @@
# Mapping Class per Design Pattern — WalkGuide
> Dokumen ini memetakan setiap class dari **Class Diagram WalkGuide** ke design pattern yang menggunakannya, beserta peran class tersebut dalam pattern dan justifikasi pemilihannya.
---
## Daftar Isi
1. [Matriks Ringkasan](#1-matriks-ringkasan)
2. [Builder Pattern](#2-builder-pattern--creational)
3. [Singleton Pattern](#3-singleton-pattern--creational)
4. [Facade Pattern](#4-facade-pattern--structural)
5. [Repository / Proxy Pattern](#5-repository--proxy-pattern--structural)
6. [Observer Pattern](#6-observer-pattern--behavioral)
7. [Strategy Pattern](#7-strategy-pattern--behavioral)
8. [Chain of Responsibility Pattern](#8-chain-of-responsibility-pattern--behavioral)
---
## 1. Matriks Ringkasan
| Pattern | Kategori | Class Utama (dari Class Diagram) |
|---------|----------|----------------------------------|
| Builder | Creational | `User`, `AuthService`, `FcmService`, `AuthDataResponse` |
| Singleton | Creational | `TtsService`, `SttService`, `YoloDetector`, `WebSocketService`, `AgoraService`, `HapticService` |
| Facade | Structural | `VoiceCommandHandler`, `GuardianDashboardService`, `GuardianController`, `VoiceCommandConfig` |
| Repository (Proxy) | Structural | `WalkGuideRepositoryImpl`, `ActivityLogRepositoryImpl`, `LocationRepositoryImpl`, `ObstacleLogRepository`, `ActivityLogRepository` |
| Observer | Behavioral | `WalkGuideBloc`, `SosBloc`, `WebSocketService`, `LocationBroadcaster`, `GuardianNotificationRepository` |
| Strategy | Behavioral | `ObstacleAnalyzer`, `AiConfig`, `AiConfigService`, `TtsService`, `HapticService` |
| Chain of Responsibility | Behavioral | `ApiClient`, `AuthInterceptor`, `ErrorInterceptor`, `JwtAuthFilter`, `SecurityConfig`, `JwtUtil`, `CustomUserDetailsService` |
---
## 2. Builder Pattern — Creational
### Justifikasi
Entitas `User` memiliki **8 field** — tidak semua selalu terisi. Field `uniqueUserId` hanya ada untuk `ROLE_USER`, `fcmToken` di-update setiap login, dan `displayName` bersifat opsional. Tanpa Builder, constructor membutuhkan 8 parameter yang rawan salah urutan dan sulit dibaca. `FcmService` membangun payload Firebase `Message` secara kondisional (beberapa field hanya ada saat SOS, yang lain hanya saat notifikasi biasa) — Builder sangat cocok untuk kasus ini.
### Class yang Terlibat
| Class | Layer | Peran dalam Pattern | Lokasi di Class Diagram |
|-------|-------|---------------------|-------------------------|
| `User` | Backend — Entity | **Product** — objek yang dibangun step-by-step via `@Builder` Lombok | Entity block |
| `AuthService` | Backend — Service | **Director/Client** — memanggil `User.builder()...build()` saat register | Service block |
| `FcmService` | Backend — Service | **Client** — memanggil `Message.builder()` Firebase untuk bangun payload FCM | Service block |
| `AuthDataResponse` | Backend — DTO | **Product 2** — response token dibangun via `AuthDataResponse.builder()` | DTO block |
| `RefreshToken` | Backend — Entity | **Product 3** — dibuat via builder saat generate refresh token | Entity block |
### Relasi Antar Class
```
AuthService ──uses──► User.builder()
.email()
.password()
.displayName()
.uniqueUserId()
.role()
.build()
User (Entity tersimpan ke UserRepository)
FcmService ──uses──► Message.builder()
.setToken()
.setNotification()
.putData("type", "SOS_ALERT")
.setAndroidConfig()
.build()
FirebaseMessage (dikirim via FCM)
```
---
## 3. Singleton Pattern — Creational
### Justifikasi
WalkGuide memiliki beberapa **resource-heavy services** yang harus ada tepat satu instance:
- `YoloDetector` memuat model `.tflite` ±6 MB ke memori — jika dibuat ulang setiap buka layar akan terjadi memory leak dan delay 23 detik.
- `WebSocketService` mempertahankan satu koneksi STOMP ke backend — multiple instance menyebabkan event diterima dobel.
- `TtsService` mengelola engine TTS — dua instance berbicara bersamaan akan saling menimpa.
- `AgoraService` mengelola RTC engine — satu per sesi, tidak boleh duplikat.
Semua di-register via `GetIt` service locator sebagai singleton agar seluruh widget tree mendapat instance yang sama.
### Class yang Terlibat
| Class | Layer | Peran dalam Pattern | Lokasi di Class Diagram |
|-------|-------|---------------------|-------------------------|
| `TtsService` | Flutter — Service | **Singleton** — satu TTS engine untuk seluruh app | Services block |
| `SttService` | Flutter — Service | **Singleton** — satu STT listener always-on | Services block |
| `YoloDetector` | Flutter — AI | **Singleton** — satu model inference engine (6 MB) | AI block |
| `WebSocketService` | Flutter — Service | **Singleton** — satu koneksi STOMP ke backend | Services block |
| `AgoraService` | Flutter — Service | **Singleton** — satu RTC engine untuk VoIP call | Services block |
| `HapticService` | Flutter — Service | **Singleton** — satu haptic controller | Services block |
| `GetIt` (injection_container) | Flutter — DI | **ServiceLocator** — mengelola lifecycle semua singleton | Bootstrap |
### Relasi Antar Class
```
injection_container.dart
├─ sl.registerSingleton<TtsService>(TtsService())
├─ sl.registerSingleton<SttService>(SttService())
├─ sl.registerSingleton<YoloDetector>(YoloDetector())
├─ sl.registerSingleton<WebSocketService>(WebSocketService())
├─ sl.registerSingleton<AgoraService>(AgoraService())
└─ sl.registerSingleton<HapticService>(HapticService())
WalkGuideBloc ──sl<YoloDetector>()──► YoloDetector (instance sama)
WalkGuideBloc ──sl<TtsService>()───► TtsService (instance sama)
VoiceCommandHandler ──sl<TtsService>()──► TtsService (instance sama, bukan baru)
```
---
## 4. Facade Pattern — Structural
### Justifikasi
Memproses satu perintah suara `"Send SOS"` membutuhkan koordinasi: `SttService`, command matcher, `SosBloc`, GPS, `TtsService` feedback, dan `GoRouter` navigasi — 6 subsystem berbeda. Tanpa Facade, setiap Widget harus inject dan tahu semua 6 dependency tersebut. `VoiceCommandHandler` menyederhanakan ini menjadi satu panggilan `processText(string)`.
Di backend, `GuardianController` membutuhkan data dari 5 repository berbeda untuk render dashboard Guardian. `GuardianDashboardService` menjadi Facade yang mengagregasi semuanya, sehingga controller cukup memanggil satu method.
### Class yang Terlibat
| Class | Layer | Peran dalam Pattern | Lokasi di Class Diagram |
|-------|-------|---------------------|-------------------------|
| `VoiceCommandHandler` | Flutter — Service | **Facade** — menyembunyikan koordinasi 6 subsystem voice | Services block |
| `WalkGuideBloc` | Flutter — BLoC | **Client** — hanya tahu `processText()`, tidak tahu subsystem | BLoC block |
| `TtsService` | Flutter — Service | **Subsystem** — salah satu dari yang dikoordinasikan | Services block |
| `SttService` | Flutter — Service | **Subsystem** — salah satu dari yang dikoordinasikan | Services block |
| `VoiceCommandConfig` | Backend — Entity | **Data** — konfigurasi perintah suara yang dibaca Facade | Entity block |
| `VoiceCommandKey` | Backend — Enum | **Enum** — key identifier 14 perintah suara | Enum block |
| `GuardianDashboardService` | Backend — Service | **Facade** — mengagregasi data dari 5 repository | Service block |
| `GuardianController` | Backend — Controller | **Client** — cukup panggil `getGuardianHomeData()` | Controller block |
| `LocationHistoryRepository` | Backend — Repository | **Subsystem** — data lokasi terakhir User | Repository block |
| `SosEventRepository` | Backend — Repository | **Subsystem** — data SOS aktif User | Repository block |
| `ActivityLogRepository` | Backend — Repository | **Subsystem** — log aktivitas User | Repository block |
| `GuardianNotificationRepository` | Backend — Repository | **Subsystem** — jumlah notif belum dibaca | Repository block |
| `PairingRelationRepository` | Backend — Repository | **Subsystem** — ambil userId yang dipasangkan ke Guardian | Repository block |
### Relasi Antar Class
```
Widget ──processText("send sos")──► VoiceCommandHandler (Facade)
┌───────────────┼───────────────┐
▼ ▼ ▼
SttService SosBloc TtsService
(stop listen) (TriggerSos) (speak feedback)
GuardianController ──getGuardianHomeData()──► GuardianDashboardService (Facade)
┌───────────────────────┼───────────────────────┐
▼ ▼ ▼
LocationHistoryRepo SosEventRepo ActivityLogRepo
(last location) (active SOS) (recent activity)
```
---
## 5. Repository / Proxy Pattern — Structural
### Justifikasi
WalkGuide harus berjalan **offline-first** — tunanetra sering berada di area tanpa sinyal. Log obstacle, activity log, dan update lokasi harus tetap tersimpan lokal (SQLite/Drift) saat offline, kemudian di-sync ke backend saat koneksi kembali. `WalkGuideBloc` tidak boleh tahu apakah data berasal dari API atau SQLite — keputusan ini ada di Repository Implementation yang bertindak sebagai Proxy.
### Class yang Terlibat
| Class | Layer | Peran dalam Pattern | Lokasi di Class Diagram |
|-------|-------|---------------------|-------------------------|
| `WalkGuideRepository` (interface) | Flutter — Domain | **Subject Interface** — kontrak yang dipakai BLoC | Domain layer |
| `WalkGuideRepositoryImpl` | Flutter — Data | **Proxy** — memutuskan API vs SQLite | Data layer |
| `ActivityLogRepository` (interface) | Flutter — Domain | **Subject Interface** | Domain layer |
| `ActivityLogRepositoryImpl` | Flutter — Data | **Proxy** | Data layer |
| `ObstacleLogRepository` | Backend — Repository | **Real Subject** — JPA repository backend | Repository block |
| `ActivityLogRepository` (JPA) | Backend — Repository | **Real Subject** — JPA repository backend | Repository block |
| `LocationHistoryRepository` | Backend — Repository | **Real Subject** — JPA repository backend | Repository block |
| `ObstacleLog` | Backend — Entity | **Data** — entitas yang disimpan/diambil | Entity block |
| `ActivityLog` | Backend — Entity | **Data** — entitas yang disimpan/diambil | Entity block |
| `LocationHistory` | Backend — Entity | **Data** — riwayat lokasi User | Entity block |
### Relasi Antar Class
```
WalkGuideBloc
│ depends on interface only
WalkGuideRepository (interface)
│ implements
WalkGuideRepositoryImpl (Proxy)
├─ if (isOnline) ──► REST API ──► ObstacleLogRepository (JPA)
│ │
│ ▼
│ ObstacleLog (Entity)
└─ else ────────► SQLite/Drift ──► Local cache
(Drift ORM)
```
---
## 6. Observer Pattern — Behavioral
### Justifikasi
Saat YOLO mendeteksi obstacle, puluhan komponen perlu bereaksi: overlay bounding box, TTS feedback, haptic, backend log, dan debug panel. Menghubungkan semua ini secara langsung akan menciptakan tight coupling. BLoC pattern adalah implementasi Observer murni — `WalkGuideBloc` sebagai Subject, widget sebagai Observer. Ketika state baru di-emit, semua subscriber otomatis bereaksi tanpa BLoC perlu tahu siapa saja Observer-nya.
Untuk Guardian, `WebSocketService` adalah Observer terhadap backend STOMP — Guardian menerima update lokasi User real-time tanpa polling.
### Class yang Terlibat
| Class | Layer | Peran dalam Pattern | Lokasi di Class Diagram |
|-------|-------|---------------------|-------------------------|
| `WalkGuideBloc` | Flutter — BLoC | **Concrete Subject** — emit state saat obstacle terdeteksi | BLoC block |
| `SosBloc` | Flutter — BLoC | **Concrete Subject** — emit state saat SOS triggered | BLoC block |
| `BlocBuilder` (widget) | Flutter — UI | **Observer** — rebuild UI saat state berubah | Widget layer |
| `BlocListener` (widget) | Flutter — UI | **Observer** — side effects tanpa rebuild UI | Widget layer |
| `WebSocketService` | Flutter — Service | **Subject** — Stream lokasi, SOS, notifikasi | Services block |
| `LocationBroadcaster` | Backend — Component | **Publisher** — push event ke STOMP topics | Backend block |
| `GuardianNotificationRepository` | Backend — Repository | **Data Store** — notifikasi yang perlu di-broadcast | Repository block |
| `SosEventRepository` | Backend — Repository | **Data Store** — SOS event yang di-broadcast ke Guardian | Repository block |
| `ActivityLogService` | Backend — Service | **Publisher** — broadcast activity log via LocationBroadcaster | Service block |
### Relasi Antar Class
```
YoloDetector ──detect()──► DetectionResult
WalkGuideBloc (Subject)
emit(ObstacleDetected)
┌───────────────┼───────────────┐
▼ ▼ ▼
BlocBuilder BlocListener BlocConsumer
(UI rebuild) (side effects) (both)
overlay bbox SnackBar error combined
Backend STOMP push:
LocationBroadcaster ──/topic/location/{userId}──► WebSocketService (Subject)
┌─────────────┼─────────────┐
▼ ▼ ▼
GuardianMap GuardianDash NotifScreen
(live marker) (SOS alert) (new notif)
```
---
## 7. Strategy Pattern — Behavioral
### Justifikasi
Guardian perlu mengkonfigurasi **cara WalkGuide memperingatkan** User tentang obstacle. Ada tiga mode: TTS saja (cocok untuk lingkungan tenang), TTS + haptic (default untuk sebagian besar situasi), haptic saja (lingkungan bising seperti pasar atau stasiun). Logika ini bisa berubah kapan saja dari dashboard Guardian tanpa restart app. Strategy Pattern memungkinkan penggantian algoritma alert di runtime tanpa mengubah `ObstacleAnalyzer` sedikit pun.
### Class yang Terlibat
| Class | Layer | Peran dalam Pattern | Lokasi di Class Diagram |
|-------|-------|---------------------|-------------------------|
| `ObstacleAlertStrategy` (interface) | Flutter — AI | **Strategy Interface** — kontrak `alert(result)` | Domain/AI layer |
| `TtsOnlyStrategy` | Flutter — AI | **Concrete Strategy 1** — hanya TTS | Strategy layer |
| `TtsWithHapticStrategy` | Flutter — AI | **Concrete Strategy 2** — TTS + haptic | Strategy layer |
| `HapticOnlyStrategy` | Flutter — AI | **Concrete Strategy 3** — hanya haptic | Strategy layer |
| `ObstacleAnalyzer` | Flutter — AI | **Context** — menyimpan dan mendelegasikan ke strategy | AI block |
| `AiConfig` | Backend — Entity | **Config Source** — menyimpan `alertMode` yang dipilih Guardian | Entity block |
| `AiConfigService` | Backend — Service | **Config Manager** — update config dan kirim FCM ke User | Service block |
| `AiConfigRepository` | Backend — Repository | **Persistence** — simpan/ambil konfigurasi AI | Repository block |
| `TtsService` | Flutter — Service | **Subsystem** — dipakai oleh TtsOnly dan TtsWithHaptic | Services block |
| `HapticService` | Flutter — Service | **Subsystem** — dipakai oleh TtsWithHaptic dan HapticOnly | Services block |
| `FcmService` | Backend — Service | **Notifier** — kirim FCM ke User saat config berubah | Service block |
### Relasi Antar Class
```
Guardian PUT /guardian/ai-config
AiConfigService ──updateConfig()──► AiConfig (simpan alertMode)
└──► FcmService.sendToUser() ──► FCM ke User: "ai_config_updated"
Flutter terima FCM
switch (config.alertMode)
┌─────────┼──────────┐
▼ ▼ ▼
TtsOnly TtsHaptic HapticOnly
Strategy Strategy Strategy
ObstacleAnalyzer.setStrategy(new)
_strategy.alert(detectionResult)
```
---
## 8. Chain of Responsibility Pattern — Behavioral
### Justifikasi
Setiap HTTP request dari Flutter perlu melewati beberapa proses berurutan: (1) menambahkan JWT, (2) auto-refresh jika expired, (3) error mapping terpusat, (4) logging. Tanpa Chain of Responsibility, setiap Repository harus menulis ulang logika ini. Di backend, setiap request perlu (1) divalidasi JWT, (2) dicek role-nya sebelum mencapai controller bisnis. Chain memungkinkan setiap handler fokus pada tanggung jawabnya sendiri dan meneruskan ke handler berikutnya.
### Class yang Terlibat
| Class | Layer | Peran dalam Pattern | Lokasi di Class Diagram |
|-------|-------|---------------------|-------------------------|
| `Interceptor` (abstract Dio) | Flutter — Network | **Abstract Handler** | Network layer |
| `AuthInterceptor` | Flutter — Network | **Concrete Handler 1** — inject token, auto-refresh | Network layer |
| `ErrorInterceptor` | Flutter — Network | **Concrete Handler 2** — map HTTP error ke Failure | Network layer |
| `LogInterceptor` | Flutter — Network | **Concrete Handler 3** — logging request/response | Network layer |
| `ApiClient` | Flutter — Network | **Chain Builder** — mengatur urutan dan membangun chain | Network layer |
| `OncePerRequestFilter` (Spring) | Backend — Security | **Abstract Handler** | Security block |
| `JwtAuthFilter` | Backend — Security | **Concrete Handler 1** — validasi JWT, set SecurityContext | Security block |
| `SecurityConfig` | Backend — Security | **Concrete Handler 2** — RBAC routing per URL pattern | Security block |
| `JwtUtil` | Backend — Security | **Component** — dipakai JwtAuthFilter untuk decode/validate | Security block |
| `CustomUserDetailsService` | Backend — Security | **Component** — dipakai JwtAuthFilter untuk load user dari DB | Security block |
| `UserRepository` | Backend — Repository | **Data Source** — dipakai CustomUserDetailsService | Repository block |
| `GlobalExceptionHandler` | Backend — Exception | **Error Handler** — menangkap exception dari seluruh chain | Exception block |
### Relasi Antar Class
```
Flutter HTTP Request
[Chain[0]] AuthInterceptor ──onRequest()──► inject "Bearer {token}" ──► next()
[Chain[1]] ErrorInterceptor ──onRequest()──► pass-through ──► next()
[Chain[2]] LogInterceptor ──onRequest()──► log URL + body ──► next()
▼ (HTTP over network)
[Backend] JwtAuthFilter ──doFilterInternal()──► validate JWT ──► chain.doFilter()
[Backend] SecurityConfig ──securityFilterChain()──► cek role ──► pass
[Backend] Controller ──handleRequest()──► proses bisnis
[Backend] GlobalExceptionHandler ──► tangkap exception jika ada
▼ (HTTP Response)
[Chain[2]] LogInterceptor.onResponse() ──► log response
[Chain[1]] ErrorInterceptor.onResponse() ──► map error jika 4xx/5xx
[Chain[0]] AuthInterceptor.onError() ──► jika 401: refresh token + retry
Repository / BLoC menerima Either<Failure, Data>
```
---
## Ringkasan Jumlah Class per Pattern
| Pattern | Jumlah Class Terlibat | Layer |
|---------|----------------------|-------|
| Builder | 5 class | Backend |
| Singleton | 7 class | Flutter |
| Facade | 13 class | Flutter + Backend |
| Repository/Proxy | 10 class | Flutter + Backend |
| Observer | 9 class | Flutter + Backend |
| Strategy | 11 class | Flutter + Backend |
| Chain of Responsibility | 12 class | Flutter + Backend |
| **Total unik** | **±40 class** | **Full Stack** |
> Semua class di atas dapat ditelusuri langsung di **Class Diagram WalkGuide** (file PDF yang dilampirkan).