프로젝트를 하면서 계속 고민했던 부분이였다. 이해가 되다가도 막상 코드를 보면 이게 뭐가 깨끗한건가 싶기도하고..?
현업 프로젝트 막바지에 MVVM 패턴은 이런것이구나! 라는것을 살짝 깨우쳤다.
그러다보니 클린 아키텍쳐랑 MVVM은 같은건가? 에 대해 고민하기 시작했고, 책도 읽고 구글 서치 해본 내용을 정리하려한다!
나와 같은 고민하는 주니어들이 분명 많을 것이다! ( 수정요청 환영입니당 )
Clean Architecture란?
코드를 짜면서 수많은 시행착오와 오류들을 겪는다. 그리고 공통적으로 나오는 치명적인 이슈들을 개선하기 위해,
어떤 원칙을 만들어서 아주 이상적이고 깨끗한 구조를 만드는것이 Clean Architecture의 근간이다.
개발하면 분명히 어디선가 들어본적있는 로버트 C. 마틴이 제시한 SOLID 원칙이 있는데, Clean Architecture는 이 SOLID원칙을 철저하게 지켜서 만든 개발 방법론이다.
단순하게 코드 치는 방법을 제시한게 아닌, 이 원칙을 잘 지킨다면 유지보수와 확장성, 코드의 가독성을 높일 수 있게 이상적인 구조를 만들 수 있다! 라고 설계 원칙을 제안한 것이다.
즉, MVVM은 Clean Architecture를 설계하기위한 하나의 도구, 패턴일 뿐이다.
SOLID 원칙이란?
그럼 이제 SOLID원칙을 이해해보자!
- 객체지향 프로그래밍 설계를 좀 더 바르게 할 수 있게 도와주는 원칙
- SOLID는 5가지 원칙의 앞 글자를 따와서 만든것이다.
1. S
- Single Responsibility Principle: 단일 책임 원칙
- 모든 클래스는 하나의 책임만 가지며, 클래스는 그 책임을 완전히 캡슐화해야한다.
Do Not
A class는 기능이 2개 존재한다. 둘 다 다른 책임을 갖고 있으며, A 클래스는 크게 두 가지 이유로 변경될 수 있다.
1. 검증작업 개선할때
2. 유저정보 출력 방식이 변경되었을때
검증작업 개선을 위해서 A class의 로직을 변경하게 되었을때, 유저정보 출력방식에 영향을 끼칠수 있다. 그다지 좋지 않은 설계이다.
Do
책임이 다르기때문에 둘을 분리한다. 더욱 견고해진다. 변경되어도 하나의 책임만 가져간다.
2. O
- Open Closed Principle: 개방-폐쇄 원칙
- 확장에 대해서는 열려있어야하고, 수정에 대해서는 닫혀있어야한다.
- 어느 로직을 수정한다. 그런데 연관된 다른 코드에 영향을 준다면? 위험 할 것이다.
- 상속받아 기능을 재활용하고 새로운 기능을 추가하는 등 유연성, 재사용성을 기대할 수 있다.
3. L
- Liskov Substitution Principle: 리스코프 치환 원칙
- 치환성
- C class는 P Class의 자식이다. 그렇다면 P를 C로 치환해도 논리적으로 그 역할에 문제가 없어야한다.
- 치환성
4. I
- Interface Segregation Principle: 인터페이스 분리 원칙
- 인터페이스를 구체적이고 작은 단위로 분리한다.
- 인터페이스를 통해 class는 꼭 필요한 메소드들만 구현할 수 있게 된다.
예를들어 추상 클래스 Animal이 있다고 가정해보자.
// 추상 클래스 Animal
abstract class Animal {
abstract fun fly()
abstract fun walk()
}
그리고 Animal을 상속받은 Dog와 bird가 있다.
// Dog 클래스: Animal 추상 클래스를 상속받아 구현
class Dog : Animal() {
override fun fly() {
println("Dog cannot fly")
}
override fun walk() {
println("Dog is walking")
}
}
// Bird 클래스: Animal 추상 클래스를 상속받아 구현
class Bird : Animal() {
override fun fly() {
println("Bird is flying")
}
override fun walk() {
println("Bird is walking")
}
}
Dog는 어차피 날지 못하는데 굳이 fly를 상속받아 구현해야할까? 인터페이스 fly를 빼줘서 Bird만 구현 할 수 있게 하자!
// Flyable 인터페이스: 날 수 있는 동물
interface Flyable {
fun fly()
}
// 추상 클래스 Animal
abstract class Animal {
abstract fun walk()
}
// Dog 클래스: Animal 추상 클래스를 상속받아 구현
class Dog : Animal() {
override fun walk() {
println("Dog is walking")
}
}
// Bird 클래스: Animal 추상 클래스를 상속받고 Flyable 인터페이스를 구현
class Bird : Animal(), Flyable {
override fun fly() {
println("Bird is flying")
}
override fun walk() {
println("Bird is walking")
}
}
5. D
- Dependency Inversion Principle: 의존 역전 원칙
- 상위 모듈은 하위 모듈에 의존해서는 안된다.
- 절대 추상화는 세부사항에 의존하면 안된다.
※참고
코틀린에서는?
- 취약한 기반 클래스(fragile base class)
- 하위 클래스가 기반 클래스에 대해 가졌던 가정이 기반클래스를 변경함으로써 깨져버린 경우에 생긴다.
- 어떤 클래스가 자신을 상속하는 방법에 대해 정확한 규칙 (어떤 메소드를 어떻게 오버라이드해야하는지 등)을 제공하지않는다면 그 클래스의 클라이언트는 기반 클래스를 작성한 사람의 의도와 다른 방식으로 메소드를 오버라이드할 위험이 있다.
- 모든 하위 클래스를 분석하는 것은 불가능하므로 기반 클래스를 변경하는 경우 하위클래스의 동작이 예기치않게 바뀔 수도 있다는 면에서 기반 클래스는 ‘취약’하다.
- 이 문제를 해결하기 위해 자바 프로그래밍 기법에 대한 책 중 가장 유명한 책인 조슈아 볼르크가 쓴 Effective Java에서는 이렇게 조언한다.
“상속을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 상속을 금지하라”
-
- 이는 특별히 하위클래스에서 오버라이드하게 의도된 클래스와 메소드가 아니라면 모두 final로 만들라는 뜻이다.
- 코틀린도 마찬가지 철학을 따른다. 자바의 클래스와 메소드는 기본적으로 상속에 대해 열려있지만, 코틀린에서 클래스와 메소드는 기본적으로 final이다.
- 어떤 클래스의 상속을 허용하려면 클래스 앞에 open변경자를 붙여야한다. 그와 더불어 오버라이드를 허용하고 싶은 메소드나 프로퍼티의 앞에도 open 변경자를 붙여야한다.
정리해보고 나니까 좀 더 명확하게 클린 아키텍쳐의 그림이 그려진거같다. 아직 공부를 더 많이 해야하지만 🤣
'Android Kotlin' 카테고리의 다른 글
Android device Storage (0) | 2024.07.10 |
---|---|
Android SDK SparseArray 알아보기 (0) | 2024.07.07 |
Android Kotlin WebSocket 웹소켓 이해하기 (0) | 2024.06.03 |
Android Kotlin ProcessCameraProvider (0) | 2024.06.02 |
Android Kotlin redundant sam constructor observer (0) | 2024.06.01 |