Search

08: 메소드

생성일
2021/10/26 07:57
태그

변환(Conversion)

객체 간의 변환은 어떻게 표현하는 것이 좋을까?
다른 패턴들과 마찬가지로 변환 패턴의 목표는 프로그래머의 의도를 명확히 전달하는 것
변환을 효과적으로 표현하기 위해 고려해야하는 몇 가지 기술적 사항
변환이 얼마나 다양한가
변환을 위해 새로운 의존성이 추가되는가?

변환 메서드(Conversion Method)

변환에 대한 요구사항이 적다면, 아래와 같이 기존 객체에 메서드를 추가해서 변환을 나타낼 수 있음
class Polar { Cartesian asCartesian() { ... } }
Java
복사
주목해야할 점은 변환 메서드의 타입대상 객체(destination object)라는 것
변환의 포인트는 다른 프로토콜(인터페이스?)을 가진 객체를 얻는 것
변환 메서드는 가독성이 좋다는 장점 때문에 널리 사용됨
그러나 변환 메서드를 만들기 위해서 원본 객체의 프로토콜을 변경해야함
의존성이 존재하지 않았는데 변환 메서드 때문에 의존성이 생기는 것은 바람직하지 않음
특정 클래스에 asXXX()와 같은 메서드가 다수 존재하는 경우, 코드를 관리하기 힘들어짐
이런 경우 변환을 이용하는 대신, 클라이언트(대상 객체) 측에서 원본 객체를 다룰 수 있게 하는 것이 좋음
이러한 담점들로 인해 변환 메서드는 자주 사용되지 않으며,
대개의 경우 변환 메서드보다는 변환 생성자 사용을 선호함

변환 생성자(Conversion Constructor)

변환 생성자는 원본 객체를 파라미터로 취해서 대상 객체를 반환함
변환 생성자는 하나의 원본 객체를 여러 다른 대상 객체로 변환할 때 유용함
변환을 사용하더라도 원본 객체의 코드가 지저분해지지 않기 때문
String.asFile() -> X File(String name) -> O URL(String spec) -> O StringReadStream(String contents) -> O
Java
복사

생성(Creation)

크기가 작은 프로그램은 큰 프로그램에 비해서 수정하기 쉬움
따라서 프로그램 수정을 쉽게 하기 위한 초기 전략은 커다란 프로그램을 수행하는 하나의 컴퓨터를 더 작은 프로그램을 수행하는 여러 개의 컴퓨터(객체)로 나누는 것이었음
객체 사용으로 인해 프로그램 수정 비용은 크게 낮아짐
객체 생성은 독자들에게 연산에 대해 관련된 세부 내용은 당장 알 필요 없다는 메시지를 전달해줌
의미 있는 객체를 생성하기 위해서는 명확하고 직접적인 표현과 유연성 사이에서 균형을 잡아야함
생성과 관련된 구현 패턴을 이용하면 "객체 만들기"를 더 잘 표현할 수 있음

완결 생성자(Complete Constructor) = 완전한 생성자

객체는 연산을 수행하기 위해 정보를 필요로함
생성자를 통해 사용자(=클라이언트 개발자)에게 전제 조건(필요한 정보)을 전달해야함
객체를 설정하는 방법이 여러 가지라면, 각 경우마다 제대로 된 객체를 반환하는 생성자를 제공해야함
때로는 인자를 사용하지 않는 생성자와 여러 개의 설정 메서드를 사용해서 객체를 생성하는 것이 좀더 유연성을 높이는 경우가 있음
하지만 이런 경우 독자 입장에서 온전한 객체를 생성하기 위해 어떤 파라미터들이 필요한지 알기 어려움
new Rectangle(0, 0, 50, 200);
Java
복사
Rectangle box = new Rectangle(); box.setLeft(0); box.setWidth(50); box.setHeight(200); box.setTop(0);
Java
복사

공장 메서드(Factory Method)

Rectangle.create(0, 0, 50, 200);
Java
복사
객체 생성을 나타내는 다른 방법은 클래스의 정적 메서드를 사용하는 것임
정적 메서드는 생성자에 비해 몇 가지 장점을 가짐
공장 메서드 장점
정적 메서드는 추상 타입을 반환할 수 있음
인터페이스 혹은 추상클래스에서는 생성자를 선언할 수 없음
의도가 담긴 별도의 이름을 사용할 수 있음
객체를 생성하는 것보다 복잡한 작업을 수행하는 경우 유용함
그러나 공장 메서드를 사용하면 복잡성이 증가하므로, 공장 메서드는 이득이 있을 경우에만 사용해야함
혹은 단순 객체 생성 이외에 다른 의도가 있을 경우에만 사용해야함

내부 공장(Internal Factory)

객체 생성을 위한 메서드가 private이지만, 복잡하거하위 클래스에 의해 변경되어야 한다면 어떻게 해야할까?
내부 공장 패턴은 게으른 초기화(lazy initialization)를 사용하는 경우 흔히 사용됨
getX() { if (x == null) x = ... ; return x; }
Java
복사
변수 x에 저장할 객체 생성 로직이 복잡한 경우에는 내부 공장을 사용하는 편이 좋음
getX() { if (x == null) x = computeX(); return x; } class Child extends Parent { @Override X computeX() { ... return x; } }
Java
복사
내부 공장은 객체 생성 로직을 하위클래스에서 변경할 수 있다는 것을 의미함

컬렉션 접근자 메서드(Collection Accessor Method)

class Library { List<Book> getBooks() { return books; } }
Java
복사
컬렉션에 대한 접근을 위 코드 처럼 직접 반환하는 경우, 클라이언트 클래스가 컬렉션을 직접 조작하게 되므로 Library 객체의 내부 상태의 유효성을 잃을 수 있음
List<Book> getBooks() { return Collections.unmodifiableList(books); }
Java
복사
해결 방법 중 하나로 컬렉션을 반환하기 전에 불변 컬렉션으로 바꿔서 반환하는 것임
하지만 컬렉션을 수정하려고 하면 예외가 발생함
클라이언트 개발자는 해당 객체가 불변인지 아닌지 모를 수 있음
이런 방법 대신 좀 더 의미적인(meaningful) 접근을 제공하는 메서드를 사용하는 것이 좋음
void addBook(Book arrival) { books.add(arrival); } void bookCount() { return books.size(); }
Java
복사
하지만 만약 여러분이 대부분의 컬렉션 인터페이스를 중복해서 구현하고 있다면, 설계상에 문제가 있을 확률이 높음
객체에서 클라이언트가 필요로 하는 적합한 작업을 제공한다면, 내부 데이터 접근을 많이 허용해야 할 필요가 없음
Ex. borrowBook(), returnBook()

불린 설정 메서드(Boolean Setting Method)

불린 상태를 설정하는 간단한 해법은 설정 메서드(Setter)를 사용하는 것
void setValid(boolean newState) { ... }
Java
복사
만약 설정 메서드의 인자가 언제나 상수값(참 혹은 거짓)이라면, 각 불린 상태마다 더 명확한 인터페이스를 제공할수도 있음
void valid() void invalid()
Java
복사
위 처럼 상태에 따라 인터페이스를 만들어주는 것이 코드가 더 읽기 쉽고, 언제 어떤 상태로 변하는지 알기 쉬움
단, 다음과 같은 코드 형태라면 설정 메서드를 사용하는 편이 나음
Boolean valid = true; if (valid == true) cache.valid(); else cache.invalid();
Java
복사
위 코드에 설정 메서드를 사용하면 아래와 같음
cache.setValid(valid);
Java
복사

질의 메서드(Query Method)

하지만 메서드를 통해 다른 객체의 상태를 알아야하는 경우에는 "be" 동사나 "have" 동사를 사용하면됨
class Client { ... if (widget.isVisible()) widget.doSomething(); else widget.doSomethingElse(); ... }
Java
복사
클라이언트 객체가 다른 객체의 상태에 의존적인 로직을 많이 가지고 있다면, 이는 로직의 위치에 문제가 있다는 신호임
위 코드의 경우 widget 객체로 로직의 위치를 옮기는 것이 나음
class Client { ... widget.run(); ... } class Widget { void run() { if (isVisible()) doSomething(); else doSomethingElse(); } }
Java
복사

취득 메서드(Getting Method)

일명 Getter
로직과 데이터를 함께 배치한다는 원칙에 입각하면, Public 혹은 Default 접근 제한자를 취하고 있는 취득 메서드를 사용하는 것은 객체 내부에 특정 로직이 있다는 힌트가됨
따라서 무작정 취득 메서드를 제공하는 것보다는 가급적 필요한 로직을 데이터가 있는 쪽으로 옮기는 것이 옳음
하지만 다른 도구(tool)에서 취득 메서드를 요구하는 상황처럼, 공용 취득 메서드를 사용해야하는 경우도 있음

설정 메서드(Setting Method)

일명 Setter
설정 메서드의 가시성을 높이는 것은 취득 메서드의 가시성을 높이는 것 보다 더 주의를 기울어야함
설정 메서드의 이름은 의도가 아닌 구현에 의해 정해짐
즉, 객체의 내부 구현을 직접적으로 노출 시킴
설정 메서드가 필요하다면, 클라이언트가 값 설정을 통해 어떤 문제를 해결할 수 있는지 파악하고, 그 문제를 직접해결할 수 있도록 하는 메서드를 그 객체에게 할당하라
취득 메서드와 마찬가지로, 어떤 도구(tool)에서 설정 메서드를 호출해야 한다면, "도구 전용"이라는 주석을 붙인 후 해당 메서드를 공용으로 만들라

안전한 복사

취득 메서드나 설정 메서드를 사용하는 경우 발생하는 문제를 안전한 복사를 통해 어느정도 해결할 수 있음
List<Book> getBooks() { List<Book> result = new ArrayList<Book>(); result.addAll(books); return result; }
Java
복사
위의 경우 단순히 컬렉션 접근자 메서드를 제공하는 편이 나음
하지만 전체 데이터에 대한 접근을 제공하기 위해서라면 이 기법을 사용하는 것이 더 안전함
List<Book> setBooks(List<Book> newBooks) { List<Book> result = new ArrayList<Book>(); result.addAll(books); }
Java
복사
설정 메서드도 위 처럼 안전한 복사를 통해 구현할 수 있음
하지만 안전한 복사 또한 문제점을 여전히 가짐
처리하고자 하는 컬렉션의 크기가 크면 성능이 대폭 저하됨
안전한 복사는 외부 접근에서 코드를 보호하는 일시적인 해결책일 뿐임
근본적인 해결 방법은 프로토콜을 좀 더 의미 있게 수정하는 것