Data Layer에 대해서 알아보기 이전에 이해를 위한 다른 개념들에 대해서 간략하게나마 살펴보기로 하자.
1. 추상화
2. 객체지향형 프로그래밍과 SOLID 원칙
3. 결론
1. 추상화
추상화, 명확하지 않지만 무엇인지는 알 수 있도록 만들어 주는 게 포인트
예를 들자면, 동아리를 운영하는데 회원들을 한 장소에 모으려고 한다.
class Me {
fun gathering(members : List<String>){
members.forEach {
when(it){
"Minho" ->{
//A는 자전거 타고옴
}
"Hoyeong" ->{
//B는 걸어옴
}
}
}
}
}
위와 같이 하나하나 회원에 대한 관리에 대한 책임을 나에게 쥐어주면
교통수단에 따라서 융통성 없고 저수준의 하드코딩을 하게 된다.
이러한 방법은 추상화를 통한 방법으로 직관적으로 만들 수 있다.
class EasyMe(){
fun gathering(members : List<Member>){
members.forEach {
it.participant()
}
}
}
interface Member{
fun participant()
}
class Minho : Member{
override fun participant() {
// 자전거 타고감
}
}
class Hoyeong : Member{
override fun participant() {
// 걸어감
}
}
각 회원들에게 오는 방법에 대한 책임을 주면 된다.
그렇게 해서 나는 회원들에게 모이는 장소로 오라고 하면 회원들이 모이게 된다.
각각의 회원이 자전거를 타던, 지하철을 타던 그 방법에 대해서는 알 필요가 없다.
그럼 각각의 회원들은 자신이 지정된 장소로 오는 방법은 스스로 구체화된 방식을 이용할 것이다.
위의 예에서 나는 회원들을 모우는 추상화된 명령(고수준의 언어)을 했고,
회원들은 그 명령을 구체화된 방법을 통해 모이는 것.
이렇게 하면 장점이 무엇인가?
1) 나의 책임을 줄여준다
내가 A는 자전거 타고 오면 된다.
B는 택시타고 와라 이런 식으로 지정해주지 않아도 되어 오는 방법에 대한 책임에서 벗어날 수 있다.
2) 확장성
회원들은 그 장소로 오기만 하면 된다.
이제껏 멀리 사는 사람이 없어서 보통의 교통수단으로 다 올 수 있었지만, 어느 날 미국에 사는 회원이 생겨서 한국으로 모이게 되어도 나는 이러한 변수에 대한 책임이 없으므로, 미국에 사는 회원은 비행기를 교통수단으로 하여 온다고 해도 별문제 없이 새로운 교통수단을 추가할 수 있다.
3) 코드가 깔끔해지고, 테스트도 편해지는 등의 많은 장점이 있음.
무조건 장점만 있는것이 아니다.
추상화를 위한 Interface 나 class가 있더라도 모든 것을 추상화시켜서 적용하게 되면,
관리해야 하는 파일은 많아지고, 오히려 작업량이 늘어날 것이므로,
각 프로젝트에 맞는 수준의 추상화가 필요함.
위에서는 오는 방법에 대해서만 추상화 시켰지만,
혹시 자전거도 추상화시켜 전기자전거인지, 일반 자전거인지,
비행기는 아시아나인지, 대한항공인지
이렇게 모두 추상화하게 되면 불필요한 작업이 될 것.
프로젝트 내에서 절대 변하지 않을 부분까지 추상화 하여 만들게 되면 문제가 생길 것,
그리고 변경사항이 필요한 부분이 생긴다면 그 시점에서 추상화를 하여도 된다.
코틀린에서는 class, interface, fun등을 통해 추상화가 가능하고,
추상화를 거친 고수준의 언어로 상위계층에서 프로그래밍을 할 수 있어 코드가 깔끔해지는 것을 느낄 수 있다.
2. 객체지향형 프로그래밍과 SOLID 원칙
우선 객체지향형 프로그래밍이란 무엇인가?
프로그램을 여러 객체(Class, Object)들의 조합으로 보고 이들의 상호작용에 의해서
프로그램이 작동할 수 있게 한다는 프로그래밍 패러다임 중 하나이다.
각각의 객체들에게 쥐어주는 역할에 의해서 프로그램의 설계, 유지, 보수의 난이도 차이가 생기게 되고,
이는 SOLID 5원칙을 준수하게 되면 보다 쉬운 유지보수가 가능하게 된다.
5대 원칙이라고 하지만, 내가 느끼기에는 모든 원칙이 비슷한 말을 하는 것 같다.
결국 SOLID원칙을 준수하는 구조는 개발에 많은 장점이 있고,
이 원칙을 준수하기 위해서 객체지향형 프로그래밍의 특징인 추상화, 캡슐화, 다형성 등을 사용한다.
SOLID
1) SRP 단일 책임 원칙 (Single Responsability Principle)
말 그대로 하나의 책임을 가지도록 Class 들을 분리해야 한다는 말로 그 Class를 수정할 원인은 단 하나의 이유만 있어야 한다.
하나의 객체에 많은 책임을 주게되면 객체 내부의 코드들은 서로 강하게 결합되어 있을 가능성이 있다.
그래서 서로 다른 기능을 하더라도 변경에 대해서 민감하게 반응하여
작은 변화에 의해서도 다른 기능들에 영향이 있는지 확인을 해야 하며,
성능 테스트에도 오류가 없는지 확인하는데 귀찮은 부분이 생기게 된다.
이 단일 책임원칙은 class만 적용되는 것이 아니라, Layer과 Module 등의 부분에서도 단일 책임을 부여하여 개발함.
Android Developer 는
UI Layer,
Domain Layer,
Data Layer의
층으로 분리하여 설명하고 있음.
2) OCP 개방 폐쇄 원칙 (Open Close Principle)
확장에는 열려있고, 변경에는 닫혀있어야 한다.
무슨 말인지 감이 안잡히는데,
위의 추상화 설명할 때 사용한 예처럼 추상화가 이루어진 고수준의 언어를 사용함으로
기존의 객체(상위 객체) 내부의 코드는 변화가 없고,
구체화시킨 부분의 확장이 쉽게 이루어질 수 있도록 만든다는 것임.
3) LSP 리스코프 치환 원칙 (Liskov Substitution Principle)
서브 타입은 베이스 타입과 치환될 수 있어야 한다.
위에서 했던 Member Interface를 상속하는 민호와 호영이의 예처럼 Member라는 베이스 타입을 통해 확장성을 높임.
민호와 호영이는 Member에서 제공하는 기능을 그대로 수행하며,
만약 Member의 기능을 다른 목적이나 결과로 유도하게 되면 LSP를 어기게 된다.
4) ISP 인터페이스 분리 원칙 (Interface Segregation Principle)
class는 자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다는 원칙이다.
아래의 참조 중에 정말 이해가 잘가는 예를 가지고 와봄
게시판의 여러 기능을 구현한 메서드를 제공하는 클래스에서는 CRUD가 제공된다. 그러나 클라이언트에 따라서 게시판의 이러한 기능 중 일부분만 사용할 수 있고 관리자는 모든 기능을 사용할 수 있다고 가정하자. 이 경우 게시판은 관련된 책임을 수행하므로 SRP를 만족하지만 이 클래스의 모든 메서드가 들어 있는 인터페이스가 클라이언트와 관리자 모두가 사용한다면 ISP에는 위배된다. 이 경우 관리자용 인터페이스와 일반 인터페이스를 분리함으로써 ISP 위반 또한 함께 해결할 수 있다.
위를 예제로 만들어 보면 아래와 같은 느낌이려나? 같은 Class를 사용하여 객체를 생성하지만, 그 기능을 제한한다.
interface User{
fun read()
}
interface Manager : User{
fun write()
fun delete()
}
class Member : Manager {
override fun write() {
...
}
override fun delete() {
...
}
override fun read() {
...
}
}
class Test(){
val minho : User = Member()
fun use(){
minho.read()
}
val hoyeong : Manager = Member()
fun managing(){
hoyeong.delete()
hoyeong.write()
hoyeong.read()
}
}
5) DIP 의존 역전 원칙 (Dependency Inversion Principle)
의존성 주입을 할 때 생각해보자. + 위의 예
추상화 된 인터페이스에 의존한 객체를 사용하므로,
변화에 유연하게 대처할 수 있는 것임.
계속 같은 말을 하는 거 같음...
즉 의존을 할 때는 구체화된 객체가 아닌 추상화된 객체에 의존하라는 원칙이다.
자동차는 엔진이 필요한데, 이 엔진의 기능을 동력을 주는것이다.
그러므로 동력을 준다는 추상화된 인터페이스나 객체에 의존을 하고 실제로 사용 시에는
구체화된 엔진을 주입하여 사용한다(ex 전기 엔진)
3. 결론
객체 지향형 프로그래밍의 특징들을 사용하여
SOLID의 원칙들을 지키면서 프로그래밍하면
유지, 보수가 쉬워지고 층(Unit)별 개발을 할 수 있다.
하지만 과도한 추상화를 통한 개발은 오히려 더 복잡해지고 객체의 수가 많아진다.
그러므로 개발할 때는 프로젝트의 크기나 필요에 따라 계층을 나누는 작업이 있어야하고,
Android Developer의 추천 아키텍처는 보편적인 방식의 아키텍처이므로,
필요에 따라 변경되는 부분이 있어도 괜찮다
여기서 다시 환기시키자면
UI Layer, Data Layer가 분리되어 있는 이유는
UI Layer는 Data를 소비하는 계층이며
이 Data를 가져오는 경로나 비즈니스 로직의 부분은 관심이 없으며
Data Layer의 객체인 Repository의 추상화된 함수를 통해
데이터소비만 한다.
class UILayer(val dataLayer : DataLayer){
fun showData(){
dataLayer.getData().forEach {
println(it)
}
}
}
interface DataLayer{
fun getData() : List<String>
}
class DataLayerImpl : DataLayer{
override fun getData(): List<String> {
// DB나 Server에서 데이터 가져오기
}
}
위 처럼 하면
UI Layer의 개발과
Data Layer의 개발을
분리하여 할 수 있다.
참조
https://www.nextree.co.kr/p6960/
https://www.nextree.co.kr/p6960/
https://medium.com/@limstar/java-solid-986bbf458c44
'안드로이드 읽어보기 > 5. Android Architecture' 카테고리의 다른 글
4) Data Layer / Android Architecture 어떻게 해야하는가? (0) | 2022.04.19 |
---|---|
Android Architecture은 어떻게 해야할까? (2) UI Layer (0) | 2022.03.16 |
MVI에 대해서. (0) | 2022.03.16 |
MVVM이 무엇인가??? (0) | 2022.03.16 |
Android Architecture 어떻게 해야하는가?(1) (0) | 2022.03.15 |