본문 바로가기
책(독서)

[켄트벡의 구현패턴] ~154p 메소드

by DevJR 2020. 9. 16.

- 메소드 

 임의의 프로그램은 복잡한 제어 흐름이 들어 있는 커다란 루틴 이라고 볼 수 있다.

여기에서의 문제는 이 커다란 루틴에서는 로직상 중요 부분중요하지 않은 부분을 구분해서 읽기가 어렵다는 것이다.

또한, 이처럼 거대한 루틴에서는 반복되는 코드가 나올 수 있는데, 하나의 큰 로직에서는 이러한 중복을 제거하기 쉽지 않다. 

이를 위해 메소드를 나누는 방식을 사용하면 읽기 쉬운 코드로 나눌 수도 있고, 반복되는 코드를 메소드를 통해 재사용할 수 있는 효과를 거둘 수 있다.

 

1. 조합 메소드

- 추상화 수준이 비슷한 메소드 호출로 하나의 메소드를 구성하라.

void calculate() {
   input();
   number = 1+x;
   output();
}

(책의 예시와는 조금 다르지만, 형태는 비슷하다.)

위 로직을 보면 중간 number = 부분은 위 아래 메소드 조합과 어울리지도 않고 의도를 한눈에 파악하기가 어렵다.

통일시켜 주는 게 낫다.

더불어 이렇나 조합 메소드를 한번에 구현하기는 어려우므로 작동하는 로직을 한번에 짜고 리팩토링을 통해 메소드를 나누는 방향이 저자의 방법이라고 한다.

 

2. 의도 제시형 이름

- 메소드 이름을 통해 메소드의 의도를 쉽게 파악할 수 있어야 한다.

but, 메소드 의도만 표시하면 되는 것이지 굳이 세부 구현이 어떻다 하는 것까진 필요 없다.

User.dataBaseSearchById(String id);

이런식으로 메소드를 통해 메소드의 의도를 나타내는 것은 좋으나, 오히려 메소드명을 다 읽을 때까지 한눈에 파악하기가 힘들다.

User.find(String id);

이런식으로 어떤 '의도' 인지만 표현하면 된다. 세부 구현이 궁금한 사람은 해당 메소드를 찾아서 보면 된다.

 

3. 메소드 가시성

- 네가지 수준이 있는데, 가시성이 높을 수록 사용하는 측에서는 필요 이상으로 많은 작업을 해야 함. 반대로 가시성이 낮을 수록 미래의 유연성이 (미래에 해당 인터페이스를 수정하는 것이 쉬운정도) 높다.

공용 : 패키지 외부에서도 이 메소드가 유용하다는 걸 의미

패키지 : 같은 패키지의 다른 객체에는 유용하나 패키지 외부의 객체에는 공개하지 않겠다는 의미

보호 : 하위 클래스는 접근 가능하다는 의미. (다른 패키지여도 자식(하위) 클래스라면 접근 가능)

전용 : 오직 해당 객체 내에서만 사용 가능.

 

final : 메소드를 사용하는 것은 자유지만, 해당 메소드를 더 이상 바꿀 수는 없다는 의미.

static : static으로 선언된 메소드는 해당 클래스의 인스턴스에 접근할 수 없을 때에도 접근 가능. (권한있을 때)

         인스턴스 생성과 무관하게 사용할 수 있는 메소드라는 의미(정적메소드 - 복잡한 로직에는 적합하지 x)

 

4. 메소드 객체

- 조금 더 다시 봐야 할 것 같다. 머리로는 이해하는 거 같은데 딱 예시로 쓰려니 막힌다.

 

5. 오버라이드

- 상위 클래스에서는 공통부분을 구현하고 하위 클래스에서는 특화한 연산을 하라는 것.

말 그대로 다시 써서(엎어써서) 구현을 하위 클래스에서 하는 것이다.

 

6. 오버로드

- 파라미터 받는 갯수 및 타입에 따라 여러개의 메소드 구현.

  '이 메소드를 사용할 수 있는 다양한 포멧이 존재한다.' 라는 의미

  메소드 자체의 연산은 동일해야 함. 다를 경우 메소드 명을 다르게 구현해야 함.

 

7. 메소드 반환 타입

- 반환 타입은 프로시저(void)와 함수(반환타입 존재)를 구별할 수 있게 해준다.

  반환 타입을 지정할 때에는 기본형 타입보다는 추상적인 타입을 정하는 것이  추후 반환 클래스의 타입을 유연하게 변경할 수 있다.

 

8. 메소드 주석

- 코드 이름과 구조를 통해 최대한 많은 정보를 주는 것이 좋으나, 이것만으로 부족할 때에는 주석을 사용.

그러나 자동화된 테스트 작성 등을 통하는 방식이 주석보다 나을 수 있다.

 

9. 도우미 메소드

- 메소드를 나누다보면, 작은 단위의 도우미 메소드가 필요.

공통으로 계속 반복되는 부분을 도우미 메소드로 구현하면 수정이 필요할 때, 해당 도우미 메소드만 수정하면 되므로 편리하다.

 

10. 디버그 출력 메소드

- Object 인터페이스 중 하나인 toString()을 통해 해당 객체에 대한 정보를 표현할 수 있다. 이 방식을 사용하면 복잡한 디버깅 과정보다 쉽게 해당 객체에 대한 정보를 얻을 수 있다.

 

11. 변환 

- 객체 A를 가진 상태에서 이후 연산을 위해 객체 B가 필요한 경우 객체를 변환한다. 변환의 방법은 여러가지가 존재한다.

 

12. 변환 메소드

class Autumn {
   Winter asWinter() {
	...
   }
}

반환타입을 변환하는 객체 타입으로 지정하여 변환한다.

변환 메소드보다는 변환 생성자 사용을 선호.

 

13. 변환 생성자

File file = new File(String fileName);

원본 객체(위 예시에서는 String) 를 파라미터로 취해서 대상 객체(File)를 반환 

 

14. 생성

- 크기가 큰 프로그램은 규모가 더 작은 프로그램보다 수정이 어렵다. 작게 나눌수록 수정이 쉽다. 

객체를 작게(여러개) 생성하여 처리하면 관리하기가 쉽다는 의미(?)

 

15. 완결 생성자

- 모든 생성자가 동일한 하나의 생성자를 사용해서 모든 초기화를 하도록 함.

 

16. 공장 메소드 

- 복잡한 객체를 생성할 때, 공장 메소드를 사용. but 일반메소드와 차이를 두기 위해 객체 생성 이외의 작업이 포함될 때 공장 메소드를 활용.

 

17. 내부 공장

- 설명이 더 필요하거나 추후 개선이 필요한 객체 생성의 경우 도우미 메소드로 캡슐화

 

18. 컬렉션 접근자 메소드

- 컬렉션에 제한적인 접근만 허용한다. 사용자가 컬렉션을 직접 조작하게 하면 안됨.

List<User> getUsers() {
   return users;
}

위처럼 하지 말고 아래처럼 처리해야 함.

List<User> getUsers() {
   return Collections.unmodifiableList(users);
}

 

19. 불린 설정 메소드

- 커뮤니케이션에 도움이 된다면, boolean값을 설정하는 두개의 메소드를 각각 제공하는게 낫다.

void valid () {
   ...
   return true;
}

void invalid() {
   ...
   return false;
}

20. 질의(쿼리) 메소드

- 조건문에 들어가는 객체(메소드)는 isXXX 라는 형태의 이름으로 명명한다. (가독성에 좋다.)

 

21. 동등성 메소드

- 객체의 동일성이 아닌 '동등성' 을 비교해야 하는 경우 equals() 와 hashcode()를 구현하라

 

22. 취득 메소드

- get으로 시작하는 메소드. 로직이 있는 곳에서 취득 메소드를 사용하지 말고 되도록 데이터가 있는 곳에서 연산 로직을 사용할 수 있도록 구현한다. (취득메소드는 되도록 지양)

 

23. 설정 메소드

- set으로 시작하는 메소드. 이 또한 취득 메소드와 마찬가지로 외부에서 설정 메소드 사용을 피하는 것이 좋다.

 

24. 안전한 복사

- 접근자 메소드를 통해 전달하거나 전달되는 인스턴스를 복사해서 앨리어스 문제 회피