엘레강트 오브젝트 - 새로운 관점에서 바라본 객체 지향 3장 (조영호 옮김, 지앤선)
OOP에서는 데이터를 대체하는 객체가 가장 중요한 지위를 차지합니다. 명령, 문장, 연산자는 더 이상 데이터를 책임지지 않습니다. 사실, 순순하고 완전한 객체지향 언어에서는 명령, 문장, 연산자가 존재해서는 안됩니다. 연산자 대신 오직 클래스와 인스턴스만을 포함해야 합니다.
- 96p
5개 이하의 public 메서드만 노출하세요.
public 메서드가 많을수록 커집니다. 클래스가 커질수록 유지보수성은 저하됩니다.
- 97p
정적 메서드를 사용하지 마세요
...
다음은 HTTP 요청을 전송해서 웹 페이지를 로드하는 기능을 구현한 '클래스'입니다.
class WebPage {
public static String read(String uri) {
// HTTP 요청을 만들고
// UTF-8 문자열로 변환한다.
...
이제 아주 간편한 방식으로 WebPage 클래스를 사용할 수 있습니다.
String html = WebPage.read("http://www.java.com");
read() 메서드는 제가 가장 강하게 반대하는 정적 메서드의 일종입니다. 더 나은 방식은 정적 메서드 대신 객체를 사용하는 것입니다(섹션 2.4에서 추천했던 명명 규칙에 따라 메서드의 이름도 변경했습니다.)
class WebPage {
private final String uri;
public String content() {
// HTTP 요청을 만들고
...
다음은 이 클래스를 사용하는 방법입니다.
String html = new WebPage("http:...").content();
- 99 ~ 100p
int max(int a, int b) {
if (a > b) {
return a;
}
return b;
}
이 코드의 어떤 점이 문제라는 것인지 의아할 지도 모르겠습니다. 사실 이 코드에 문제는 없습니다. 완벽하게 실행됩니다. 모든 컴퓨터는 실제로 이런 방식으로 동작합니다. 컴퓨터는 우리가 명령어를 제공해줄 것이라고 기대하고, 제공된 명령어를 하나씩 순차적으로 실행합니다. 이것이 프로그래머들이 수년간 소프트웨어를 작성해 왔던 방식입니다.
...
이런 순차적인 사고 방식을 가리켜 '컴퓨터 입장에서 생각하기'라고 부릅니다.
...
(def x (max 5 9))
우리는 CPU에게 할 일을 지시하는 것이 아니라 정의합니다. 앞의 예제는 x를 (max 5 9)에 바인딩합니다. 컴퓨터에게 최댓값을 계산하라고 요청하지 않습니다. 단순히 x는 이 두 수의 최댓값'이다(is a)'라고 이야기할 뿐입니다. 이 최댓값을 어떤 방식으로 계산하고 언제 계산하는지는 우리의 통제 밖에 있습니다. x는 최댓값'이다(is a)'라고 정의하는 것이 핵심입니다. 함수형, 논리형, 객체지향 프로그래밍이 절차적 프로그래밍과 차별화되는 점이 바로 이 'is a'입니다.
- 101 ~ 102p
OOP의 관점에서 최댓값을 계산하는 코드는 다음과 같아야 합니다.
class Max implements Number {
private final Number a;
private final Number b;
public Max(Number left, Number right) {
this.a = left;
this.b = right;
}
}
다음은 Max 클래스 사용방법입니다.
Number x = new Max(5, 9);
위 코드는 최댓값을 계산하지 않습니다. 그저 x가 5와 9의 최댓값이라는 사실을 정의할 뿐입니다.
- 102 ~ 103p
선언형 vs 명령형
선언형 -> 호출과 즉시 결과를 반환
public static int between(int l, int r, int x) {
return Math.min(Math.max(l,x),r);
between()메서드는 호출된 즉시 결과를 반환합니다.
int y = Math.between(5, 9, 13);
메서드를 호출한 시점에 CPU가 즉시 결과를 계산합니다. 이것이 바로 명령형 스타일입니다. 그렇다면 선언형 스타일은 어떨까요?
class Between implements Number {
private final Number num;
Between(Number left, Number right, Number x) {
this.num = new min(nex Max(left,x), right);
}
@Override
public int intValue() {
return this.num.intValue();
}
다음은 이 클래스를 사용하는 예제입니다.
Number y = new Between(5, 9, 13); // 아직 !
정적 메서드를 사용한 방식과의 차이점이 보이나요? 이 차이는 매우 중요합니다. 아직까지는 CPU에게 숫자를 계산하라고 말하지 않았기 때문에, 이 방식은 선언형 스타일입니다. 저는 Between이 무엇인지만 정의하고, 변수 y의 사용자가 intValue()의 값을 계산하는 시점을 결정합니다.
- 104 ~ 105p
두 번재 코드에서는 CPU에게 모든 것을 계산하라고 말하지 않습니다. 대신 CPU에게 결과가 실제로 필요한 시점과 위치를 결정하도록 우임하고, CPU는 요청이 있을 경우에만 계산을 실행합니다.
- 106p
1. 사실 더 빠르다
2. 다형성이 가능하다
선언형 방식을 따르는 코드
class Between implements Number {
private final Number num;
Between(int left, int right, int x) {
this(new Min(new Max(left, x), right));
}
Between(Number number) {
this.num = number;
}
Integer x = new Between(new IntegerWithMyOwnAlgorithm(5, 9, 13));
- 106 ~ 107p
3. 표현력 때문
=> 선언형 방식은 결과를 이야기하는데 반해, 명령형 방식은 수행 가능한 한 가지 방법을 이야기 한다!
Collections<Integer> evens = new LinkedList<>();
for (int numbers : numbers) {
if (number %s == 0) {
evens.add(number)
}
}
이 코드가 하는 일을 이해하기 위해서는 코드의 실행경로를 추적해야 합니다. 코드 안의 루프를 '마음 속으로 시각화'해야 합니다. 배열의 요소를 검사하고 새로운 리스트에 짝수를 추가하는 일과 같은 근본적으로 CPU가 수행해야 하는 일을 코드를 읽는 사람도 동일하게 수행해야 합니다. 다음은 동일한 알고리즘을 선언형 스타일로 작성한 예입니다.
Collection<Integer> evens = new Filtered(
numbers,
new Predicate<Ineger>() {
@Override
public boolean suitable(Integer number) {
return number % 2 ==0;
}
}
}
이 코드는 앞의 코드보다는 영어에 훨신 더 가깝습니다. 이 코드는 글자 그대로 "evens는 짝수만 포함하는 필터링된 컬렉션입니다."라고 읽힙니다. 저는 Filtered 클래스가 이 컬렉션을 어떻게 생성하는지 모릅니다. ... 코드에는 구현과 관련된 세부 사항은 감춰져 있고, 오직 행동만 표현되어 있습니다.
- 108p
Groovy 사용도 방법
def evens = new Filtered( numbers, { Integer number -> number % 2 == 0} );
- 109p
유틸리티 클래스와 싱글턴 클래스를 구분하는 핵심적인 차이는 뭘까요? ... 싱글톤은 분리 가능한 의존성으로 연결되어 있는데 반해, 유틸리티 클래스는 분리가 불가능한 하드코딩된 결합도를 가진다는 것입니다. 다시 말해서 싱글톤의 장점은 getInstance()와 함께 setInstance()를 추가할 수 있다는 점입니다.
- 114p
다음은 싱글톤 클래스 Math를 사용하는 코드입니다.
Math.getInstance().max(5,9);
이 코드는 Math 클래스에 결합되어 있습니다. 다시 말해서, Math 클래스는 이 코드가 의지하고 있는 의존성입니다. Math 클래스가 없으면 코드가 동작하지 않기 때문에, 코드를 테스트하기 위해서는 Math 클래스 요청을 처리할 수 있는 상태로 만들어야 합니다.
Math math = new FakeMath();
Math.setInstance(math);
싱글톤 패턴을 사용하면 내부에 캡슐화된 정적 객체를 교체해서 전체 개념을 테스트할 수 있습니다.
- 114p
싱글톤이 유틸리티 클래스보다 낫다고는 하지만 여전히 좋지 않다.
- 115p
논리적인 관점과 기술적인 관점에서 모두 싱글톤은 전역 변수 그 이상도 그 이하도 아니기 때문입니다.
- 115p
싱글톤은 객체지향 패러다임을 잘못 사용한 예이며, 오직 정적 메서드가 있었기 때문에 탄생할 수 있었습니다.
- 115p
다음과 같은 절차적인 코드 대신,
float rate;
if (client.age() > 65) {
rate =2.5;
} else {
rate = 3.0;
}
다음과 같은 객체지향적인 코드
float rate = new If(client.age() > 65, 2.5, 3.0);
객체지향적인 방식으로 조금 더 개선한 다음 코드는 어떤가요?
float rate = new If(new Greater(client.age(), 65), 2.5, 3.0);
그리고 마지막으로 개선한 최종 코드입니다.
float rate = new If(new GreaterThan(new AgeOf(client), 65), 2.5, 3.0);
- 119p
정적 메서드는 조합이 불가능합니다. 정적 메서드는 바로 앞에서 설명한 모든 일들을 불가능하게 만듭니다. 정적 메서드를 포함하는 작은 객체들을 조합해서 더 큰 객체를 만들 수가 없습니다. 간단히 말해서 정적 메서드는 합성(composition)이라는 아이디어와 대치됩니다. 이것이 OOP에서 정적 메서드를 사용해서는 안되는 또 다른 이유입니다.
- 120p
'Book' 카테고리의 다른 글
[독서 기록] 자바로 배우는 리팩토링 입문 (0) | 2022.11.22 |
---|---|
[독서 기록] 엘레강트 오브젝트 - 새로운 관점에서 바라본 객체 지향 3장 - 2 (2) | 2022.11.19 |
[독서 기록] 엘레강트 오브젝트 - 새로운 관점에서 바라본 객체 지향 2장 (0) | 2022.11.16 |
[독서 기록] 엘레강트 오브젝트 - 새로운 관점에서 바라본 객체 지향 1장 (0) | 2022.11.16 |
[독서 기록] 헤드퍼스트 디자인 패턴 11장 | 프록시 패턴 - 객체 접근 제어하기 (0) | 2022.11.15 |