[자바로 배우는 리팩토링 입문] 4장 널 객체 도입

2022. 4. 15. 19:25·Major/Java
728x90

1. 리팩토링

1) 널 객체 도입 

- 널 객체를 사용하면 '이 변수는 현재 null인가' 라는 확인을 생략할 수 있음

 

2) 리팩토링 카탈로그

이름 널 객체 도입 (Introduce Null Object)
상황 객체를 다룸
문제 Null 확인이 너무 많은 경우
해법 Null을 나타내는 특별한 개체를 도입해 ‘아무것도 안 함’ 이라는 처리를 하자
결과 장점 : null 확인이 줄어든다.
단점 : 널 객체만큼 클래스가 늘어난다.

 

2. 예제 프로그램

클래스명 역할
Person 사람을 나타내는 클래스(이름과 메일 주소를 담는다)
Label 표시 가능한 문자열을 나타내는 클래스
Main 동작 확인용 클래스

1) 리팩토링 전

public class Person {
    private final Label _name;
    private final Label _mail;
    public Person(Label name, Label mail) {
        _name = name;
        _mail = mail;
    }
    public Person(Label name) {
        this(name, null);
    }
    public void display() {
        if (_name != null) {
            _name.display();
        }
        if (_mail != null) {
            _mail.display();
        }
    }
    public String toString() {
        String result = "[ Person:";

        result += " name=";
        if (_name == null) {
            result += "\"(none)\"";
        } else {
            result += _name;
        }

        result += " mail=";
        if (_mail == null) {
            result += "\"(none)\"";
        } else {
            result += _mail;
        }

        result += " ]";

        return result;
    }
}
public class Label {
    private final String _label;
    public Label(String label) {
        _label = label;
    }
    public void display() {
        System.out.println("display: " + _label);
    }
    public String toString() {
        return "\"" + _label + "\"";
    }
}
public class Main {
    public static void main(String[] args) {
        Person[] people = {
            new Person(new Label("Alice"), new Label("alice@example.com")),
            new Person(new Label("Bobby"), new Label("bobby@example.com")),
            new Person(new Label("Chris")),
        };

        for (Person p : people) {
            System.out.println(p.toString());
            p.display();
            System.out.println("");
        }
    }
}

 

2) 리팩토링 실행

 

⑴ 널 객체 클래스 작성

① 널 객체 클래스 작성

- Label의 하위 클래스로 널 객체 틀래스를 작성한다

public class NullLabel extends Label { }

② isNull 메서드 작성

- 널 객체인지 판정하는 isNull 메서드를 작성한다.

- 기존 Label 클래스는 false를 반환하고, NullLabel 클래스는 true를 반환한다.

public class Label {
    ...
    public boolean isNull() {
        return false;
    }
}
public class NullLabel extends Label {
    @Override public boolean isNull() {
        return true;
    }
}

③ 컴파일

- 컴파일 에러가 나는데, 에러가 나는 이유는 NullLabel 클래스가 Label클래스를 상속받았는데, 

  Label클래스에는 기본생성자가 없기때문에 묵시적인 생성자 호출이 안된다. 따라서 직접 명시적으로 호출해야한다.

public NullLabel() {
    super("(none)");
}

⑵ null 치환하기

① null을 널 객체로 치환한다.

② null 확인을 isNull 메서드 호출로 치환

③ 컴파일해서 테스트

 

⑶ 널 객체 클래스를 재정의해서 조건 판단 삭제하기

① isNull 메서드를 사용하는 조건 판단문 찾기

② 널 객체 클래스에서 doSomething 메서드를 오버라이드함. 이 메서드에는 <null 동작>을 작성함

public class NullLabel extends Label {
    ...
    @Override public void display() {
    }
}

③ 조건 판단 삭제

④ 컴파일해서 테스트

 

3) 리팩토링 후

public class Person {
    private final Label _name;
    private final Label _mail;
    public Person(Label name, Label mail) {
        _name = name;
        _mail = mail;
    }
    public Person(Label name) {
        this(name, new NullLabel());
    }
    public void display() {
        _name.display();
        _mail.display();
    }
    public String toString() {
        return "[ Person: name=" + _name + " mail=" + _mail + " ]";
    }
}
public class Label {
    private final String _label;
    public Label(String label) {
        _label = label;
    }
    public void display() {
        System.out.println("display: " + _label);
    }
    public String toString() {
        return "\"" + _label + "\"";
    }
    public boolean isNull() {
        return false;
    }
}
public class NullLabel extends Label {
    public NullLabel() {
        super("(none)");
    }
    @Override public void display() {
    }
    @Override public boolean isNull() {
        return true;
    }
}

 

3. 한 걸음 더 나아가기

1) 팩토리 메서드 패턴

- 인스턴스 생성에 직접 클래스 명을 사용하지 않는 것 

public static Label newNull() {
    return new NullLabel();
}

new NullLabel() => Label.newNull()

2) 싱글톤 패턴

- 널 객체가 필요할 때마다 new NullLabel()로 인스턴스를 작성하는 것은 시간과 메모리 낭비이다.

- 따라서 싱글톤 패턴으로 실제 인스턴스를 줄이는 게 낫다.

싱글톤 패턴 : 어떤 클래스가 최초 한번만 메모리를 할당하고(Static) 그 메모리에 객체를 만들어 사용하는 디자인 패턴

 

3) 널 객체로 중첩 클래스 사용

- 널 객체를 도입하면 null 확인이 줄어들지만 클래스 개수가 늘어난다.

- 널 객체 클래스를 원래 클래스의 중첩 클래스(클래스안에 클래스)로 구현하면 클래스 관리가 조금 편해진다.

- 팩토리 메서드 패턴을 사용하면 NullLabel 이라는 클래스 이름은 Label 클래스 외부에 노출되지 않아도 되므로, NullLabel클래스는 private로 선언한다.

public class Label {
    private final String _label;
    public Label(String label) {
        _label = label;
    }
    public void display() {
        System.out.println("display: " + _label);
    }
    public String toString() {
        return "\"" + _label + "\"";
    }
    public boolean isNull() {
        return false;
    }
    // 팩토리 메서드
    public static Label newNull() {
        return NullLabel.getInstance();
    }

    // 널 객체
    private static class NullLabel extends Label {
        // 싱글톤
        private static final NullLabel singleton = new NullLabel();
        private static NullLabel getInstance() {
            return singleton;
        }
        private NullLabel() {
            super("(none)");
        }
        @Override public void display() {
        }
        @Override public boolean isNull() {
            return true;
        }
    }
}
public class Person {
    private final Label _name;
    private final Label _mail;
    public Person(Label name, Label mail) {
        _name = name;
        _mail = mail;
    }
    public Person(Label name) {
        this(name, Label.newNull());
    }
    public void display() {
        _name.display();
        _mail.display();
    }
    public String toString() {
        return "[ Person: name=" + _name + " mail=" + _mail + " ]";
    }
}

 

4) null 확인이 나쁜가?

- 그건 아니지만, null 확인이 너무 많으면 널객체로 바꾸면 실수를 방지할 수 있다

 

5) 패턴 중독에 빠지지 않기

- 패턴을 적용할때도, 리팩토링할 때도 "지금 여기서 해결해야 하는 문제는 무엇인가" 를 의식해야한다.

  • null 확인이 너무 많다면 널 객체를 도입하자!
  • 클래스명을 은폐할려면 팩토리 메소드 패턴을 사용하자
  • 메모리 소비량이 많으면 싱글톤 패턴을 사용하자!

위 3가지가 무조건적으로 해야하는것은 아니다.

 

6) 상수와 널 객체

- newNULL 이라는 메서드로 널 객체를 제공하는 게 아니라 NULL이라는 클래스 필드를 선언하는 방식도 있음

- 이렇게 하면 '널 객체는 상수의 한 종류다'라는걸 명확하게 소스코드로 표현 가능

public class Label {
    public final static Label NULL = new NullLabel();
    ...

    // 널 객체
    private static class NullLabel extends Label {
        private NullLabel() {
            super("(none)");
        }
        @Override public void display() {
        }
        @Override public boolean isNull() {
            return true;
        }
    }
}

 

7) isNull 메서드는 필요한가

- isNull 메서드는 실제 코드에 따라서 만들어도 되고.. 안만들어도 된다.

 

8) 기존 클래스를 수정할 수 없다면

obj.isNull() => obj instanceof Null 을 사용

- 상위 클래스를 변경할 수없으면 위 방법이 해결책이다. (마커 인터페이스)

728x90
저작자표시 비영리 (새창열림)

'Major > Java' 카테고리의 다른 글

[자바로 배우는 리팩토링 입문] 6장 클래스 추출  (0) 2022.06.02
[자바로 배우는 리팩토링 입문] 5장 메서드 추출  (0) 2022.06.01
[자바로 배우는 리팩토링 입문] 3장 어서션 도입  (0) 2022.04.15
[자바로 배우는 리팩토링 입문] 2장 제어 플래그 삭제  (0) 2022.04.13
[자바로 배우는 리팩토링 입문] 1장 매직 넘버를 기호 상수로 치환  (0) 2022.04.13
'Major/Java' 카테고리의 다른 글
  • [자바로 배우는 리팩토링 입문] 6장 클래스 추출
  • [자바로 배우는 리팩토링 입문] 5장 메서드 추출
  • [자바로 배우는 리팩토링 입문] 3장 어서션 도입
  • [자바로 배우는 리팩토링 입문] 2장 제어 플래그 삭제
BeNI
BeNI
코딩하는 블로그
  • BeNI
    코딩못하는컴공
    BeNI
  • 전체
    오늘
    어제
    • Menu (253)
      • My profile (1)
      • 회고 | 후기 (8)
      • Frontend (65)
        • Article (11)
        • Study (35)
        • 프로그래머스 FE 데브코스 (19)
      • Backend (0)
      • Algorithm (58)
        • Solution (46)
        • Study (12)
      • Major (111)
        • C&C++ (23)
        • Java (20)
        • Data Structure (14)
        • Computer Network (12)
        • Database (15)
        • Linux (6)
        • Architecture (3)
        • Lisp (15)
        • OS (1)
        • Security (2)
      • etc (2)
  • 링크

    • 깃허브
    • 방명록
  • 인기 글

  • 최근 댓글

  • 최근 글

  • 태그

    리팩토링
    lisp
    프로그래머스
    Algorithm
    C++
    데브코스
    백준
    react
    파일처리
    자료구조
  • hELLO· Designed By정상우.v4.10.2
BeNI
[자바로 배우는 리팩토링 입문] 4장 널 객체 도입
상단으로

티스토리툴바