티스토리 뷰

1. 스테이트 패턴이란?

스테이트 패턴을 이용하면 객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있다.

마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있다.

 

구성요소

Context

- 클라이언트가 필요한 인터페이스르 정의한다.

- 현재 상태를 나타내는 ConcreteState 서브클래스를 유지한다.

 

State

- 특정 상태에 해당되는 기능들이 정의된 인터페이스를 선언하고 있다.

 

ConcreteState

- 각 서브클래스는 해당되는 상태의 기능들을 구현한다.

 

2. 동기

 

장점

- 상태에 특화된 행동들을 분리하고, 새로운 행동을 추가하더라도 다른 행동에 영향을 주지 않는다.

- 코드 복잡도를 줄일 수 있다.

- 상태의 전환이 명시적으로 변한다.

 

단점

- 구조의 복잡도가 증가한다.

 

3. 구현

구현 시 고려사항

1) 누가 상태 변경을 담당하는가?

- 상태 전환을 중앙화하는 방법이 있고 탈중앙화하는 방법이 있다.

- 만약 상태가 바뀌는 방식이 이미 정해져 있다면, Context 내에서 상태 변경 코드를 모두 구현할 수 있다.

- 그렇지 않고 각 State마다 다음 State에 대한 전환을 명시할 수 있지만, 이는 각 상태끼리 커플링 된다는 단점이 있다.

 

2) 테이블로 상태 변경 담당하기

- 장점: 한 테이블에서 모든 상태에 대한 명시가 되어있기 때문에 관리가 용의하고, 코드 변경을 하지 않아도 된다.

- 단점: lookup에 소요되는 시간이 있고, 상태 변경이 어떻게 되는건지 테이블을 통해 파악하기 어렵다.

 

3) 상태 객체의 생성과 파괴

- 필요할 때만 생성하고 파괴할 것인가

- 미리 생성해두고 삭제하는 과정은 생략할 서인가

 

4) 동적 상속 사용

구현예시

public class Client {

    public static void main(String[] args) {
        Student student = new Student("whiteship");
        OnlineCourse onlineCourse = new OnlineCourse();

        Student keesun = new Student("keesun");
        keesun.addPrivateCourse(onlineCourse);

        onlineCourse.addStudent(student);
        onlineCourse.changeState(OnlineCourse.State.PRIVATE);

        onlineCourse.addStudent(keesun);

        onlineCourse.addReview("hello", student);

        System.out.println(onlineCourse.getState());
        System.out.println(onlineCourse.getStudents());
        System.out.println(onlineCourse.getReviews());
    }
}

public class OnlineCourse {

    public enum State {
        DRAFT, PUBLISHED, PRIVATE
    }

    private State state = State.DRAFT;

    private List<String> reviews = new ArrayList<>();

    private List<Student> students = new ArrayList<>();

    public void addReview(String review, Student student) {
        if (this.state == State.PUBLISHED) {
            this.reviews.add(review);
        } else if (this.state == State.PRIVATE && this.students.contains(student)) {
            this.reviews.add(review);
        } else {
            throw new UnsupportedOperationException("리뷰를 작성할 수 없습니다.");
        }
    }

    public void addStudent(Student student) {
        if (this.state == State.DRAFT || this.state == State.PUBLISHED) {
            this.students.add(student);
        } else if (this.state == State.PRIVATE && availableTo(student)) {
            this.students.add(student);
        } else {
            throw new UnsupportedOperationException("학생을 해당 수업에 추가할 수 없습니다.");
        }

        if (this.students.size() > 1) {
            this.state = State.PRIVATE;
        }
    }

    public void changeState(State newState) {
        this.state = newState;
    }

    public State getState() {
        return state;
    }

    public List<String> getReviews() {
        return reviews;
    }

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

    private boolean availableTo(Student student) {
        return student.isEnabledForPrivateClass(this);
    }
}
public class Student {

    private String name;

    public Student(String name) {
        this.name = name;
    }

    private List<OnlineCourse> privateCourses = new ArrayList<>();

    public boolean isEnabledForPrivateClass(OnlineCourse onlineCourse) {
        return privateCourses.contains(onlineCourse);
    }

    public void addPrivateCourse(OnlineCourse onlineCourse) {
        this.privateCourses.add(onlineCourse);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}

패턴 적용 후

public interface State {

    void addReview(String review, Student student);

    void addStudent(Student student);
}

public class Private implements State {

    private OnlineCourse onlineCourse;

    public Private(OnlineCourse onlineCourse) {
        this.onlineCourse = onlineCourse;
    }

    @Override
    public void addReview(String review, Student student) {
        if (this.onlineCourse.getStudents().contains(student)) {
            this.onlineCourse.getReviews().add(review);
        } else {
            throw new UnsupportedOperationException("프라이빗 코스를 수강하는 학생만 리뷰를 남길 수 있습니다.");
        }
    }

    @Override
    public void addStudent(Student student) {
        if (student.isAvailable(this.onlineCourse)) {
            this.onlineCourse.getStudents().add(student);
        } else {
            throw new UnsupportedOperationException("프라이빛 코스를 수강할 수 없습니다.");
        }
    }
}


public class OnlineCourse {

    private State state = new Draft(this);

    private List<Student> students = new ArrayList<>();

    private List<String> reviews = new ArrayList<>();

    public void addStudent(Student student) {
        this.state.addStudent(student);
    }

    public void addReview(String review, Student student) {
        this.state.addReview(review, student);
    }

    public State getState() {
        return state;
    }

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

    public List<String> getReviews() {
        return reviews;
    }

    public void changeState(State state) {
        this.state = state;
    }
}

public class Student {

    private String name;

    public Student(String name) {
        this.name = name;
    }

    private Set<OnlineCourse> onlineCourses = new HashSet<>();

    public boolean isAvailable(OnlineCourse onlineCourse) {
        return onlineCourses.contains(onlineCourse);
    }

    public void addPrivate(OnlineCourse onlineCourse) {
        this.onlineCourses.add(onlineCourse);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}