From 0a0e9ef8677c119109c6284fabedb64bc05c3dfe Mon Sep 17 00:00:00 2001 From: EuiSung <52964858+gowoonsori@users.noreply.github.com> Date: Mon, 14 Mar 2022 18:47:50 +0900 Subject: [PATCH 1/3] =?UTF-8?q?Add=20:=20=EC=83=81=ED=83=9C=ED=8C=A8?= =?UTF-8?q?=ED=84=B4=20=EC=BD=94=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../summary/code.md" | 352 ++++++++++++++++++ 1 file changed, 352 insertions(+) create mode 100644 "\355\226\211\353\217\231/11\354\243\274\354\260\250-\354\203\201\355\203\234/summary/code.md" diff --git "a/\355\226\211\353\217\231/11\354\243\274\354\260\250-\354\203\201\355\203\234/summary/code.md" "b/\355\226\211\353\217\231/11\354\243\274\354\260\250-\354\203\201\355\203\234/summary/code.md" new file mode 100644 index 0000000..c8f19cf --- /dev/null +++ "b/\355\226\211\353\217\231/11\354\243\274\354\260\250-\354\203\201\355\203\234/summary/code.md" @@ -0,0 +1,352 @@ +예제 코드 요구사항 + +- 유저는 채용 공고에 지원할 수 있다. +- 채용 공고가 진행중일 때 입사 지원을 할 수 있다. +- 공고에 2명이 지원하면 해당 공고에는 더이상 다른 유저는 지원할 수 없다. +- 채용 공고가 대기중이거나 끝난 상태일 때 유저는 해당 공고에 지원을 취소할 수 없다. + +위 요구사항을 만족하는 코드를 작성하면 다음과 같다. + +### 상태패턴 적용 전 +```java +@Getter +@RequiredArgsConstructor +public class User { + private final String name; +} + +@Getter +public class Recruit { + public enum State { + WAIT, PROGRESS, END, FULL + } + + private static final int MAX_NUM = 2; + private final Set users = new HashSet<>(); + private State state; + private final String name; + + public Recruit(String name) { + this.name = name; + System.out.println("========공고 게시(모집전)====="); + this.state = State.WAIT; + } + + public void applyRecruit(User user) { + System.out.print(user.getName() + " 님의 공고 지원 결과 : "); + if (state == State.WAIT || state == State.END || state == State.FULL) { + System.out.println("해당 채용공고에 지원할 수 없습니다."); + return; + } + + this.users.add(user); + if (users.size() == MAX_NUM) { + this.state = State.FULL; + } + System.out.println("지원 성공하였습니다."); + } + + public void cancelRecruit(User user) { + System.out.print(user.getName() + " 님의 지원 취소 결과 : "); + if (state == State.WAIT) { + System.out.println("아직 공고 모집이 시작되지 않았습니다."); + return; + } else if (state == State.END) { + System.out.println("이미 종료된 채용 공고입니다."); + return; + }else if(!this.users.remove(user)){ + System.out.println("지원한 공고가 아닙니다."); + return; + } + + if (state == State.FULL) { + state = State.PROGRESS; + } + System.out.println("지원 취소 되었습니다."); + } + + public void closeRecruit() { + System.out.println("========공고 모집 종료====="); + this.state = State.END; + } + + public void startRecruit() { + System.out.println("========공고 모집 시작====="); + this.state = State.PROGRESS; + } + + public void printUsers() { + System.out.println("====입사 지원 유저 목록===="); + System.out.println(users.stream().map(User::getName).toList().toString()); + } +} + +public class Client { + public static void main(String[] args) { + User user1 = new User("고길동"); + User user2 = new User("고둘리"); + User user3 = new User("또치"); + + //공고 게시(모집전) + Recruit recruit = new Recruit("사람인 공고"); + recruit.applyRecruit(user1); + System.out.println(); + + //공고 모집 시작 + recruit.startRecruit(); + recruit.applyRecruit(user1); + recruit.applyRecruit(user2); + recruit.applyRecruit(user3); + System.out.println(); + + recruit.printUsers(); + System.out.println(); + + //공고 한명 취소후 다른사람이 지원 + recruit.cancelRecruit(user1); + recruit.applyRecruit(user3); + + System.out.println(); + recruit.printUsers(); + } +} + +결과: +========공고 게시(모집전)===== +고길동 님의 공고 지원 결과 : 해당 채용공고에 지원할 수 없습니다. + +========공고 모집 시작===== +고길동 님의 공고 지원 결과 : 지원 성공하였습니다. +고둘리 님의 공고 지원 결과 : 지원 성공하였습니다. +또치 님의 공고 지원 결과 : 해당 채용공고에 지원할 수 없습니다. + +====입사 지원 유저 목록==== +[고길동, 고둘리] + +고길동 님의 지원 취소 결과 : 지원 취소 되었습니다. +또치 님의 공고 지원 결과 : 지원 성공하였습니다. + +====입사 지원 유저 목록==== +[고둘리, 또치] +``` + +채용공고에 상태를 Enum으로 정의해놓고 해당 상태에 따라 입사지원, 취소가 분기처리 되고 있는 것을 볼 수 있다. + +하지만 분기 처리 코드 인해 코드가 한눈에 들어오지 않고 만약 상태값들이 더 늘어나게 된다면 그만큼 분기처리도 더욱 많아져 더 가독성이 악화 될 것이다. + +이러한 문제를 해결하기 위해 상태 패턴을 사용할 수 있다. + +아래는 상태 패턴을 적용한 코드이다. + +### 상태패턴 적용 후 +```java +@Getter +@RequiredArgsConstructor +public class User { + private final String name; +} + +//Context +@Getter +public class Recruit{ + private final String name; //공고 이름 + private final Set users = new HashSet<>(); //공고 지원자들 + private RecruitState recruitState; //공고 상태 + + public Recruit(String name) { + this.name = name; + System.out.println("========" + name +" 공고 게시(모집전)====="); + this.recruitState = new Wait(this); + } + + //공고 지원 + public void applyRecruit(User user) { + System.out.print(user.getName() + " 님의 지원 결과 : "); + recruitState.addUser(user); //공고 지원에 대한 세부 로직은 상태에게 위임 + } + + //공고 취소 + public void cancelRecruit(User user) { + System.out.print(user.getName() + " 님의 지원 취소 결과 : "); + recruitState.removeUser(user); //공고 취소에 대한 세부 로직은 상태에게 위임 + } + + public void changeState(RecruitState recruitState) { + this.recruitState = recruitState; //상태 변경 + } + + public void printUsers() { + System.out.println("====입사 지원 유저 목록===="); + System.out.println(users.stream().map(User::getName).toList().toString()); + } +} + + +//채용 공고에 지원할 유저 클래스 +@Getter +public class User { + private String name; + + public User(String name) { + this.name = name; + } +} + +//State Interface +public interface RecruitState { + void setRecruit(Recruit recruit); + void addUser(User user); //공고 지원 + void removeUser(User user); //공고 취소 +} + + +//Concrete State +public class Wait implements RecruitState{ + private Recruit recruit; + + public Wait(Recruit recruit) {this.recruit = recruit;} + + @Override + public void setRecruit(Recruit recruit) { + this.recruit = recruit; + } + + @Override + public void addUser(User user) { + System.out.println("아직 공고 모집이 시작되지 않았습니다."); + } + + @Override + public void removeUser(User user) { + System.out.println("아직 공고 모집이 시작되지 않았습니다."); + } +} + +//Concrete State +public class Progress implements RecruitState{ + private Recruit recruit; + private static final int MAX_NUM = 2; + + public Progress(Recruit recruit) {this.recruit = recruit;} + + @Override + public void setRecruit(Recruit recruit) { + this.recruit = recruit; + } + + @Override + public void addUser(User user) { + recruit.getUsers().add(user); + System.out.println(user.getName() + "의 입사지원이 완료됐습니다."); + if(recruit.getUsers().size() == MAX_NUM) { + this.recruit.changeState(new Full(recruit)); //가득 찼다면 마감상태로 변경 + } + } + + @Override + public void removeUser(User user) { + recruit.getUsers().remove(user); + System.out.println(user.getName() + "의 입사지원이 취소됐습니다."); + } +} + + +//Concrete State +public class End implements RecruitState { + private Recruit recruit; + + public End(Recruit recruit) {this.recruit = recruit;} + + @Override + public void setRecruit(Recruit recruit) { + this.recruit = recruit; + } + @Override + public void addUser(User user) { + System.out.println("이미 종료된 채용 공고입니다."); + } + @Override + public void removeUser(User user) { + System.out.println("이미 종료된 채용 공고입니다."); + } +} + +//Concrete State +public class Full implements RecruitState{ + Recruit recruit; + + public Full(Recruit recruit) {this.recruit = recruit;} + + @Override + public void setRecruit(Recruit recruit) { + this.recruit = recruit; + } + + @Override + public void addUser(User user) { + System.out.println("채용 공고 지원수가 꽉 찼습니다."); + } + + @Override + public void removeUser(User user) { + recruit.getUsers().remove(user); + System.out.println(user.getName() + "의 입사지원이 취소됐습니다."); + this.recruit.changeState(new Progress(recruit)); //모집중 상태로 변경 + } +} + +public class Client { + public static void main(String[] args) { + User user1 = new User("고길동"); + User user2 = new User("고둘리"); + User user3 = new User("또치"); + + //공고 게시(모집전) + Recruit recruit = new Recruit("사람인"); + recruit.applyRecruit(user1); + System.out.println(); + + //공고 모집 시작 + System.out.println("==== 공고 모집 시작 ===="); + recruit.changeState(new Progress(recruit)); //모집중 상태로 변경 + recruit.applyRecruit(user1); + recruit.applyRecruit(user2); + recruit.applyRecruit(user3); + System.out.println(); + + recruit.printUsers(); + System.out.println(); + + //공고 한명 취소후 다른사람이 지원 + recruit.cancelRecruit(user1); + recruit.applyRecruit(user3); + + System.out.println(); + recruit.printUsers(); + } +} + +결과 : +========사람인 공고 게시(모집전)===== +고길동 님의 지원 결과 : 아직 공고 모집이 시작되지 않았습니다. + +==== 공고 모집 시작 ==== +고길동 님의 지원 결과 : 고길동의 입사지원이 완료됐습니다. +고둘리 님의 지원 결과 : 고둘리의 입사지원이 완료됐습니다. +또치 님의 지원 결과 : 채용 공고 지원수가 꽉 찼습니다. + +====입사 지원 유저 목록==== +[고둘리, 고길동] + +고길동 님의 지원 취소 결과 : 고길동의 입사지원이 취소됐습니다. +또치 님의 지원 결과 : 또치의 입사지원이 완료됐습니다. + +====입사 지원 유저 목록==== +[또치, 고둘리] +``` + +![Untitled](https://user-images.githubusercontent.com/32676275/153326418-6fe4c82d-f8c3-430f-a2ec-ae22fedaed48.png) + +--- + +UML 다이어그램대로 상태 패턴을 적용하여 Recruit이라는 객체 내에 존재하던 비즈니스 로직을 각 State에게 위임하여 확장에 유연하도록 개선해보았다. \ No newline at end of file From 6dd884e215376b0173a1d915870c50a6fe672901 Mon Sep 17 00:00:00 2001 From: EuiSung <52964858+gowoonsori@users.noreply.github.com> Date: Mon, 14 Mar 2022 18:55:35 +0900 Subject: [PATCH 2/3] Update code.md --- .../summary/code.md" | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git "a/\355\226\211\353\217\231/11\354\243\274\354\260\250-\354\203\201\355\203\234/summary/code.md" "b/\355\226\211\353\217\231/11\354\243\274\354\260\250-\354\203\201\355\203\234/summary/code.md" index c8f19cf..26c992d 100644 --- "a/\355\226\211\353\217\231/11\354\243\274\354\260\250-\354\203\201\355\203\234/summary/code.md" +++ "b/\355\226\211\353\217\231/11\354\243\274\354\260\250-\354\203\201\355\203\234/summary/code.md" @@ -1,13 +1,13 @@ -예제 코드 요구사항 - +## 코드 +### 요구사항 - 유저는 채용 공고에 지원할 수 있다. - 채용 공고가 진행중일 때 입사 지원을 할 수 있다. - 공고에 2명이 지원하면 해당 공고에는 더이상 다른 유저는 지원할 수 없다. - 채용 공고가 대기중이거나 끝난 상태일 때 유저는 해당 공고에 지원을 취소할 수 없다. -위 요구사항을 만족하는 코드를 작성하면 다음과 같다. ### 상태패턴 적용 전 +위 요구사항을 만족하는 코드를 작성하면 다음과 같다. ```java @Getter @RequiredArgsConstructor From 4d9a629c07fdfbdd611eb3a683045ca27937d9fb Mon Sep 17 00:00:00 2001 From: dev-splin Date: Wed, 13 Apr 2022 23:51:09 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=EC=83=81=ED=83=9C=20=ED=8C=A8=ED=84=B4=20-?= =?UTF-8?q?=20=EC=A0=95=EB=A6=AC=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../summary/code.md" | 66 ++++++++++++++++--- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git "a/\355\226\211\353\217\231/11\354\243\274\354\260\250-\354\203\201\355\203\234/summary/code.md" "b/\355\226\211\353\217\231/11\354\243\274\354\260\250-\354\203\201\355\203\234/summary/code.md" index 26c992d..20766b6 100644 --- "a/\355\226\211\353\217\231/11\354\243\274\354\260\250-\354\203\201\355\203\234/summary/code.md" +++ "b/\355\226\211\353\217\231/11\354\243\274\354\260\250-\354\203\201\355\203\234/summary/code.md" @@ -1,12 +1,33 @@ -## 코드 -### 요구사항 + + +## 1. 상태 패턴(State Pattern)이란? + +객체 내부 상태 변경에 따라 객체의 행동이 달라지는 패턴 + +상태에 특화된 행동들을 분리해낼 수 있으며 새로운 행동을 추가하더라도 다른 행동에 영향을 주지 않는다. + +[![스크린샷 2022-02-09 오후 8.35.34.png](https://github.com/Nelmm/DesignPattern/raw/main/%ED%96%89%EB%8F%99/11%EC%A3%BC%EC%B0%A8-%EC%83%81%ED%83%9C/img/chang1.png)](https://github.com/Nelmm/DesignPattern/blob/main/행동/11주차-상태/img/chang1.png) + +- `Context` : 상태가 변경되는 객체, 상태를 변경하는 메서드를 가지고 있음 +- `State` : Context가 변경될 수 있는 여러 상태들에 대한 공통된 인터페이스 +- `ConcreteState` : State를 구현하는 클래스, 각 상태에 따라 실질적으로 달라지는 행동들을 구현함 + + + + + +## 2. 코드로 알아보는 상태 패턴 + +### 2-1. 요구사항 - 유저는 채용 공고에 지원할 수 있다. - 채용 공고가 진행중일 때 입사 지원을 할 수 있다. - 공고에 2명이 지원하면 해당 공고에는 더이상 다른 유저는 지원할 수 없다. - 채용 공고가 대기중이거나 끝난 상태일 때 유저는 해당 공고에 지원을 취소할 수 없다. -### 상태패턴 적용 전 + +### 2-2. 상태패턴 적용 전 + 위 요구사항을 만족하는 코드를 작성하면 다음과 같다. ```java @Getter @@ -130,15 +151,16 @@ public class Client { [고둘리, 또치] ``` -채용공고에 상태를 Enum으로 정의해놓고 해당 상태에 따라 입사지원, 취소가 분기처리 되고 있는 것을 볼 수 있다. - -하지만 분기 처리 코드 인해 코드가 한눈에 들어오지 않고 만약 상태값들이 더 늘어나게 된다면 그만큼 분기처리도 더욱 많아져 더 가독성이 악화 될 것이다. +채용공고에 상태를 Enum으로 정의해놓고 해당 상태에 따라 입사지원, 취소가 분기처리 되고 있는 것을 볼 수 있음 :arrow_right: 분기 처리 코드 인해 코드가 한눈에 들어오지 않고 만약 상태값들이 더 늘어나게 된다면 그만큼 분기처리도 더욱 많아져 가독성이 악화 됨 이러한 문제를 해결하기 위해 상태 패턴을 사용할 수 있다. + + +### 2-3. 상태패턴 적용 후 + 아래는 상태 패턴을 적용한 코드이다. -### 상태패턴 적용 후 ```java @Getter @RequiredArgsConstructor @@ -345,8 +367,34 @@ public class Client { [또치, 고둘리] ``` + + +### 2-4. UML + +UML 다이어그램대로 상태 패턴을 적용하여 **Recruit이라는 객체 내에 존재하던 비즈니스 로직을 각 State에게 위임하여 확장에 유연하도록 개선**할 수 있었다. + ![Untitled](https://user-images.githubusercontent.com/32676275/153326418-6fe4c82d-f8c3-430f-a2ec-ae22fedaed48.png) ---- -UML 다이어그램대로 상태 패턴을 적용하여 Recruit이라는 객체 내에 존재하던 비즈니스 로직을 각 State에게 위임하여 확장에 유연하도록 개선해보았다. \ No newline at end of file + + + +## 3. 장점과 단점 + +### 3-1. 장점 + +- 상태에 따른 동작을 개별 클래스로 옮겨서 관리할 수 있음 +- 기존의 특정 상태에 따른 동작을 변경하지 않고 새로운 상태에 다른 동작을 추가할 수 있음 +- 코드 복잡도가 줄어듦 :arrow_right: 가독성 증가 + +### 3-2. 단점 + +- 복잡도가 증가한다. (on, off 같이 단 두가지 정도의 상태만 있을 때, 상태 패턴을 적용하면 상태 개수에 비해 복잡해질 수 있음) + + + + + +## 4. 마치며 + +웹 페이지뿐만 아니라 프로그래밍 전반적으로 상태에 따라 객체가 달라지는 형태는 자주 볼 수 있다. 때문에 상태 패턴은 다방면으로 사용할 수 있어 유용한 패턴이라고 생각된다. \ No newline at end of file