객체지향 생활체조 원칙은 소트웍스 앤솔러지(ThoughtWorks Anthology) 라는 책에 나오는 원칙이다.

목차

  1. 한 메서드에 오직 한 단계의 들여쓰기(indent)만 한다.
  2. else 예약어를 사용하지 않는다.
  3. 모든 원시 값과 문자열을 포장한다.
  4. 일급 컬렉션을 쓴다.
  5. 한 줄에 점을 하나만 찍는다.
  6. 줄여 쓰지 않는다 ( 축약 금지 )
  7. 모든 엔티티를 작게 유지한다.
  8. 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
  9. Getter / Setter / Property를 쓰지 않는다.
일급 컬렉션

 

 

일급 컬렉션이란?

Collection을 Wrapping하면서 그 외의 다른 멤버 변수가 없는 상태를 일급 컬렉션이라고 합니다.

일급컬렉션의 장점은 아래와 같습니다.

  1. 비즈니스의 종속적인 자료구조
  2. 불변성 보장
  3. 상태와 행위를 한 곳에서 관리
  4. 이름이 있는 컬렉션

 

@Getter
public class Student {

    private Name name;
    private Age age;

    public Student(Name name, Age age) {
        this.name = name;
        this.age = age;
    }
    
}

 

이전 원시값 포장에 대해서 설명할때 사용되었던 Student 객체입니다.

 

이번에는 학생명단을 만들어보겠습니다.

  • 학생명단은 최대 10명
  • 이름은 중복되지 않아야함

예시를 위해서 두가지의 검증로직이 필요하다고 가정하겠습니다. 그리고 이 검증로직을 Service 계층에서 진행했습니다.

 

public class StudentService {

    public void addStudent(List<Student> students, Student student) {
        validateStudentsSize(students);
        validateStudentName(students, student);

        students.add(student);
    }

    private void validateStudentName(List<Student> students, Student student) {
        String name = student.getName().getName();

        for (Student studentFor : students) {
            String nameFor = studentFor.getName().getName();

            if (name.equals(nameFor)) {
                throw new IllegalStudentsException("중복된 이름이 존재합니다.");
            }
        }
    }

    private void validateStudentsSize(List<Student> students) {
        if (students.size() > 10) {
            throw new IllegalStudentsException("최대 학생 수는 10명입니다.");
        }
    }
}

 

비즈니스로직을 모두 처리했지만 약간의 문제가 있습니다.

  1. List 컬렉션에 의존적입니다.
  2. List<Student>와 StudentService간에 결합도가 높습니다.
  3. 다른 사람이 보았을 때 List<Student>의 일을 파악하기 어렵습니다.
  4. 컬력션이 변경된다면 OCP원칙에 위배됩니다.

이러한 직접적인 의존성은 프로그램이 변화하거나 성장할 때 유연성을 제한하며, List<Student>의 변경은 StudentService 클래스의 범위를 넓혀 유지보수를 어렵게 만듭니다. 이런 구조는 코드 변경에 취약하며, 새로운 데이터 구조나 변경 사항이 발생할 때마다 Service 로직을 수정해야 하는 번거로움이 발생할 수 있습니다. 또한 이 문제는 개방-폐쇄원칙(OCP 원칙)을 위배하게 됩니다.

 

이제 이 문제들을 해결해 보겠습니다.

 

 

 

1. 비즈니스의 종속적인 자료구조

public class StudentList {

    private final List<Student> students;

    public StudentList() {
        this.students = new ArrayList<>();
    }

    public void add(Student student) {
        validateStudentsSize();
        validateStudentName(student);

        students.add(student);
    }
    
    public List<Student> getStudents() {
        return Collections.unmodifiableList(students);
    }

    private void validateStudentName(Student student) {
        String name = student.getName().getName();

        for (Student studentFor : students) {
            String nameFor = studentFor.getName().getName();

            if (name.equals(nameFor)) {
                throw new IllegalStudentsException("중복된 이름이 존재합니다.");
            }
        }
    }

    private void validateStudentsSize() {
        if (students.size() > 10) {
            throw new IllegalStudentsException("최대 학생 수는 10명입니다.");
        }
    }
}

 

StudentList 클래스를 도입함으로써 List<Student> 객체를 캡슐화 했고, Service에서 관리하던 비즈니스로직을 StudentList 클래스로 종속시켰습니다이 변경으로 인해 StudentService(외부에서)는 더 이상 직접적으로 List<Student>를 다루지 않고, StudentList의 메소드를 통해 학생 목록을 조작할 수 있게 되었고, 

 

따라서 컬렉션 변경에 대한 OCP원칙을 지킬 수 있게 되었고, 학생명단과 StudentService간에 결합도가 낮아졌습니다.

 

 

 

2. 불변성 보장

일급 컬렉션은 컬렉션의 불변을 보장하는데, 단순히 final 을 사용하는 것이 아니라 캡슐화를 통해 이뤄집니다.

* final은 재할당만 금지하는 것이므로 add, remove 등이 가능합니다.

 

List<Student> 객체는 StudentList 클래스 내부에 있기때문에 Student 하위에 setter가 존재하지 않는 한 값의 변경이 일어날 수 없습니다. 불변성에 대한 자세한 내용은 차후 다룰 예정입니다.

 

public List<Student> getStudents() {
    return Collections.unmodifiableList(students);
}

 

// getStudents : Collections.unmodifiableList(students);
List<Student> students = studentList.getStudents();
Student newStudent = new Student(new Name("이OO"), new Age(21));
students.add(newStudent); // UnsupportedOperationException 런타임 예외발생!

 

 

3. 상태와 행위를 한 곳에서 관리

일급컬렉션은 값과 로직이 한 객체안에 있기때문에 응집도가 높아집니다. 따라서 변경이 용이하고 유지보수가 수월해집니다.

 

만약 비즈니스 로직이 추가된다면?

Service에서 로직을 추가할 경우 결합도만 높아지게 되고 수정사항이 증가하게 될겁니다.

하지만 일급컬렉션을 사용한다면 클래스 내부에서 수정 및 추가만 하면 됩니다.

 

public class StudentList {

    private final List<Student> students;

    public StudentList() {
        this.students = new ArrayList<>();
    }
	
    // 만약 나이의 평균을 구하는 로직이 필요하다면? 
    public double averageAge() {
        return students.stream()
            .mapToInt(student -> student.getAge().getAge())
            .average()
            .orElse(0);
    }
	
    // 나머지 메서드 ...

}

 

 

4. 이름이 있는 컬렉션

List<Student> 만으로 그 의미를 추론하기는 어렵습니다. 하지만 StudentList 또는 그 상황에 맞는 이름을 붙혀준다면 그 의미는 명확해지고, 다른 사람이 코드를 분석할 때 객체 이름으로 어떤일을 할지 파악하기 쉬워진다는 겁니다.

 

public static void main(String[] args) {

    // 같은 List<Payment> 이기 때문에 ,
    // List<Payment>를 매개변수로 받는 메서드에는 모두 들어갈 수 있습니다.
    List<Payment> kakaoPay = createPayments();
    List<Payment> naverPay = createPayments();

    // 객체가 다르다면 사용용도가 명확해집니다.
    KakaoPay kakaoPay = createKakaoPay();
    NaverPay naverPay = createNaverPay();

}

 

 

+ Recent posts