Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[기록] 동일한 역할을 하는 객체들을 하나의 타입으로 묶어보자 #5

Open
SH0123 opened this issue Feb 8, 2024 · 1 comment
Assignees
Labels

Comments

@SH0123
Copy link
Owner

SH0123 commented Feb 8, 2024

배경

앱을 개선하고 방향성을 변경하면서 두가지 요구사항이 발생했다.

  • 도서 검색 API를 국내용, 해외용 두가지 사용해야한다.
  • 코어데이터 구조를 나타내는 구조체의 이름이 모호하고 속성에도 변경이 필요하다.

이 때 아래와 같은 문제가 발생했다.

  • API 마다 Response를 decoding 하기 위한 객체를 만들어줘야한다.
  • 상황에 따라 서로 다른 API를 사용해야하고, decoding하여 객체를 매핑해주는 함수 또한 각각 다르게 사용해줘야한다.
  • 코어데이터 구조체 이름을 변경하고 속성을 변경하는 경우, 이 변경이 영향을 미치는 모든 파일을 수정해줘야한다. (변경에 취약한 상태)

고민사항

  1. 두 가지 API를 사용하는 함수, 객체를 각각 작성해야하는데 어떻게 하면 코드를 최대한 재사용하여 만들 수 있을까?
  2. 데이터 구조체와 다른 객체들간의 결합도를 어떻게 낮출 수 있을까?

내 생각

오늘은 1번에 대해 학습하고 정리해보겠다.

@SH0123 SH0123 added the 기록 label Feb 8, 2024
@SH0123 SH0123 self-assigned this Mar 4, 2024
@SH0123 SH0123 changed the title [기록] 동일한 역할을 하는 객체들의 코드를 재사용하고 결합도 또한 낮추는 방법 [기록] 동일한 역할을 하는 객체들을 하나의 타입으로 묶어보자 Mar 16, 2024
@SH0123
Copy link
Owner Author

SH0123 commented Mar 17, 2024

기존

기존에는 책 정보를 로직이 필요한 viewModel과 view에 존재했다.
이를 객체로 빼내고, 해외용 도서 검색 API를 도입하면 아래 그림과 같은 상태가 됐을 것이다.

방향성

위의 강한 결합을 해결하고, 해외용 도서 검색 API를 도입하는 유연한 방법으로 두 가지 방향을 생각해볼 수 있다.

  1. 합성
  2. 상속

각각에 대해 구현하고 장, 단점에 대해 생각해보자

1. 합성

완전히 만족스러운 형태는 아니지만 몇시간 고군분투하여 만들어봤다.
클래스 다이어그램을 먼저 그려보자

class diagram drawio

장점

  • 구현체에 의존하지 않기 때문에 변동에 용이하다.
  • 기존에 구체화된 객체인 AladinBookAPI에 의존했다면 해당 객체를 사용하여 데이터를 불러오는 모든 코드에 변동이 필요 했을 것이다.
  • 하지만 추상화에 의존하여 구현했기 때문에 AladinBookAPI를 GoogleKeywordAPI로 바꿔주기만 하면 다른 코드에서는 변동이 없다. 즉, 확장은 개방적으로 수정은 폐쇄적으로 하는 개방 폐쇄의 원칙을 따를 수 있었다

단점

  • 초기 비용이 많이 든다.
  • class diagram과 문서이 없다면 팀원이 이해하는데 시간이 걸릴 수 있다

아쉬운 점

  1. 범용적으로 사용할 수 없는 network layer이다. swift network layer를 구현한 많은 자료를 보고 참고해서 구현해 볼 필요성을 느낀다.
  2. 국내용, 해외용 API를 같은 추상화 객체를 기반으로 구현하지 않고 각각의 객체로 만들었다면? 아래처럼 모든 사용되는 객체에서 Locale 변수에 접근하여 if else문으로 분기하고 API를 사용했을 것이다. 좋지 않은 코드처럼 보이지만 오늘의 내가 이런 구현체를 만드는데 시간이 많이 든 것을 생각해보면 분기해서 사용하는것도 나쁘지 않았을지도 모른다...ㅎ
if like {
        if let countryCode = Locale.current.region?.identifier {
            switch countryCode {
            case "KR":
                let apiHandler = AladinKeywordAPI()
            default:
                let apiHandler = GoogleBooksKeywordAPI()
            }
        } else {
            let apiHandler = GoogleBooksKeywordAPI()
        }
        
        apiHandler.fetchIsbnBooks() { result in
            if var bookWithPage = result {
                
                bookWithPage.wish = true
                saveBookData(newBook: bookWithPage, nil)
            }
        }
    }
  1. 국가 위치에 따라 다른 API를 사용해야 한다. 그렇기에 BookAPIManager에서 전역적으로 접근 가능한 Locale 변수에 접근하여 아래와 같이 계산 프로퍼티를 만들어내고 있다. 이렇게 한 구현이 옳은지 모르겠다.
var keywordApi: any BookAPI {
        if let countryCode = Locale.current.region?.identifier {
            switch countryCode {
            case "KR":
                return AladinKeywordAPI()
            default:
                return GoogleBooksKeywordAPI()
            }
        } else {
            return GoogleBooksKeywordAPI()
        }
    }
    
    var isbnApi: any BookAPI {
        if let countryCode = Locale.current.region?.identifier  {
            switch countryCode {
            case "KR":
                return AladinISBNAPI()
            default:
                return GoogleBooksISBNAPI()
            }
        } else {
            return GoogleBooksISBNAPI()
        }
    }

오브젝트 마인드를 향한 코드 변화

  1. API 사용 코드가 모든 객체에 산재되어 해당 객체를 계속 수정해줘야 했던 코드
  2. API 사용 코드를 분리한 코드
  3. API 사용 코드를 추상화 객체 기반으로 구체화한 코드

2. 상속

결론

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant