[독서] 엘레강트 오브젝트 2장. 학습(1)

wally·2022년 6월 8일
0

독서시리즈

목록 보기
2/10

2.1. 가능하면 적게 캡슐화하세요

상태 없는 객체는 존재해서는 안되고, 상태는 객체의 식별자여야 한다.

  • 객체지향의 사실과 오해는 상태만 있는 것을 값객체로 보고 식별 가능한 식별자가 따로 존재해야 한다고 하였다.(entity 관점) 하지만 저자는 상태 그 자체들을 객체의 식별자로 보았다.(equals 오버라이딩)
  • 두가지는 관점의 차이이며 case by case 로 생각된다.

4개 이하의 객체로 캡슐화하라고 하지만 너무 원론적이다. 가능하면 상태를 적게 유지하여 복잡성을 줄여 유지보수성을 유지하자. 객체의 책임에 대해서도 생각하자.

2.2. 최소한 뭔가를 캡슐화하세요

저자는 객체의 식별자로 상태로 보기때문에 상태가 없는 객체는 OOP 적으로 맞지 않다고 이야기 하고 있다. 상태가 없다면 자기 자신을 식별하기 위해 다른 객체를 캡슐화 하라고 한다. 순수 OOP 관점에서는 상태가 있는것이 맞는 말인것 같긴하다.

Class Year{
	...
    Year(final int a){
    ...
    }
}
  • 생성자 에서 값을 final 로 받음으로써 생성자는 생성만하라는 책임에서 벗어나 다른 로직이 적용되 값이 변경되는 위험을 줄여줄 수 있다.

2.3. 항상 인터페이스를 사용하세요

  • 인터페이스는 강한 결합도를 느슨하게 만든다.
  • 인터페이스를 계약으로 표현하는데 클래스안의 퍼블릭 메서드가 인터페이승를 구현해야 하기 때문이다.
  • 만약 오버라이딩 하지않으면 Cash.cents() 와 같이 강한 결합도가 생기면 동시에 구현을 대체하기 어렵다.

복잡성을 줄이기 위해 인터페이스를 사용하자

2.4. 메서드 이름을 신중하게 선택하세요

1. 빌더는 명사다

int pow(int base, int power);

  • 빌더는 이름을 명사로 하고 반환값이 존재한다.
  • 내부 상태 변경이 아닌 반환의 목적이다.

2. 조정자는 동사다

void save(String content);

  • 조정자는 이름이 동사이고 반환값이 없다.
  • 내부 상태 변경이 목적이며 반환하지 않는다.
  • 객체는 객체에게 메세지를 보내며 그 메세지가 반드시 반환을 한다고 기대하면 안된다. 동시에 그 메세지가 객체의 자율성을 해치면 안된다.
    - void saveByDistInYourPocketThisIsWhatYouMustDo(String content);

3. 빌더와 조정자 혼합하기

  • 빌더 조정자 2개의 책임을 주지말아라. 책임 과다

4. Boolean 값을 결과로 반환하는 경우

  • 형용사를 활용하자

Boolean 같은 경우 동사가 아닌 형용사를 쓰라며 isEmpty 대신 empty 를 주장하지만 관습적 사용을 취소하면서 사용명을 바꾸는은 협력관점에서 너무 과도하다고 생각이든다.

빌더와 조정자는 분명 코드 작성을 하면서 동시에 사용하는것이 더 편하고 실제 그렇게 쓰이는 경우도 분명 존재할거라 생각한다. 하지만 명령쿼리분리원칙을 생각한다면 기본적으로 코드작성에서 안전한 방식이라고 생각한다.(명령에서 반환값이 있다면 명령이 실행되는줄 모르고 쿼리로 오해하여 메서드를 사용하는 불상사가 존재할 수 있다. - 쿼리용도인데 알고보니 상태변화 발생...)

2.5. 퍼블릭 상수(Public Constant)를 사용하지 마세요

토론하기 : http://goo.gl/QlUoru

  • 객체는 어떤것도 공유하면 안된다. - 공유는 캡슐화와 OOP 를 부정하는것
  • 독립적이고 닫혀 있어야 한다.
  • 따라서 private static final 을 통해 각 클래스내부에서만 사용하는 변수를 만드는 것은 책임을 아무대나 던져놓는 것과 동일하다.
  • private static final 을 통해서 사용하는 용도를 표현하는 클래스로 감싸주는 것이 더 낫다. 그래야 중복코드를 줄일 수 있지만 기능위주가 아닌 데이터 위주이므로 아직은 협력이라 보기 어렵다.
public calss Constants{
	public static final String EOL = "\r\n";
}
  • 이로써 코드 중복도 해결
  • 하지만 결합도가 높고 응집도가 낮다는 문제가 있다.

1. 결합도 증가

Constants.EOL 을 통해 하드 코딩을 하게 된다.

  • EOL 변경 시 이 값이 어떻게 사용되는 지 알 방법이 없다.
  • 즉 Constants.EOL 객체는 사용 방법과 관련된 어떤 정보도 제공하지 않은 채 모든 곳에서 접근 가능한 전역 가시성 안에 방치되어있다.

2. 응집도 저하

  • Constants.EOL 은 자신에 대해 아무것도 모른다.
  • 의미를 추가하기 위해 EOL 을 사용하는 객체에 코드를 추가 해야 하낟.
  • 따라서 기능을 공유하는 새로운 클래스를 만들자
    즉 데이터가 아닌 기능을 공유해야 한다.
class Test{
	private final String origin;
    Test(String src){
    	this.origin = src;
    }
	@Override
    String toString(){
		return String.format("%s\r\m", origin);
	}
}
  • 이런경우 상수가 아닌 메시지를 주고 받기 때문에 협력을 하게 된다.
  • 저자는 계약이라고 표현한다.
  • 예외처리도 해당 클래스에게 책임을 넘기면 된다.
  • 저자는 퍼블릭 상수마다 의미를 캡슐화하는 새로운 클래스를 만들라고 한다. 어느정도 공감한다.
  • 동일 단어를 자주 쓰면 그 의미는 모호해진다. 아예 새로운 단어들을 많이 쓰는게 의미상 명확하다
  • 열거형도 쓰지말라

어느정도 공감되는 글이다. 기능위주로 책임을 가지는 객체로 캡슐화하고 상수마다 의미를 캡슐화하라는 말은 인상깊었다. 하지만 열거형을 쓰지 말라는 말은 조금 지나친거 같다. 이미 열거형 자체가 상수가 가지는 문제를 해결하기 위해 나왔고, 다양한 기능들이 제공되기에 열거형에 긍정적이다.

2.6 불변 객체로 만드세요

토론하기 : http://goo.gl/zlXGjO

  • 가변객체는 존재해서는 안되고 엄격하게 금지해야 한다. 불변객체를 반드시 사용하라.
  • 지연 로딩등은 불변객체로 구현이 불가능 하므로 해킹의 일종인 캐싱 기법을 활용하자

불변객체의 장점

식별자 가변성

  • 변하지 않으므로 객체가 그자체로 식별자가 될 수 있다.

실패 원자성

  • 실패 원자성 이란 오나전하고 견고한 상태의 객체를 가지거나 실패하거나 둥중 하나만 가능. 중간은 없다.
  • 가변 객체가 값이 변하는 도중 예외를 던진다면 객체의 절반만 수정되는 불미스러운 일이 발생할 수 있다.
  • 명시적으로 가변 객체의 실패 원자성 보장은 객체의 복잡성을 높인다.

시간적 결합

  • null 할당 후 setter 로 값을 지정한다면 객체의 처리 순서가 있을시 코드 배열이 의미를 가지게 되고 이런경우 유지보수가 어렵다
  • 불변 객체는 new 를 통한 초기화만 가능하므로 이런문제 해결가능

부수효과제거(side-effect-free)

  • 객체를 바꿀 수 있다면 잘못하여 값을 바꾸는 부수효과를 가져온다.

null 참조 없애기

  • null 은 언제 객체가 유효한 상태이고 언제 객체가 아닌 다른 형태로 바뀌는지 이해하기 어렵다. 따라서 null 자체를 쓰지 않고 바로 초기화 시키자.

쓰레드 안전성

  • 멀티 쓰레드의 경우 동시 접근하여 객체가 원하지 않는 결과를 가져올 수 있다.
  • 이를 해결하기 위해 synchronized 가 있지만 비용이 크고, 데드락의 위험성이 있다.

개인 의견

지연 로딩을 캐시로 바꾼다고 하지만 결국 불변객체를 보장하기 위해 가변객체의 캐시를 이용하는 것이므로 완전하지 못한다.

그렇다면 가변객체는 장점이 없나?

  • 메모리 성능에서 불변객체보다 좋다(new 를 통한 메모리 할당이 없다)
  • 가변성 scope 를 제한함으로써 안정성을 보장할 수 있다.
    • local, function 등 scope 를 줄이면 가변성임에도 안정성을 보장할 수 있다.

그럼에도 불구하고 불변객체를 쓰자.

조정자와 불변객체

  • 사실상 불변객체는 상태수정이 안되기때문에 조정자를 쓸수가 없다. 그렇기 때문에 작가가 말하고자 하는 의도가 충돌한다.

코드적용

1. 열거형도 쓰지말아야 될까?

저자의 의도대로 기능위주의 자율성있는 객체를 원하면 쓰지 말아야 될거같다.
하지만 열거형이 필요한 용도에 쓰자


public class Test2_5_2 {
    public static void main(String[] args) {
        Double value = 5d;
        System.out.println(MathObject.PIE * MathObject.PIE * value);

        Pie pie = new Pie(value);
        System.out.println(pie.circleSize());

        System.out.println(PieEnum.GET_CIRCLE_SIZE.apply(value));
        Double pie1 = PieEnum.pie;
    }

    class MathObject {
        private static final Double PIE = 3.14;
    }

    static class Pie {
        private final Double value;
        private final Double pie = 3.14;

        public Pie(Double value) {
            this.value = value;
        }

        public Double circleSize() {
            return value * value * pie;
        }

        public Double circleLength() {
            return value * pie * 2;
        }
    }

    enum PieEnum {
        GET_CIRCLE_SIZE(1) {
            public double apply(double value) {
                return value * pie * pie;
            }
        },
        GET_CIRCLE_LENGTH(2) {
            public double apply(double value) {
                return value * pie * 2;
            }
        };

        private static final Double pie = 3.14;
        private final int value;

        PieEnum(int value) {
            this.value = value;
        }

        public abstract  double apply(double x);
    }
}

profile
클린코드 지향

0개의 댓글