오브젝트 리뷰 - 5

기술/아키텍처 2021. 7. 27. 20:00 Posted by 아는 개발자

https://book.naver.com/bookdb/book_detail.nhn?bid=15007773

 

오브젝트

역할, 책임, 협력을 향해 객체지향적으로 프로그래밍하라!객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를

book.naver.com

 

의존성 관리하기 

 

의존성 이해하기 

 

어떤 객체가 예정된 작업을 정상적으로 수행하기 위해 다른 객체를 필요로 하는 경우 두 객체 사이에 의존성이 존재한다고 말한다. 의존성은 방향성을 가지며 항상 단방향이다 (p254)

 

의존성은 전이될 수 있기 때문에 의존성의 종류를 직접 의존성(direct dependency)간접 의존성(indirect dependency)로 나누기도 한다. 직접 의존성이란 한 요소가 다른 요소에 직접 의존하는 경우를 가리킨다. 간접 의존성이란 직접적인 관계는 존재하지 않지만 의존성 전이에 의해 영향이 전파되는 경우를 뜻한다. (p257)

 

런타임 의존성은 실행 시점에 객체 간의 의존성을 뜻한다. 컴파일 타임은 코드 상에서 클래스 간의 의존성을 뜻한다. 런타임 의존성과 컴파일타임 의존성이 다를 수 있다. 유연하고 재사용 가능한 코드를 설계하기 위해서는 두 종류의 의존성을 서로 다르게 만들어야 한다. 런타임 의존성과 컴파일 타임 의존성 사이의 거리가 멀면 멀수록 설계가 유연해진다. (p258)

 

클래스가 사용될 특정한 문맥에 대해 최소한의 가정만으로 이뤄져 있다면 다른 문맥에서 재사용하기가 더 수월해진다. 이를 컨텍스트 독립성이라고 부른다. 설계가 유연해지기 위해서는 가능한 자신이 실행될 컨텍스트에 대한 구체적인 정보를 최대한 적게 알아야한다. (p260)

 

유연한 설계 

 

바람직한 의존성은 재사용성과 관련이 있다. 어떤 의존성이 다양한 환경에서 클래스를 재사용할 수 없도록 제한한다면 그 의존성은 바람직하지 못한 것이다. 컨텍스트에 독립된 의존성은 다양한 환경에서 재사용 될 수 있는 가능성을 열어 놓는다(p265, 266)

 

바람직한 의존성을 가질 때 두 객체는 느슨한 결합도(loose coupling) 또는 약한 결합도를(weak coupling) 가진다고 말한다. 반대로 바람직하지 못한 의존성을 가질 때 두 객체는 단단한 결합도(tight coupling) 또는 강한 결합도(strong coupling)을 가진다고 말한다. (p266)

 

결합도의 정도는 한 요소가 자신이 의존하고 있는 다른 요소에 대해 알고 있는 정보의 양으로 결정된다. 한 요소가 다른 요소에 대해 더 많은 정보를 알고 있을 수록 두 요소는 강하게 결합된다. 반대로 더 적은 정보를 알고 있을 수록 두 요소는 약하게 결합된다. 더 많이 알수록 더 많이 결합된다. 더 많이 알고 있다는 것은 더 적은 컨텍스트에서 재사용 가능하다는 것을 의미한다. 결합도를 느슨하게 만들기 위해선 협력하는 대상에 대해 필요한 정보 외에는 최대한 감추는 것이 중요하다(p267)

 

추상화란 어떤 양상, 세부사항, 구조를 좀더 명확하게 이해하기 위해 특정 절차나 물체를 의도적으로 생략하거나 감춤으로써 복잡도를 극복하는 방법이다. 구체 클래스에 비해 추상 클래스는 클라이언트가 알아야하는 지식의 양이 더 적기 때문에 구체 클래스보다 추상 클래스에 의존하는 것이 결합도가 더 낮다. 인터페이스에 의존하면 추상 클래스의 상속 계층을 모르더라도 협력이 가능해진다. 인터페이스 의존성은 협력하는 객체가 어떤 메시지를 수신할 수 있는지에 대한 지식만을 남기기 때문에 추상 클래스보다 결합도가 낮다. 의존하는 대상이 추상적일수록 결합도는 더 낮아진다는 것이 핵심이다. (p268)

 

의존성이 퍼블릭 인터페이스에 노출되는 경우 명시적 의존성이라고 한다. 반대로 퍼블릭 인터페이스에 노출되지 않는 경우 숨겨진 의존성이라고 한다. 의존성이 명시적이지 않으면 클래스를 다른 컨텍스트에서 재사용하기 위해 내부 구현을 직접 변경해야 할 수 있다 (p270, 271)

 

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 5  (0) 2021.07.27
오브젝트 리뷰 - 4  (0) 2021.07.25
오브젝트 리뷰 - 3  (0) 2021.07.21
오브젝트 리뷰 - 2  (0) 2021.07.12
오브젝트 리뷰 - 1  (0) 2021.07.11
응집도(Cohesion)와 결합도(Coupling)  (0) 2021.07.10

오브젝트 리뷰 - 4

기술/아키텍처 2021. 7. 25. 15:56 Posted by 아는 개발자

https://book.naver.com/bookdb/book_detail.nhn?bid=15007773

 

오브젝트

역할, 책임, 협력을 향해 객체지향적으로 프로그래밍하라!객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를

book.naver.com

 

메시지와 인터페이스 

 

협력과 메시지

 

메시지는 객체 사이의 협력을 가능하게 하는 매개체다. 객체가 다른 객체에게 접근할 수 있는 유일한 방법은 메시지를 전송하는 것뿐이다. 전통적인 방법은 클라이언트-서버 모델이다. 메시지를 전송하는 객체를 클라이언트, 메시지를 수신하는 객체를 서버라 부르며 협력은 클라이언트가 서버의 서비스를 요청하는 단방향 상호작용이다. (p176)

 

객체가 독립적으로 수행할 수 있는 것보다 더 큰 책임을 수행하기 위해서는 다른 객체와 협력해야 한다. 두 객체 사이의 협력을 가능하게 해주는 매개체가 바로 메시지다. (p177)

 

메시지는 객체들이 협력하기 위해 사용할 수 있는 유일한 의사소통 수단이다. 한 객체가 다른 객체에게 도움을 요청하는 것을 메시지 전송 또는 메시지 패싱이라고 부른다. 이때 메시지를 전송하는 객체를 메시지 전송자라고 부르고 메시지를 수신하는 객체를 메시지 수신자라고 부른다. 메시지는 오퍼레이션명과 인자로 구성되며 메시지 전송은 여기에 메시지 수신자를 추가한 것이다. (p177)

 

메시지를 수신했을 때 실제로 실행되는 함수 또는 프로시저를 메서드라 부른다. 코드 상에서 동일한 이름의 변수에게 동일한 메시지를 전송하더라도 객체의 타입에 따라 실행되는 메서드가 달라질 수 있다. 객체는 메시지와 메서드라는 두 가지 서로 다른 개념을 실행 시점에 연결해야 하기 때문에 컴파일 시점과 실행 시점의 의미가 달라질 수 있다. 이런 메시지와 메서드의 구분은 전송자와 메시지 수신자가 느슨하게 결합될 수 있게 한다. (p178)

 

객체가 의사소통을 위해 외부에 공개하는 메시지의 집합을 퍼블릭 인터페이스라고 부른다. 프로그래밍 언어 관점에서 퍼블릭 인터페이스에 포함된 메시지를 오퍼레이션이라고 부른다. 그에 비해 메시지를 수신했을 때 실제로 실행되는 코드는 메서드라고 부른다. (p179)

 

오페레이션의 이름과 파라미터 목록을 합쳐 시그니처라고 부른다. 오퍼레이션은 실행 코드 없이 시그니처만을 정의한 것이고 메서드는 이 시그니처에 구현을 더한 것이다. (p180)

 

인터페이스와 설계 품질 

 

좋은 인터페이스는 최소한의 인터페이스와 추상적인 인터페이스라는 조건을 만족해야한다 (p181)

 

디미터 법칙은 객체 내부 구조에 강하게 결합되지 않도록 협력 경로를 제한한다. 오직 하나의 도트만 사용하라는 말로 요약되기도 한다. 디미터 법칙을 따르기 위해서는 클래스가 특정한 조건을 만족하는 대상에게만 메시지를 전송하도록 프로그래밍 해야한다. 디미터 법칙을 따르면 부끄럼타는 코드를 작성할 수 있다. 부끄럼타는 코드란 불필요한 어떤 것도 다른 객체에게 보여주지 않으며 다른 객체의 구현에 의존하지 않는 코드를 의미한다. (p183, 184, 185)

 

디미터 법칙을 만족하지 않은 경우 내부 구조에 대해 물어보고 반환받은 요소에 대해 연쇄적으로 메세지를 전송하는 경우가 생기는데 이런 경우를 기차 충돌(train wreck)이라 한다. 기차 충돌은 클래스의 내부 구현이 외부로 노출됐을 때 나타나는 전형적인 형태다. (p186)

 

훌륭한 메시지는 객체의 상태에 관해 묻지 말고 원하는 것을 시켜야한다. 묻지 말고 시켜라는 이런 스타일의 메시지 작성을 장려하는 원칙이다. 객체의 정보를 이용하는 행동을 객체의 외부가 아닌 내부에 위치시키기 때문에 자연스럽게 정보와 행동을 동일한 클래스 안에 두게 되고 자연스럽게 정보 전문가에게 책임을 할당하게 되고 높은 응집도를 가진 클래스를 얻게 된다 (p186, 187)

 

메서드 이름을 짓는 두번째 방법은 '어떻게'가 아니라 '무엇'을 하는지를 드러내는 것이다. 무엇을 하는지를 드러내는 이름은 코드를 이해하기 쉽고 유연한 코드를 낳는 지름길이다. 무엇을 하는지 드러내도록 메서드의 이름을 짓기 위해서는 객체가 협력 안에서 수행해야 하는 책임에 관해 고민해야한다. 이처럼 무엇을 하느냐에 따라 메서드의 이름을 짓는 패턴을 의도를 드러내는 선택자라고 부른다. (p188, 189, 190)

 

명령 - 쿼리 분리 원칙 

 

어떤 절차를 묶어 호출하도록 이름을 부여한 기능 모듈을 루틴이라 부른다. 루틴은 프로시저와 함수로 구분 되는데 프로시저는 정해진 절차에 따라 내부 상태를 변경하는 루틴이며 함수는 어떤 절차에 따라 필요한 값을 계산해서 반환하는 루틴의 종류이다. 명령은 객체의 인터페이스 측면에서 프로시저에 해당하고, 쿼리는 함수에 해당한다. (p202)

 

명령-쿼리 분리 원칙의 요지는 오퍼레이션은 부수효과를 발생시키는 명령이거나 부수효과를 발생시키지 않는 쿼리 중 하나여야 한다는 것이다. 어떤 오퍼레이션도 명령인 동시에 쿼리여서는 안된다. 명령은 상태를 변경할 수 있지만 상태를 반환해서는 안되며 쿼리는 객체의 상태를 반환할 수 있지만 상태를 변경해서는 안된다. (p203)

 

명령-쿼리 분리 원칙은 부수효과를 가지는 명령으로부터 부수효과를 가지지 않는 쿼리를 명백하게 분리함으로써 제한적이나마 참조 투명성의 혜택을 누릴 수 있게 해준다 (p213)

 

객체 분해

 

프로시저 추상화와 데이터 추상화 

 

현대적인 프로그래밍 언어를 특징 짓는 중요한 두 가지 추상화 메커니즘은 프로시저 추상화와 데이터 추상화다. 프로시저 추상화는 소프트웨어가 무엇을 해야하는지를 추상화하고 테이터 추상화는 소프트웨어가 무엇을 알아야 하는지를 추상화한다. (p218)

 

프로시저 추상화를 중심으로 시스템을 분해하기로 결정했다면 기능분해(functional decomposition)의 길로 들어서는 것이다. 기능 분해는 알고리즘 분해라고 부르기도 한다. 데이터 추상화를 중심으로 시스템을 분해하기로 결정했다면 데이터를 중심으로 타입을 추상화(type abstraction)하는 방향과 데이터를 중심으로 프로시저를 추상화 하는 방법(procedure abstraction)중 하나를 선택한다 (p218)

 

프로시저 추상화와 기능 분해

 

전통적인 기능 분해 방법은 하향식 접근법을 따른다. 하향식 접근법이란 시스템을 구성하는 가장 최상위 기능을 정의하고 좀 더 작은 단계의 하위 기능으로 분해해가는 방법을 말한다. (p219)

 

하향식 접근법과 기능분해는 근본적으로 변경에 취약한 설계를 갖는다. 메인 함수를 유일한 정상으로 간주하는 하향식 기능 분해의 경우에는 새로운 기능을 추가할 때마다 매번 메인 함수를 수정해야 한다. (p226, 227)

 

하향식 접근법은 비즈니스 로직을 설계하는 초기 단계부터 입력방법과 출력 양식을 함께 고민하도록 강요한다. 그러나 사용자 인터페이스는 시스템 내에서 가장 많은 변경이 발생하는 부분이므로 수정이 발생하면 상대적으로 변경이 적은 비즈니스 로직까지 영향을 미치게 된다. 근본적으로 변경에 불안정한 아키텍처가 만들어진다. (p229)

 

하향식 접근법의 경우 처음부터 구현을 염두에두기 때문에 자연스럽게 함수들의 실행 순서를 정의하는 시간 제약을 강조한다. 분해를 진행할 수 없어 기능 분해 방식은 중앙집중 제어스타일의 형태를 띨 수 밖에 없다. 이를 해결하기 위해선 시간 제약에 대한 미련을 버리고 좀 더 안정적인 논리적 제약을 설계 기준으로 삼아야 한다. 객체 지향은 삼수 간의 호출 순서가 아니라 객체 사이의 논리적인 관계를 중심으로 설계를 이끌어간다. (p230)

 

모듈 

 

시스템을 모듈 단위로 분해하기 위한 기본 원리로 시스템에서 자주 변경되는 부분을 상대적으로 덜 변경되는 안정적인 인터페이스 뒤로 감춰야 한다는 것이 핵심이다. 모듈 분해는 감춰야 하는 비밀을 선택하고 비밀 주변에 안정적인 보호막을 설치하는 보존의 과정이다. 비밀을 결정하고 모듈을 분해한 후에는 기능 분해를 이용해 모듈에 필요한 퍼블릭 인터페이스를 구현할 수 있다. 모듈은 복잡성과 변경 가능성을 감춰야한다. (p235, 236)

 

모듈은 기능이 아니라 변경의 정도에 따라 시스템을 분해한다. 각 모듈은 외부에 감춰야 하는 비밀과 관련성 높은 데이터와 함수의 집합이다. 따라서 모듈 내부는 높은 응집도를 유지한다. 또한 퍼블릭 인터페이스를 통해서만 통신해야 하므로 낮은 결합도를 유지한다. 모듈에게 있어 핵심은 데이터다. (p239)

 

클래스 

 

명확한 의미에서 클래스는 추상 데이터 타입과는 다르다. 핵심적인 차이는 클래스는 상속과 다형성을 지원하는데 비해 추상 데이터 타입은 지원하지 못한다는 것이다.  타입 추상화를 기반으로 하는 대표적인 기법이 바로 추상 데이터 타입이다. 추상 데이터 타입은 오퍼레이션을 기준으로 여러가지 타입을 나타낼 수 있다. 반면 객체 지향은 타입을 기준으로 오퍼레이션을 묵는다. (p245, 246)

 

실제 내부에서 수행되는 절차는 다르지만 클래스를 이용한 다형성은 절차에 대한 차이점을 감춘다. 다시 말해 객체 지향은 절차 추상화다.

 

 

 

 

 

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 5  (0) 2021.07.27
오브젝트 리뷰 - 4  (0) 2021.07.25
오브젝트 리뷰 - 3  (0) 2021.07.21
오브젝트 리뷰 - 2  (0) 2021.07.12
오브젝트 리뷰 - 1  (0) 2021.07.11
응집도(Cohesion)와 결합도(Coupling)  (0) 2021.07.10

Coroutine + Retrofit | Coroutine + Room

개발/안드로이드 2021. 7. 22. 21:00 Posted by 아는 개발자

Coroutine + Retrofit

 

Retrofit 2.6.0 버전부터 suspend 함수로 api를 작성할 수 있게 됐다. 다른 Retrofit 인터페이스처럼 어노테이션을 추가하고 suspend 함수를 추가하면 빌드 될 때 Retrofit 에서 전처리한다. 

 

interface LibraryApi {
    @GET("/1.0/new")
    suspend fun getNew() : BookListResp
}

 

suspend로 쓰였기 때문에, api 를 호출하는 부분에서도 suspend 함수를 받아서 처리할 수 있다. 예로 Repository 인 경우 suspend 함수를 이용해서 아래 코드로 표현이 가능하다. withContext를 받아서 I/O 쓰레드에서 실행하도록 변경해 Main 쓰레드 안전성이 보장됐다.

 

class LibraryRepository(
    private val apiProvider: ApiProvider
) {

    private val libraryApi by lazy { apiProvider.createApi(LibraryApi::class.java) }

    suspend fun loadNew(): BookListResp = withContext(Dispatchers.IO) {
        return@withContext libraryApi.getNew()
    }

 

Coroutine + Room 

 

Room에서 데이터 변화에 따라 UI를 바꾸거나 특정 로직을 실행해야할 때가 있다. Room 에서는 LiveData와 Flow를 이용하는 두가지 방법을 제공하는데, 이 포스트에서는 Flow를 활용한 버전만 다룬다. 

 

@Dao
interface BookSearchDao {
    @Query("select * from BookSearch order by BookSearch.createdAt desc")
    fun selectBookSearchList(): Flow<List<BookSearch>?>
    
class LibraryRepository(
    private val bookSearchDao: BookSearchDao
) {
    suspend fun loadBookSearchHistory(): Flow<List<BookSearch>?> = withContext(Dispatchers.IO) {
        return@withContext bookSearchDao.selectBookSearchList()
    }

 

Dao의 리턴타입을 Flow로 싸고 Repository 에서는 suspend 함수를 이용해 IO 쓰레드에서 실행하도록 변경하고 리턴했다. Flow는 RxJava의 Flowable과 같아서 내부 DB에 변경사항이 생기면 스트림을 따라서 알림을 준다. 

 

class SearchViewModel @Inject constructor(
    private val libraryRepository: LibraryRepository
): ViewModel() {
    fun loadHistory() {
        viewModelScope.launch {
            libraryRepository.loadBookSearchHistory()
                .distinctUntilChanged()
                .collect { list ->
                    searchHistory.value = list ?: listOf()
                }
        }
    }

 

ViewModel에선 loadBookSearchHistory() 함수의 리턴값인 Flow를 viewModelScope 내에서 subscribe 한다. 변화가 있을 때마다 collect 내부의 바디 코드가 실행된다. viewModelScop 으로 실행했기 때문에 ViewModel이 종료되면 subscribe도 자동으로 종료된다.

728x90

'개발 > 안드로이드' 카테고리의 다른 글

Coroutine + Retrofit | Coroutine + Room  (0) 2021.07.22
suspend fun  (0) 2021.07.22
Single, Maybe, Completable  (0) 2021.07.04
Serializable 과 Parcelable  (0) 2021.06.19
kotlin lateinit, lazy by  (0) 2021.06.05
Kotlin - Coroutine  (0) 2021.05.21

suspend fun

개발/안드로이드 2021. 7. 22. 20:00 Posted by 아는 개발자

코틀린에서 추가된 suspend 함수는 Coroutine 내에서만 실행 가능한 함수다. 블로그 글마다 suspend 함수에 대해서 각각 정의가 다른데 나는 suspend 함수를 Coroutine Context를 갖고 있는 함수 정도로 정의하고 싶다.

 

간단한 사용법 

 

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        CoroutineScope(Dispatchers.Main).launch {
            val sum = suspendSum(1, 2) // no compile error  
            Log.d("suspend sum", sum.toString())
        }
        
        suspendSum(1, 2) // compile error
    }

    private suspend fun suspendSum(a: Int, b: Int) : Int {
        return a + b
    }
}

29210-29210/com.kwony.mylib D/suspend sum: 3

 

suspend 함수는 Coroutine Job 내에서 일반 함수처럼 호출이 가능하다. 그런데 외부에서는 부모의 Coroutine Context를 받지 않기 때문에 일반 함수처럼 호출이 안된다. Couroutine Context를 가지고 있는 점을 이용해서 아래 코드처럼 바꿀 수 있다. 

 

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        CoroutineScope(Dispatchers.Main).launch {
            val sum = suspendSum(1, 2)
            Log.d("mainactivity coroutine", Thread.currentThread().name)
            Log.d("mainactivity coroutine", sum.toString())
        }
    }

    private suspend fun suspendSum(a: Int, b: Int) : Int = withContext(Dispatchers.IO) {
        Log.d("mainactivity suspend", Thread.currentThread().name)
        return@withContext a + b
    }
    
    
mainactivity suspend: DefaultDispatcher-worker-2
mainactivity coroutine: main
mainactivity coroutine: 3

 

위 코드를 보면 suspend 함수 body가 withContext로 싸여져 있는 것을 볼 수 있다. 아래 코드를 실행 할 때는 withContext 함수를 이용해 쓰레드를 바꿔서 실행할 수 있다. suspend + withContext를 활용하면 특정 함수에 대해서 실행 쓰레드를 정해 Main 함수를 건드리지 않고 안전하게 실행할 수 있게 된다.

 

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        CoroutineScope(Dispatchers.Main).launch {
            val sum = suspendSum(1, 2)
            Log.d("mainsuspendsum", sum.toString())
        }
    }

    private suspend fun suspendSum(a: Int, b: Int) : Int = withContext(Dispatchers.IO) {
        val deferredSum = async { a + b }
        val deferredZero = async { 0}
        return@withContext deferredSum.await() + deferredZero.await()
    }

 

상황에 따라서 내부에 async 로 새로운 job을 생성해서 실행이 가능하다. suspend 함수는 내부가 Coroutine과 완전히 동일하다고 봐도 된다. 코드를 좀더 간결하게 쓸 수 있는 도구가 될 것 같다.

728x90

'개발 > 안드로이드' 카테고리의 다른 글

Coroutine + Retrofit | Coroutine + Room  (0) 2021.07.22
suspend fun  (0) 2021.07.22
Single, Maybe, Completable  (0) 2021.07.04
Serializable 과 Parcelable  (0) 2021.06.19
kotlin lateinit, lazy by  (0) 2021.06.05
Kotlin - Coroutine  (0) 2021.05.21

오브젝트 리뷰 - 3

기술/아키텍처 2021. 7. 21. 19:00 Posted by 아는 개발자

https://book.naver.com/bookdb/book_detail.nhn?bid=15007773

 

오브젝트

역할, 책임, 협력을 향해 객체지향적으로 프로그래밍하라!객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를

book.naver.com

Chapter 5 리뷰 

 

책임 주도 설계를 향해 

 

데이터보다 행동을 먼저 결정하라. 객체에게 중요한 것은 데이터가 아니라 외부에 제공하는 행동이다. 클라이언트 관점에서 객체가 수행하는 행동이란 곧 객체의 책임을 의미한다. 

 

협력이라는 문맥 안에서 책임을 결정하라. 객체에게 할당된 책임의 품질은 협력에 적합한 정도로 결정된다. 객체에게 할당된 책임이 협력에 어울리지 않는다면 그 책임은 나쁜 것이다. 협력에 적합한 책임을 수확하기 위해선 객체를 결정한 후에 메시지를 선택하는 것이 아니라 메시지를 결정한 후에 객체를 선택해야 한다.

 

올바른 객체지향 설계는 클라이언트가 전송할 메시지를 결정한 후에 비로소 객체의 상태를 저장하는 데 필요한 내부 데이터에 관해 고민하기 시작한다. 결록적으로 협력이라는 문맥 안에서 객체가 수행할 책임에 초점을 맞춘다. 

 

책임 할당을 위한 GRASP 패턴 

 

INFORMATION EXPERT

 

객체에게 책임을 할당하는 첫 번째 원칙은 책임을 수행할 정보를 알고 있는 객체에게 책임을 할당하는 것이다. GRASP에서는 이를 INFORMATION EXPERT(정보 전문가) 패턴이라고 부른다. 정보 전문가 패턴은 객체가 자신이 소유하고 있는 정보와 관련된 작업을 수행한다는 일반적인 직관을 표현하는 것이며 여기서 말하는 정보는 데이터와는 다르다. 정보를 알고 있다고 해서 정보를 저장하고 있을 필요는 없다.

 

LOW COUPLING, HIGH COHESION

 

책임을 할당할 수 있는 다양한 대안들이 존재한다면 응집도와 결합도의 측면에서 더 나은 대안을 선택하는 것이 좋다. 다시 말해 두 협력 패턴 중에서 높은 응집도와 낮은 결합도를 얻을 수 있는 설계가 있다면 그 설계를 선택해야 한다는 것이다. GRASP에서는 이를 LOW COUPLING(낮은 결합도) HIGH COHESION(높은 응집도) 패턴이라고 부른다. 설계를 진행하면서 책임과 협력의 품질을 검토하는 데 사용할 수 있는 중요한 평가 기준이다.

 

응집도가 낮다는 것은 연관성이 없는 기능이나 데이터가 하나의 클래스 안에 뭉쳐있다는 것을 의미한다. 문제를 해결하기 위해선 변경의 이유에 따라서 클래스를 분리해야한다. 먼저 변경의 이유가 하나 이상인 클래스를 찾는 것으로부터 시작하는 것이 좋다. 변경의 이유를 파악할 수 있는 첫번째 방법은 인스턴스 변수가 초기화되는 시점을 살펴보는 것이다. 응집도가 높은 클래스는 인스턴스를 생성할 때 모든 속성을 함께 초기화 한다. 반면 응집도가 낮은 클래스는 객체의 속성 중 일부만 초기화하고 일부는 초기화되지 않은 상태로 남겨져 있다. 함께 초기화되는 속성을 기준으로 코드를 분리해야 한다. 

 

두번째 방법은 메서드들이 인스턴스 변수를 사용하는 방식을 살펴보는 것이다. 모든 메서드가 객체의 모든 속성을 사용하면 응집도가 높다고 볼 수 있으나 속성에따라 그룹별로 나뉜다면 클래스의 응집도가 낮다고 볼 수 있다. 응집도를 높이기 위해서는 속성 그룹과 해당 그룹에 접근하는 메서드 그룹을 기준으로 코드를 분리해야 한다.

 

POLYMORPHISM, PROTECTED VARIATION 

 

변화가 예상되는 불안정한 지점을 식별하고 그 주위에 안정된 인터페이스를 형성하도록 책임을 할당하라. PROTECTED VARIATION(변경 보호) 패턴은 책임 할당의 관점에서 캡슐화를 설명한 것이다. 클래스를 변경에 따라 분리하고 임터페이스를 이용해 변경을 캡슐화 하는 것은 설계의 결합도와 응집도를 향상시키는 매우 강력한 방법이다. 하나의 클래스가 여러 타입의 행동을 구현하고 있는 것처럼 보인다면 클래스를 분해하고 POLYMORPHISM 패턴에 따라 책임을 분산시켜라. 예측 가능한 변경으로 인해 여러 클래스들이 불안정해진다면 PROTECTED VARIATION 패턴에 따라 안정적인 인터페이스 뒤로 변경을 캡슐화하라. 

 

책임 주도 설계의 대안 

 

최대한 빠르게 목적한 기능을 수행하는 코드를 작성하고 리팩토링 한다.

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 5  (0) 2021.07.27
오브젝트 리뷰 - 4  (0) 2021.07.25
오브젝트 리뷰 - 3  (0) 2021.07.21
오브젝트 리뷰 - 2  (0) 2021.07.12
오브젝트 리뷰 - 1  (0) 2021.07.11
응집도(Cohesion)와 결합도(Coupling)  (0) 2021.07.10