티스토리 뷰

디자인 패턴

20. 상태(State) 패턴

jin-park 2022. 2. 11. 11:27

1.  상태 패턴

  • 객체 내부 상태 변경에 따라 객체의 행동이 달라지는 패턴.
  • 상태에 특화된 행동들을 분리해 낼수 있으며, 새로운 행동을 추가하더라도 다른 행동에 영향을 주지 않는다.
    • ex) 티비가 켜있을때와 꺼져있을때의 리모콘 전원 버튼의 동작 - 티비의 상태에따라 리모톤의 동작이 변경됨.

다이어그램

2.  상태 패턴 적용

2.1.  적용 전 코드

  • OnlineCourse 내부의 addReview, addStudent 메서드의 경우 온라인 강의의 상태에 따라 동작이 변경된다.
  • 현재는 모든 상태의 동작이 하나의 메서드 안에 구현되어있어 파악이 힘들다.
public class Student {
    private final String name;
 
    public Student(String name) {
        this.name = name;
    }
 
    public void addPrivateCourse(OnlineCourse onlineCourse) {
    }
}
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("학생을 해당 수업에 추가할 수 없습니다.");
        }
    }
 
    public void changeState(State newState) {
        this.state = newState;
    }
 
    /**
     * 소스를 인위적으로 복잡하게 하기위해 만들어짐
     */
    private boolean availableTo(Student student) {
        return true;
    }
 
    public State getState() { return state; }
    public List<String> getReviews() { return reviews; }
    public List<Student> getStudents() { return students; }
}
public class StatePatternClient {
 
    public static void main(String[] args) {
        Student student = new Student("jin.bak");
        OnlineCourse onlineCourse = new OnlineCourse();
 
        student.addPrivateCourse(onlineCourse);
 
        onlineCourse.addStudent(student);
        onlineCourse.changeState(State.PRIVATE);
 
        onlineCourse.addStudent(student);
        onlineCourse.addReview("Hello", student);
 
        System.out.println(onlineCourse.getState());
        System.out.println(onlineCourse.getStudents());
        System.out.println(onlineCourse.getReviews());
    }
}

2.2.  상태 패턴 적용 후 코드

  • 각 상태에 따른 동작을 각 구현 클래스(Draft, Published, Private)로 분리하여 사용한다.
public interface State {
    void addReview(String review, Student student);
    void addStudent(Student student);
}
public class Draft implements State {
 
    private final OnlineCourse onlineCourse;
 
    public Draft(OnlineCourse onlineCourse) {
        this.onlineCourse = onlineCourse;
    }
 
    @Override
    public void addReview(String review, Student student) {
        throw new UnsupportedOperationException("드래프트 상태에서는 리뷰를 남길 수 없습니다.");
    }
 
    @Override
    public void addStudent(Student student) {
        this.onlineCourse.getStudents().add(student);
        if (this.onlineCourse.getStudents().size() > 1) {
             this.onlineCourse.changeState(new Private(this.onlineCourse));
        }
    }
}
public class Published implements State {
     
    private final OnlineCourse onlineCourse;
 
    public Published(OnlineCourse onlineCourse) {
        this.onlineCourse = onlineCourse;
    }
 
    @Override
    public void addReview(String review, Student student) {
        this.onlineCourse.getReviews().add(review);
    }
 
    @Override
    public void addStudent(Student student) {
        this.onlineCourse.getStudents().add(student);
    }
}
public class Private implements State {
 
    private final 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 AfterStateClient {
 
    public static void main(String[] args) {
        Student student = new Student("jin-park");
        Student otherStudent = new Student("other user");
 
        OnlineCourse onlineCourse = new OnlineCourse();
        otherStudent.addPrivate(onlineCourse);
 
        onlineCourse.addStudent(student);
        onlineCourse.changeState(new Private(onlineCourse));
        onlineCourse.addReview("Hello", student);
        onlineCourse.addStudent(otherStudent);
 
        System.out.println(onlineCourse);
    }
}

3.  장단점

3.1.  장점

  • 상태가 한곳에 몰려있는 코드에비해 상태별로 코드를 파악 할 수있어 유지보수에 용이하다.
  • 단위 테스트를 손쉽게 할수있다.
  • 기존 코드를 수정하지 않고 상태에 따른 동작을 추가할 수 있다.

3.2.  단점

  • 코드 구조가 복잡해진다.
  • 상태 구조가 복잡하지 않은 상태에서 패턴을 적용한다면 복잡도 상승만 초래할 수 있다. (주관적으로 판단해야함.)
 

위글은 인프런의 코딩으로 학습하는 GoF의 디자인 패턴강의를 정리하였습니다.

백기선님의 수락으로 정리하였으며 더 자세한 내용은 강의를 수강하시기 바랍니다.

https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4/dashboard

 

코딩으로 학습하는 GoF의 디자인 패턴 - 인프런 | 강의

디자인 패턴을 알고 있다면 스프링 뿐 아니라 여러 다양한 기술 및 프로그래밍 언어도 보다 쉽게 학습할 수 있습니다. 또한, 보다 유연하고 재사용성이 뛰어난 객체 지향 소프트웨어를 개발할

www.inflearn.com

 

'디자인 패턴' 카테고리의 다른 글

22. 템플릿 메소드 패턴  (0) 2022.02.11
21. 전략 패턴  (0) 2022.02.11
19. 옵저버 패턴  (0) 2022.02.11
18. 메멘토 패턴  (0) 2022.02.10
17. 중재자 패턴  (0) 2022.02.10
댓글