From 883d1b38f439a6b7eb2c6fb826d19deaaecf6449 Mon Sep 17 00:00:00 2001 From: EuiSung <52964858+gowoonsori@users.noreply.github.com> Date: Mon, 14 Mar 2022 23:04:13 +0900 Subject: [PATCH 1/6] =?UTF-8?q?Add:=20=EC=BB=A4=EB=A7=A8=EB=93=9C=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=81=EC=9A=A9=EC=A0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../summary/code.md" | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 "\355\226\211\353\217\231/8\354\243\274\354\260\250-\354\273\244\353\247\250\353\223\234/summary/code.md" diff --git "a/\355\226\211\353\217\231/8\354\243\274\354\260\250-\354\273\244\353\247\250\353\223\234/summary/code.md" "b/\355\226\211\353\217\231/8\354\243\274\354\260\250-\354\273\244\353\247\250\353\223\234/summary/code.md" new file mode 100644 index 0000000..108e82b --- /dev/null +++ "b/\355\226\211\353\217\231/8\354\243\274\354\260\250-\354\273\244\353\247\250\353\223\234/summary/code.md" @@ -0,0 +1,208 @@ +## 코드 +### 요구사항 +사람인서비스에 이력서를 저장하고 조회하고 삭제하는 서비스를 개발하고자 할때 `사람인 양식`과 `Pdf`를 통한 이력서 생성 서비르를 제공하고 있으며 해당 양식으로 생성한 이력서마다 조회와 삭제에 대한 비즈니스 로직이 다르다고 가정을 해보자. + +간단하게 개발을 해보면 아래와 같이 개발할 수 있을 것이다. + +```java +//이력서 모델 +//편의를 위해 모든 데이터 String 형으로 대체 +public class Resume { + private String basicInfo; + private String careers; + private String educations; + private String activities; + private String certificates; + private String skills; + private String preferential; + private String portpolio; + private String selfIntroduction; + private String resumeType; + private String pdfFile; + private String documents; +} + +//사람인 폼 이력서 정보 DTO +public class SaraminFormDto { + private String basicInfo; + private String careers; + private String educations; + private String activities; + private String certificates; + private String skills; + private String preferential; + private String portpolio; + private String selfIntroduction; + + public Resume toEntity(){ + return new Resume(basicInfo,careers,educations,activities,certificates,skills,preferential,portpolio,selfIntroduction, + "saramin",null,null); + } +} + +//PDF 이력서 정보 DTO +public class PdfFormDto { + private String basicInfo; + private String careers; + private String educations; + private String pdfFile; + private String documents; + + public Resume toEntity(){ + return new Resume(basicInfo,careers,educations,null,null,null,null,null, + null, "pdf",pdfFile,documents); + } +} + +//이력서 서비스 +@RequiredArgsConstructor +public class ResumeService { + private final ResumeRepository resumeRepository; + + //사람인 폼 이력서 저장 + public void saveResumeBy(SaraminFormDto resumeData) { + System.out.println("===사람인 이력서 작성==="); + resumeRepository.save(resumeData.toEntity()); + } + + //PDF 이력서 저장 + public void saveResumeBy(PdfFormDto resumeData) { + System.out.println("===PDF로 이력서 작성==="); + resumeRepository.save(resumeData.toEntity()); + } + + //이력서 정보 조회 + public void getResume(int resumeId) { + resumeRepository.getResumeById(resumeId); + } + + //이력서 삭제 + public void deleteResume(int resumeId) { + resumeRepository.deleteResumeById(resumeId); + + } +} + +//DB이용하는 Repository가 아닌 Stub객체 +//이력서 Repository +public class ResumeRepository { + public static final int SARAMIN_RESUME_ID = 1; //id가 1일 경우 사람인폼 이력서 + public static final int PDF_RESUME_ID = 2; //id가 2일 경우 pdf 이력서 + + public void save(Resume resume) { + System.out.println(resume.getResumeType() + " 타입의 이력서 저장"); + } + + public void getResumeById(int id) { + System.out.println("===이력서 조회==="); + if (id == SARAMIN_RESUME_ID) { + System.out.println("1. 이력서 정보 반환"); + }else if (id == PDF_RESUME_ID){ + System.out.println("1. pdf 이미지로 변환"); + System.out.println("2. 이력서 정보 반환"); + } + } + + public void deleteResumeById(int id) { + System.out.println("===이력서 삭제==="); + if (id == SARAMIN_RESUME_ID) { + System.out.println("1. 이력서 데이터 삭제"); + }else if (id == PDF_RESUME_ID){ + System.out.println("1. 이력서 데이터 삭제"); + System.out.println("2. pdf 파일 삭제"); + } + } +} + +//Controller와 같이 Service를 사용하는 객체 +public class Client { + public static void main(String[] args) { + ResumeService resumeService = new ResumeService(new ResumeRepository()); + + //사람인폼으로 이력서 저장 + resumeService.saveResumeBy(new SaraminFormDto()); + resumeService.getResume(SARAMIN_RESUME_ID); + resumeService.deleteResume(SARAMIN_RESUME_ID); + System.out.println(); + + //Pdf로 이력서 저장 + resumeService.saveResumeBy(new PdfFormDto()); + resumeService.getResume(PDF_RESUME_ID); + resumeService.deleteResume(PDF_RESUME_ID); + } +} + +결과 : +===사람인 이력서 작성=== +saramin 타입의 이력서 저장 +===이력서 조회=== +1. 이력서 정보 반환 +===이력서 삭제=== +1. 이력서 데이터 삭제 + +===PDF로 이력서 작성=== +pdf 타입의 이력서 저장 +===이력서 조회=== +1. pdf 이미지로 변환 +2. 이력서 정보 반환 +===이력서 삭제=== +1. 이력서 데이터 삭제 +2. pdf 파일 삭제 +``` + +주어진 요구사항을 필요한 부분만 나타내어 개발해보면 위와 같이 개발 할 수 있을 것이다. 여기서 만약에 `URL`을 통한 이력서 서비스를 추가로 제공해야 한다면 `Service`에 초점을 맞춰서 보면 아래와 같이 코드를 추가해야 할 것이다. + +```java +public class UrlFormDto { + private String basicInfo; + private String careers; + private String educations; + private String url; + private String documents; + + public Resume toEntity(){ + return new Resume(basicInfo,careers,educations,null,null,null,null,null, + null, "url",null,url,documents); + } +} + +@RequiredArgsConstructor +public class ResumeService { + private final ResumeRepository resumeRepository; + + public void saveResumeBy(SaraminFormDto resumeData) { + System.out.println("===사람인 이력서 저장==="); + resumeRepository.save(resumeData.toEntity()); + } + + public void saveResumeBy(PdfFormDto resumeData) { + System.out.println("===PDF로 이력서 저장==="); + resumeRepository.save(resumeData.toEntity()); + } + + + //저장 메서드 추가 + public void saveResumeBy(UrlFormDto resumeData) { + System.out.println("===URL로 이력서 저장==="); + resumeRepository.save(resumeData.toEntity()); + } + + public void getResume(int resumeId) { + Resume resume = resumeRepository.getResumeById(resumeId); + if (resume.getResumeType().equals("pdf")) { + System.out.println("pdf 파일 이미지로 변환"); + }else if(resume.getResumeType().equals("url")) { //이력서 조회 로직 추가 + System.out.println("url에서 html 다운"); + } + } + + public void deleteResume(int resumeId) { + Resume resume = resumeRepository.getResumeById(resumeId); + + resumeRepository.deleteResumeById(resumeId); + if (resume.getResumeType().equals("pdf")) { //만일 다른 로직이 필요하다면 로직 추가 + System.out.println("pdf 파일 삭제"); + } + } +} +``` \ No newline at end of file From 87c1d03df772d00f30089f082fe149f1013680ef Mon Sep 17 00:00:00 2001 From: EuiSung <52964858+gowoonsori@users.noreply.github.com> Date: Tue, 15 Mar 2022 11:33:19 +0900 Subject: [PATCH 2/6] =?UTF-8?q?Update:=20=EC=BB=A4=EB=A7=A8=EB=93=9C=20?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=BD=94=EB=93=9C(UML=EA=B3=BC=20?= =?UTF-8?q?=EC=A1=B0=EA=B8=88=20=EC=83=81=EC=9D=B4=ED=95=9C=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=20=EC=88=98=EC=A0=95)=20|=20=EC=A0=81=EC=9A=A9?= =?UTF-8?q?=ED=9B=84=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../summary/code.md" | 310 ++++++++++++------ 1 file changed, 204 insertions(+), 106 deletions(-) diff --git "a/\355\226\211\353\217\231/8\354\243\274\354\260\250-\354\273\244\353\247\250\353\223\234/summary/code.md" "b/\355\226\211\353\217\231/8\354\243\274\354\260\250-\354\273\244\353\247\250\353\223\234/summary/code.md" index 108e82b..2f98942 100644 --- "a/\355\226\211\353\217\231/8\354\243\274\354\260\250-\354\273\244\353\247\250\353\223\234/summary/code.md" +++ "b/\355\226\211\353\217\231/8\354\243\274\354\260\250-\354\273\244\353\247\250\353\223\234/summary/code.md" @@ -1,29 +1,27 @@ ## 코드 ### 요구사항 -사람인서비스에 이력서를 저장하고 조회하고 삭제하는 서비스를 개발하고자 할때 `사람인 양식`과 `Pdf`를 통한 이력서 생성 서비르를 제공하고 있으며 해당 양식으로 생성한 이력서마다 조회와 삭제에 대한 비즈니스 로직이 다르다고 가정을 해보자. - -간단하게 개발을 해보면 아래와 같이 개발할 수 있을 것이다. +사람인서비스에 이력서를 저장하고 조회하고 삭제하는 서비스를 개발하고자 할때 `사람인 양식`과 `Pdf`를 통한 이력서 생성 서비스를 제공하고 있으며 해당 양식으로 생성한 이력서마다 저장하는 과정에서 내부 파라미터를 셋팅하는 로직이 다르다고 가정을 해보자. ```java //이력서 모델 //편의를 위해 모든 데이터 String 형으로 대체 public class Resume { - private String basicInfo; - private String careers; - private String educations; - private String activities; - private String certificates; - private String skills; - private String preferential; - private String portpolio; - private String selfIntroduction; - private String resumeType; - private String pdfFile; - private String documents; + private String basicInfo; //기본 정보 + private String careers; //경력 사항 + private String educations; //교육 사항 + private String activities; //대외 활동 + private String certificates; //자격증 + private String skills; //보유 기술 + private String preferential; //우대 사항 + private String portpolio; //포트폴리오 + private String selfIntroduction;//자기소개서 + private String resumeType; //이력서 타입(saramin,pdf) + private String pdfFile; //pdf file + private String documents; //추가 문서,자료 } //사람인 폼 이력서 정보 DTO -public class SaraminFormDto { +public class SaraminFormResume { private String basicInfo; private String careers; private String educations; @@ -41,7 +39,7 @@ public class SaraminFormDto { } //PDF 이력서 정보 DTO -public class PdfFormDto { +public class FileFormResume { private String basicInfo; private String careers; private String educations; @@ -50,7 +48,7 @@ public class PdfFormDto { public Resume toEntity(){ return new Resume(basicInfo,careers,educations,null,null,null,null,null, - null, "pdf",pdfFile,documents); + null, "pdf",pdfFile,null,documents); } } @@ -59,101 +57,77 @@ public class PdfFormDto { public class ResumeService { private final ResumeRepository resumeRepository; - //사람인 폼 이력서 저장 - public void saveResumeBy(SaraminFormDto resumeData) { - System.out.println("===사람인 이력서 작성==="); - resumeRepository.save(resumeData.toEntity()); - } - - //PDF 이력서 저장 - public void saveResumeBy(PdfFormDto resumeData) { - System.out.println("===PDF로 이력서 작성==="); - resumeRepository.save(resumeData.toEntity()); + //이력서 저장 + public void saveResume(Resume resume) { + //공통 로직 + System.out.println("====이력서 저장===="); + System.out.print("기본정보, 경력사항 등 기본 값 validate와 셋팅"); + + //이력서 타입에 따른 개별 비즈니스 로직 수행 + if (resume.getResumeType().equals("saramin")) { + System.out.println("사람인 이력서 제외 item 조회"); + System.out.println("불러오기 가능한 이력서 리스트 조회"); + System.out.println("인적성 응시 시험 리스트 조회"); + }else if(resume.getResumeType().equals("file")) { + System.out.println("파일 정보 파라미터 할당"); + System.out.println("파일 이력서 제외 item 조회"); + System.out.println("파일 썸네일 추출"); + System.out.println("파일 서버에 업로드"); + } + resumeRepository.save(resume); } +} - //이력서 정보 조회 - public void getResume(int resumeId) { - resumeRepository.getResumeById(resumeId); - } - //이력서 삭제 - public void deleteResume(int resumeId) { - resumeRepository.deleteResumeById(resumeId); - - } -} //DB이용하는 Repository가 아닌 Stub객체 //이력서 Repository public class ResumeRepository { - public static final int SARAMIN_RESUME_ID = 1; //id가 1일 경우 사람인폼 이력서 - public static final int PDF_RESUME_ID = 2; //id가 2일 경우 pdf 이력서 + public static final int SARAMIN_RESUME_ID = 1; //사람인 폼 이력서 id + public static final int FILE_RESUME_ID = 2; //File 폼 이력서 id - public void save(Resume resume) { + //이력서 저장 + public void save(Resume resume) { System.out.println(resume.getResumeType() + " 타입의 이력서 저장"); } - - public void getResumeById(int id) { - System.out.println("===이력서 조회==="); - if (id == SARAMIN_RESUME_ID) { - System.out.println("1. 이력서 정보 반환"); - }else if (id == PDF_RESUME_ID){ - System.out.println("1. pdf 이미지로 변환"); - System.out.println("2. 이력서 정보 반환"); - } - } - - public void deleteResumeById(int id) { - System.out.println("===이력서 삭제==="); - if (id == SARAMIN_RESUME_ID) { - System.out.println("1. 이력서 데이터 삭제"); - }else if (id == PDF_RESUME_ID){ - System.out.println("1. 이력서 데이터 삭제"); - System.out.println("2. pdf 파일 삭제"); - } - } } + //Controller와 같이 Service를 사용하는 객체 public class Client { public static void main(String[] args) { ResumeService resumeService = new ResumeService(new ResumeRepository()); //사람인폼으로 이력서 저장 - resumeService.saveResumeBy(new SaraminFormDto()); - resumeService.getResume(SARAMIN_RESUME_ID); - resumeService.deleteResume(SARAMIN_RESUME_ID); + resumeService.saveResume(new SaraminFormResume().toEntity()); System.out.println(); //Pdf로 이력서 저장 - resumeService.saveResumeBy(new PdfFormDto()); - resumeService.getResume(PDF_RESUME_ID); - resumeService.deleteResume(PDF_RESUME_ID); + resumeService.saveResume(new FileFormResume().toEntity()); + } } 결과 : -===사람인 이력서 작성=== +====이력서 저장==== +기본정보, 경력사항 등 기본 값 validate와 셋팅사람인 이력서 제외 item 조회 +불러오기 가능한 이력서 리스트 조회 +인적성 응시 시험 리스트 조회 saramin 타입의 이력서 저장 -===이력서 조회=== -1. 이력서 정보 반환 -===이력서 삭제=== -1. 이력서 데이터 삭제 - -===PDF로 이력서 작성=== -pdf 타입의 이력서 저장 -===이력서 조회=== -1. pdf 이미지로 변환 -2. 이력서 정보 반환 -===이력서 삭제=== -1. 이력서 데이터 삭제 -2. pdf 파일 삭제 + +====이력서 저장==== +기본정보, 경력사항 등 기본 값 validate와 셋팅파일 정보 파라미터 할당 +파일 이력서 제외 item 조회 +파일 썸네일 추출 +파일 서버에 업로드 +file 타입의 이력서 저장 ``` 주어진 요구사항을 필요한 부분만 나타내어 개발해보면 위와 같이 개발 할 수 있을 것이다. 여기서 만약에 `URL`을 통한 이력서 서비스를 추가로 제공해야 한다면 `Service`에 초점을 맞춰서 보면 아래와 같이 코드를 추가해야 할 것이다. ```java -public class UrlFormDto { +//새로 추가된 URL폼 이력서 DTO +public class UrlFormResume { private String basicInfo; private String careers; private String educations; @@ -170,39 +144,163 @@ public class UrlFormDto { public class ResumeService { private final ResumeRepository resumeRepository; - public void saveResumeBy(SaraminFormDto resumeData) { - System.out.println("===사람인 이력서 저장==="); - resumeRepository.save(resumeData.toEntity()); + public void saveResume(Resume resume) { + System.out.println("====이력서 저장===="); + System.out.print("기본정보, 경력사항 등 기본 값 validate와 셋팅"); + if (resume.getResumeType().equals("saramin")) { + System.out.println("사람인 이력서 제외 item 조회"); + System.out.println("불러오기 가능한 이력서 리스트 조회"); + System.out.println("인적성 응시 시험 리스트 조회"); + }else if(resume.getResumeType().equals("file")) { + System.out.println("파일 정보 파라미터 할당"); + System.out.println("파일 이력서 제외 item 조회"); + System.out.println("파일 썸네일 추출"); + System.out.println("파일 서버에 업로드"); + }else if(resume.getResumeType().equals("url")) { //새롭게 추가되는 분기(비즈니스 로직) + System.out.println("URL 이력서 제외 item 조회"); + System.out.println("URL 통해 html 다운"); + System.out.println("URL 썸네일 추출"); + System.out.println("파일 서버에 업로드"); + } + resumeRepository.save(resume); } +} - public void saveResumeBy(PdfFormDto resumeData) { - System.out.println("===PDF로 이력서 저장==="); - resumeRepository.save(resumeData.toEntity()); +public class Client { + public static void main(String[] args) { + //URL로 이력서 저장 + resumeService = new ResumeService(new UrlResumeCommand(),new ResumeRepository()); + resumeService.saveResume(new UrlFormResume().toEntity()); } +} +결과 : +====이력서 저장==== +기본정보, 경력사항 등 기본 값 validate와 셋팅URL 이력서 제외 item 조회 +URL 통해 html 다운 +URL 썸네일 추출 +파일 서버에 업로드 +url 타입의 이력서 저장 +``` +이력서의 종류가 추가될 수록 서비스가 담고있는 비즈니스 로직은 무거워지고 요구사항이 변경될때마다 서비스의 객체가 빈번히 수정이 일어나게 된다. 수정히 빈번히 발생하게 되면 다른 곳에는 영향이 있는지없는지 체크하기 힘들며, 테스트코드를 제아무리 꼼꼼히 작성했다하더라도 많은 객체의 의존성이 묶여있어 단위테스트가 어려워지게 된다. - //저장 메서드 추가 - public void saveResumeBy(UrlFormDto resumeData) { - System.out.println("===URL로 이력서 저장==="); - resumeRepository.save(resumeData.toEntity()); +이를 커맨드 패턴을 이용해서 저장,조회,삭제에 대한 행동을 더 세부객체에게 위임해보자. + +### 커맨드 패턴 적용 후 +```java +//Command 인터페이스 +public interface ResumeCommand { + void saveResume(); //저장 명령 +} + +//Concrete Command +//사람인 폼 이력서 Command +@RequiredArgsConstructor +public class SaraminResumeCommand implements ResumeCommand { + private final Resume resume; //Receiver + private final ResumeRepository resumeRepository; //Receiver + + //저장 세부 비즈니스로직을 수행 + @Override + public void saveResume() { + System.out.print("기본정보, 경력사항 등 기본 값 validate와 셋팅"); + System.out.println(resume.getResumeType() + " 이력서 제외 item 조회"); + System.out.println("불러오기 가능한 이력서 리스트 조회"); + System.out.println("인적성 응시 시험 리스트 조회"); + resumeRepository.save(resume); } +} - public void getResume(int resumeId) { - Resume resume = resumeRepository.getResumeById(resumeId); - if (resume.getResumeType().equals("pdf")) { - System.out.println("pdf 파일 이미지로 변환"); - }else if(resume.getResumeType().equals("url")) { //이력서 조회 로직 추가 - System.out.println("url에서 html 다운"); - } +//Concrete Command +//파일 폼 이력서 Command +@RequiredArgsConstructor +public class FileResumeCommand implements ResumeCommand { + private final Resume resume; //Receiver + private final ResumeRepository resumeRepository; //Receiver + + //저장 세부 비즈니스로직을 수행 + @Override + public void saveResume(Resume resume) { + System.out.print("기본정보, 경력사항 등 기본 값 validate와 셋팅"); + System.out.println(resume.getResumeType() + " 이력서 제외 item 조회"); + System.out.println("파일 정보 파라미터 할당"); + System.out.println("파일 썸네일 추출"); + System.out.println("파일 서버에 업로드"); + resumeRepository.save(resume); } +} - public void deleteResume(int resumeId) { - Resume resume = resumeRepository.getResumeById(resumeId); +//Concrete Command +//URL 폼 이력서 Command +@RequiredArgsConstructor +public class UrlResumeCommand implements ResumeCommand { + private final Resume resume; //Receiver + private final ResumeRepository resumeRepository; //Receiver + + //저장 세부 비즈니스로직을 수행 + @Override + public void saveResume(Resume resume) { + System.out.print("기본정보, 경력사항 등 기본 값 validate와 셋팅"); + System.out.println(resume.getResumeType() + " 이력서 제외 item 조회"); + System.out.println("URL 통해 html 다운"); + System.out.println("URL 썸네일 추출"); + System.out.println("파일 서버에 업로드"); + resumeRepository.save(resume); + } +} - resumeRepository.deleteResumeById(resumeId); - if (resume.getResumeType().equals("pdf")) { //만일 다른 로직이 필요하다면 로직 추가 - System.out.println("pdf 파일 삭제"); - } +//이력서 서비스 +//Invoker +@RequiredArgsConstructor +public class ResumeService { + private final ResumeCommand resumeCommand; + + public void saveResume(Resume resume) { + System.out.println("====이력서 저장===="); + resumeCommand.saveResume(); //명령을 통해 비즈니스 로직 수행을 세부객체에게 위임 } } -``` \ No newline at end of file + +public class Client { + public static void main(String[] args) { + //사람인폼으로 이력서 저장 + Resume resume = new SaraminFormResume().toEntity(); + ResumeService resumeService = new ResumeService(new SaraminResumeCommand(resume,new ResumeRepository())); + resumeService.saveResume(); + + System.out.println(); + + //Pdf로 이력서 저장 + resume = new FileFormResume().toEntity(); + resumeService = new ResumeService(new FileResumeCommand(resume,new ResumeRepository())); + resumeService.saveResume(); + + System.out.println(); + + //URL로 이력서 저장 + resume = new UrlFormResume().toEntity(); + resumeService = new ResumeService(new UrlResumeCommand(resume,new ResumeRepository())); + resumeService.saveResume(); + } +} +결과 : +====이력서 저장==== +기본정보, 경력사항 등 기본 값 validate와 셋팅saramin 이력서 제외 item 조회 +불러오기 가능한 이력서 리스트 조회 +인적성 응시 시험 리스트 조회 +saramin 타입의 이력서 저장 + +====이력서 저장==== +기본정보, 경력사항 등 기본 값 validate와 셋팅file 이력서 제외 item 조회 +파일 정보 파라미터 할당 +파일 썸네일 추출 +파일 서버에 업로드 +file 타입의 이력서 저장 + +====이력서 저장==== +기본정보, 경력사항 등 기본 값 validate와 셋팅url 이력서 제외 item 조회 +URL 통해 html 다운 +URL 썸네일 추출 +파일 서버에 업로드 +url 타입의 이력서 저장 +``` From d6c0deb337b5030fc2591f4a608c8b7e865065a1 Mon Sep 17 00:00:00 2001 From: EuiSung <52964858+gowoonsori@users.noreply.github.com> Date: Tue, 15 Mar 2022 20:49:47 +0900 Subject: [PATCH 3/6] =?UTF-8?q?Add:=20=ED=94=8C=EB=9D=BC=EC=9D=B4=EC=9B=A8?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=ED=8C=A8=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" | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 "\352\265\254\354\241\260/6\354\243\274\354\260\250-\355\224\214\353\235\274\354\235\264\354\233\250\354\235\264\355\212\270/summary/code.md" diff --git "a/\352\265\254\354\241\260/6\354\243\274\354\260\250-\355\224\214\353\235\274\354\235\264\354\233\250\354\235\264\355\212\270/summary/code.md" "b/\352\265\254\354\241\260/6\354\243\274\354\260\250-\355\224\214\353\235\274\354\235\264\354\233\250\354\235\264\355\212\270/summary/code.md" new file mode 100644 index 0000000..27ba4bf --- /dev/null +++ "b/\352\265\254\354\241\260/6\354\243\274\354\260\250-\355\224\214\353\235\274\354\235\264\354\233\250\354\235\264\355\212\270/summary/code.md" @@ -0,0 +1,104 @@ +## 예제 코드 + +### 요구사항 +1. 사람인에 근무하고 있는 사람들의 정보를 볼 수 있는 시스템을 만들고 싶어요. +2. 근무자들은 한 조직(팀)에 속해 있으니 팀의 정보도 포함해야 해요. + +이를 위해 사람인의 근무자들의 정보를 DB나 api를 통하여 데이터를 불러와 각각 Member라는 객체로 만들어 서비스를 개발할 수 있을 것이다. + +여기서 우리는 Member나는 객체를 만드는 과정에 집중해서 살펴 볼 것이다. + +### 패턴 적용 전 코드 +```java +@AllArgsConstructor +public class Member { + private String name; //이름 + private LocalDate birthDay; //생일 + private String description; //기타 설명 + private String position; //직급 + private String organizationName; //조직이름 + private String teamName; //팀이름 + private String partName; //파트이름 +} + +public class Client { + public static void main(String[] args) { + Member member1 = new Member("고길동", LocalDate.of(1980,12,7),"종로로 갈까요?", + "파트장","IT연구소","사람인개발팀","D1"); + Member member2 = new Member("고둘리", LocalDate.of(1995,4,8),"호잇!", + "팀원","IT연구소","사람인개발팀","D1"); + Member member3 = new Member("또치", LocalDate.of(1988,2,7),"난 세상에서 가장 우아하고, 잘난 귀족 타조야", + "파트장","IT연구소","사람인개발팀","D2"); + Member member4 = new Member("도우너", LocalDate.of(1992,8,17),"안녕하세요!!", + "팀원","IT연구소","사람인개발팀","D2"); + Member member5 = new Member("마이클", LocalDate.of(1990,3,27),"안녕하세요!!", + "팀원","IT연구소","사람인개발팀","D2"); + } +} +``` +예시에서는 팀원이 5명밖에 안되긴 하지만 5명의 예시만 봐도 팀정보와 같이 중복되며 자주 변하지 않는 데이터가 보일 것이다. + +이런 부분들을 분류하고 플라이웨이트 패턴을 적용해서 메모리를 절약해보자. + +### 적용 후 코드 +```java +@AllArgsConstructor +public class Member { + private String name; + private LocalDate birthDay; + private String description; + private String position; + private Team team; //자주 변하지 않을 팀 정보를 따로 객체로 분리 +} + +//팀 정보 객체 +//Flyweight +@AllArgsConstructor +public class Team { + private String organizationName; + private String teamName; + private String partName; +} + +//FlyWeight Factory +public class TeamFactory { + private final Map cache = new HashMap<>(); //캐싱하기 위한 Map + private final Pattern teamPattern = Pattern.compile("([a-zA-Z0-9가-힣 ]*):([a-zA-Z0-9가-힣 ]*):([a-zA-Z0-9가-힣 ]*)"); //team조회를 위한 입력 표현식 (조직이름:팀이름:파트이름) + + public Team getTeam(String team) { + Matcher teamMatcher = teamPattern.matcher(team); + + //정의한 regxr과 다르다면 error + if(!teamMatcher.matches()){ + throw new IllegalArgumentException(); + } + + if (cache.containsKey(team)) { + return cache.get(team); //캐싱되어있다면 바로 반환 + } else { + Team newTeam = new Team(teamMatcher.group(1),teamMatcher.group(2),teamMatcher.group(3)); //새로운 팀 생성 + cache.put(team, newTeam); //캐싱 + return newTeam; + } + } +} + +//Client +//FlyWeight를 사용 +public class Client { + public static void main(String[] args) { + TeamFactory teamFactory = new TeamFactory(); + + Member member1 = new Member("고길동", LocalDate.of(1980,12,7),"종로로 갈까요?", + "파트장",teamFactory.getTeam("IT연구소:사람인개발팀:D1")); + Member member2 = new Member("고둘리", LocalDate.of(1995,4,8),"호잇!", + "팀원",teamFactory.getTeam("IT연구소:사람인개발팀:D1")); + Member member3 = new Member("또치", LocalDate.of(1988,2,7),"난 세상에서 가장 우아하고, 잘난 귀족 타조야", + "파트장",teamFactory.getTeam("IT연구소:사람인개발팀:D2")); + Member member4 = new Member("도우너", LocalDate.of(1992,8,17),"안녕하세요!!", + "팀원",teamFactory.getTeam("IT연구소:사람인개발팀:D2")); + Member member5 = new Member("마이클", LocalDate.of(1990,3,27),"안녕하세요!!", + "팀원",teamFactory.getTeam("IT연구소:사람인개발팀:D2")); + } +} +``` \ No newline at end of file From 6e4b451a4effb051d6b10c28e34ee5bbdd3af3b3 Mon Sep 17 00:00:00 2001 From: EuiSung <52964858+gowoonsori@users.noreply.github.com> Date: Tue, 15 Mar 2022 20:53:37 +0900 Subject: [PATCH 4/6] =?UTF-8?q?Delete:=20=ED=94=8C=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=9B=A8=EC=9D=B4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0(=EB=8B=A4=EB=A5=B8=20=EB=B8=8C=EB=9E=9C=EC=B9=98?= =?UTF-8?q?=EC=97=90=20=EC=9E=98=EB=AA=BB=20=EC=98=AC=EB=A6=BC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../summary/code.md" | 104 ------------------ 1 file changed, 104 deletions(-) delete mode 100644 "\352\265\254\354\241\260/6\354\243\274\354\260\250-\355\224\214\353\235\274\354\235\264\354\233\250\354\235\264\355\212\270/summary/code.md" diff --git "a/\352\265\254\354\241\260/6\354\243\274\354\260\250-\355\224\214\353\235\274\354\235\264\354\233\250\354\235\264\355\212\270/summary/code.md" "b/\352\265\254\354\241\260/6\354\243\274\354\260\250-\355\224\214\353\235\274\354\235\264\354\233\250\354\235\264\355\212\270/summary/code.md" deleted file mode 100644 index 27ba4bf..0000000 --- "a/\352\265\254\354\241\260/6\354\243\274\354\260\250-\355\224\214\353\235\274\354\235\264\354\233\250\354\235\264\355\212\270/summary/code.md" +++ /dev/null @@ -1,104 +0,0 @@ -## 예제 코드 - -### 요구사항 -1. 사람인에 근무하고 있는 사람들의 정보를 볼 수 있는 시스템을 만들고 싶어요. -2. 근무자들은 한 조직(팀)에 속해 있으니 팀의 정보도 포함해야 해요. - -이를 위해 사람인의 근무자들의 정보를 DB나 api를 통하여 데이터를 불러와 각각 Member라는 객체로 만들어 서비스를 개발할 수 있을 것이다. - -여기서 우리는 Member나는 객체를 만드는 과정에 집중해서 살펴 볼 것이다. - -### 패턴 적용 전 코드 -```java -@AllArgsConstructor -public class Member { - private String name; //이름 - private LocalDate birthDay; //생일 - private String description; //기타 설명 - private String position; //직급 - private String organizationName; //조직이름 - private String teamName; //팀이름 - private String partName; //파트이름 -} - -public class Client { - public static void main(String[] args) { - Member member1 = new Member("고길동", LocalDate.of(1980,12,7),"종로로 갈까요?", - "파트장","IT연구소","사람인개발팀","D1"); - Member member2 = new Member("고둘리", LocalDate.of(1995,4,8),"호잇!", - "팀원","IT연구소","사람인개발팀","D1"); - Member member3 = new Member("또치", LocalDate.of(1988,2,7),"난 세상에서 가장 우아하고, 잘난 귀족 타조야", - "파트장","IT연구소","사람인개발팀","D2"); - Member member4 = new Member("도우너", LocalDate.of(1992,8,17),"안녕하세요!!", - "팀원","IT연구소","사람인개발팀","D2"); - Member member5 = new Member("마이클", LocalDate.of(1990,3,27),"안녕하세요!!", - "팀원","IT연구소","사람인개발팀","D2"); - } -} -``` -예시에서는 팀원이 5명밖에 안되긴 하지만 5명의 예시만 봐도 팀정보와 같이 중복되며 자주 변하지 않는 데이터가 보일 것이다. - -이런 부분들을 분류하고 플라이웨이트 패턴을 적용해서 메모리를 절약해보자. - -### 적용 후 코드 -```java -@AllArgsConstructor -public class Member { - private String name; - private LocalDate birthDay; - private String description; - private String position; - private Team team; //자주 변하지 않을 팀 정보를 따로 객체로 분리 -} - -//팀 정보 객체 -//Flyweight -@AllArgsConstructor -public class Team { - private String organizationName; - private String teamName; - private String partName; -} - -//FlyWeight Factory -public class TeamFactory { - private final Map cache = new HashMap<>(); //캐싱하기 위한 Map - private final Pattern teamPattern = Pattern.compile("([a-zA-Z0-9가-힣 ]*):([a-zA-Z0-9가-힣 ]*):([a-zA-Z0-9가-힣 ]*)"); //team조회를 위한 입력 표현식 (조직이름:팀이름:파트이름) - - public Team getTeam(String team) { - Matcher teamMatcher = teamPattern.matcher(team); - - //정의한 regxr과 다르다면 error - if(!teamMatcher.matches()){ - throw new IllegalArgumentException(); - } - - if (cache.containsKey(team)) { - return cache.get(team); //캐싱되어있다면 바로 반환 - } else { - Team newTeam = new Team(teamMatcher.group(1),teamMatcher.group(2),teamMatcher.group(3)); //새로운 팀 생성 - cache.put(team, newTeam); //캐싱 - return newTeam; - } - } -} - -//Client -//FlyWeight를 사용 -public class Client { - public static void main(String[] args) { - TeamFactory teamFactory = new TeamFactory(); - - Member member1 = new Member("고길동", LocalDate.of(1980,12,7),"종로로 갈까요?", - "파트장",teamFactory.getTeam("IT연구소:사람인개발팀:D1")); - Member member2 = new Member("고둘리", LocalDate.of(1995,4,8),"호잇!", - "팀원",teamFactory.getTeam("IT연구소:사람인개발팀:D1")); - Member member3 = new Member("또치", LocalDate.of(1988,2,7),"난 세상에서 가장 우아하고, 잘난 귀족 타조야", - "파트장",teamFactory.getTeam("IT연구소:사람인개발팀:D2")); - Member member4 = new Member("도우너", LocalDate.of(1992,8,17),"안녕하세요!!", - "팀원",teamFactory.getTeam("IT연구소:사람인개발팀:D2")); - Member member5 = new Member("마이클", LocalDate.of(1990,3,27),"안녕하세요!!", - "팀원",teamFactory.getTeam("IT연구소:사람인개발팀:D2")); - } -} -``` \ No newline at end of file From 572fbf9445f4c5c30afa27ad8a13e4c808ea2fb2 Mon Sep 17 00:00:00 2001 From: EuiSung <52964858+gowoonsori@users.noreply.github.com> Date: Wed, 30 Mar 2022 00:27:55 +0900 Subject: [PATCH 5/6] Update: Proxy --- .../hong.md" | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git "a/\352\265\254\354\241\260/7\354\243\274\354\260\250-\355\224\204\353\241\235\354\213\234/hong.md" "b/\352\265\254\354\241\260/7\354\243\274\354\260\250-\355\224\204\353\241\235\354\213\234/hong.md" index e314637..fa62ab3 100644 --- "a/\352\265\254\354\241\260/7\354\243\274\354\260\250-\355\224\204\353\241\235\354\213\234/hong.md" +++ "b/\352\265\254\354\241\260/7\354\243\274\354\260\250-\355\224\204\353\241\235\354\213\234/hong.md" @@ -1452,6 +1452,34 @@ Interceptor에 걸리지 못해 그대로 Enhancer의 프록시 객체로 로직 이는 트랜잭션, AOP, Secruity,Async 에서도 발생할 수 있는 예외상황으로 알아두면 좋을 것 같다. + +### 22/03/29 추가 정리 +``` +private: +DispatcherServlet->InvocableHandlerMethod.doInvoke()-> proxy class ->... + +public: +DispatcherServlet->InvocableHandlerMethod.doInvoke()-> proxy class ->public perform AOP->CglibAopProxy.invokeJoinpoint()-> obtain bean attribute ->.. +``` +Controller의 private 메서드가 호출될 수 있는 것은 reflection을 이용해 메소드를 호출하기 때문에 호출이 될 수 있다. + +cglib를 이용하는 AOP가 적용된 클래스라면 private메서드는 호출 될까? +- aop가 적용하려고 한다면 적용이 되지 않고 원본 메서드가 사용된다. +- aop가 적용되지 않는 그냥 method 라면 위와 같이 정상적으로 로직이 수행이 가능하다. + - 특정 메서드의 AOP적용 유무와 상관없이 클래스가 Enhancer대상이라면 Enhancer를 통해 메서드 수행 + - cglib는 상속을 이용하기에 public 메서드만 재정의하여 사용되기 때문에 private 메서드는 원본 메서드가 사용된다고 이해할 수 있다. + - 하지만, logtrace를 보면 실행주체는 Enhancer객체이다. 어떻게 Enhancer에서 없는 private메서드를 실행할까? + - Enhancer는 프록시객체의 SuperClass를 reflection으로 private메서드도 실행이 Enhancer에서 가능하다. + +만일, private 메서드에서 의존성이 주입된 객체를 사용하려고하면 null이 주입되어 사용된다. 그 이유는? +- 만일 public 메서드라면 AOP기능을 수행후 실제 target메서드를 수행하기때문에 의존성이 주입된 실제 빈으로 로직을 정상적으로 수행. +- private 메서드라면 호출은 되지만 의존성 객체가 null이 되어 NullPointException이 발생할 수 있다. + - AOP가 적용되는 메서드라면 실제 target을 통해 메서드가 수행될테지만 적용되지 않는 객체라면 실제 메서드가 빈에서 수행되는 것이 아니라 Enhancer객체에서 로직이 수행된다. + - Enhancer는 Proxy객체로 상태를 가지고 있지 않기 때문에 의존성주입된 객체가 null이 되어 발생하는 문제이다. + + + +


### Reference From ba5dfa44322c2ba960cf5347cba387ed97cf8e06 Mon Sep 17 00:00:00 2001 From: dev-splin Date: Mon, 11 Apr 2022 00:04:40 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=EC=BB=A4=EB=A7=A8=EB=93=9C=20=ED=8C=A8?= =?UTF-8?q?=ED=84=B4=20-=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" | 114 +++++++++++++++++- 1 file changed, 110 insertions(+), 4 deletions(-) diff --git "a/\355\226\211\353\217\231/8\354\243\274\354\260\250-\354\273\244\353\247\250\353\223\234/summary/code.md" "b/\355\226\211\353\217\231/8\354\243\274\354\260\250-\354\273\244\353\247\250\353\223\234/summary/code.md" index 2f98942..b1104f6 100644 --- "a/\355\226\211\353\217\231/8\354\243\274\354\260\250-\354\273\244\353\247\250\353\223\234/summary/code.md" +++ "b/\355\226\211\353\217\231/8\354\243\274\354\260\250-\354\273\244\353\247\250\353\223\234/summary/code.md" @@ -1,5 +1,32 @@ -## 코드 -### 요구사항 +## 1. 커맨드 패턴(Command Pattern)이란? + +> 요청을 캡슐화 하여 호출자(invoker)와 수신자(receiver)를 분리하는 패턴 + +요청을 호출하는 쪽과 수신하는 쪽을 커맨드 객체를 사용해서 **디커플링**하여 분리시키는게 핵심이다. + +요청을 처리하는 방법이 바뀌더라도, 호출자의 코드는 변경되지 않는다. + +코드를 통해 실제로 필요한 상황을 알아보자. + +![command](https://user-images.githubusercontent.com/79291114/162621083-24761d9f-91af-4675-946b-df74a4062ad5.PNG) + +- `Invoker` (Button) : Command 에서 제공하는 메소드를 실행함 + - 주로 `execute()` 메소드를 호출하고, 경우에 따라 undo() 와 같은 메소드도 사용 +- `Command` : 구체적인 커맨드를 추상화 시킨 인터페이스 + - Interface 또는 abstract 로 선언 +- `ConcreteCommand` : 구현체, 상속받은 클래스 + - 실제로 어떤 Receiver를 사용할지, 요청에 대한 작업을 수행하는 메소드와 파라미터가 필요한지 구현 +- `Receiver (Light & Police)` : 실제 요청에 대한 작업을 수행 + +위와 같이 커맨드 패턴은 Command 를 재사용할 수 있는 장점이 있고, Command를 로깅한다거나, undo 등 추가적인 작업을 관리하기 쉬워진다. + + + + + +## 2. 코드로 알아보는 커맨드 패턴 + +### 2-1. 요구사항 사람인서비스에 이력서를 저장하고 조회하고 삭제하는 서비스를 개발하고자 할때 `사람인 양식`과 `Pdf`를 통한 이력서 생성 서비스를 제공하고 있으며 해당 양식으로 생성한 이력서마다 저장하는 과정에서 내부 파라미터를 셋팅하는 로직이 다르다고 가정을 해보자. ```java @@ -182,11 +209,18 @@ URL 썸네일 추출 url 타입의 이력서 저장 ``` -이력서의 종류가 추가될 수록 서비스가 담고있는 비즈니스 로직은 무거워지고 요구사항이 변경될때마다 서비스의 객체가 빈번히 수정이 일어나게 된다. 수정히 빈번히 발생하게 되면 다른 곳에는 영향이 있는지없는지 체크하기 힘들며, 테스트코드를 제아무리 꼼꼼히 작성했다하더라도 많은 객체의 의존성이 묶여있어 단위테스트가 어려워지게 된다. +이력서의 종류가 추가될 수록 서비스가 담고있는 비즈니스 로직은 무거워지고 요구사항이 변경될때마다 서비스의 객체가 빈번히 수정이 일어나게 된다. + +또한, 수정이 빈번히 발생하게 되면 다른 곳에는 영향이 있는지없는지 체크하기 힘들다. + + 테스트코드를 제아무리 꼼꼼히 작성했다하더라도 많은 객체의 의존성이 묶여있어 단위테스트가 어려워지게 된다. 이를 커맨드 패턴을 이용해서 저장,조회,삭제에 대한 행동을 더 세부객체에게 위임해보자. -### 커맨드 패턴 적용 후 + + +### 2-2. 커맨드 패턴 적용 후 + ```java //Command 인터페이스 public interface ResumeCommand { @@ -304,3 +338,75 @@ URL 썸네일 추출 파일 서버에 업로드 url 타입의 이력서 저장 ``` + + + + + +## 3. 장점과 단점 + +### 3-1. 장점 + +- 기존 코드를 변경하지 않고 새로운 Command를 만들 수 있음 `(OCP 원칙)` +- 수신자(Receiver)의 코드가 변경되어도, 호출자(Invoker)의 코드는 변경되지 않음 `(단일책임원칙)` +- Command 객체를 로깅, DB에 저장, 네트워크로 전송하는 등 다양한 방법으로 활용 가능 + + + +### 3-2. 단점 + +- 대부분의 디자인 패턴가 마찬가지로 코드가 복잡하고 클래스가 많아짐 + + + + + +## 4. Command 패턴과 State 패턴과의 비교 + +공부를 하면서 `전략 패턴, 커맨드 패턴, 상태 패턴`이 비슷하다고 생각했는데, 이 3개 패턴의 다이어그램을 보면서 차이점을 간단히 정리했다. + + + +### 4-1. 전략 패턴 + +[![strategy](https://user-images.githubusercontent.com/79291114/153312563-a830529c-8f3e-47f8-b047-9c84225f3401.PNG)](https://user-images.githubusercontent.com/79291114/153312563-a830529c-8f3e-47f8-b047-9c84225f3401.PNG) + +여러 알고리즘을 캡슐화하고 상호 교환 가능하게 만드는 패턴 + +- 여러 알고리즘을 `상호 교환` 한다는 것은 이미 알고리즘이 실행되는 것은 정해져있고 해당 알고리즘만 교체해준다는 의미입니다. +- 다이어그램을 보면, **Client가 ConcreteStrategy를 교체**하는 것을 볼 수 있습니다. +- 즉, **행동이 정해져 있는 상태에서 어떠한 방법으로 수행**할지만 달라집니다. + + + +### 4-2. 커맨드 패턴 + +[![command](https://user-images.githubusercontent.com/79291114/153312559-5b068040-1e19-493c-a680-499744f56f08.PNG)](https://user-images.githubusercontent.com/79291114/153312559-5b068040-1e19-493c-a680-499744f56f08.PNG) + +요청을 캡슐화하여 호출자와 수신자를 분리하는 패턴 + +- 호출자와 수신자를 분리한다는 것은 `명령(호출자)`과 `행동(수신자)`을 분리한다는 의미입니다. +- 다이어그램을 보면, **Invoker에 따라서 다양한 Receiver가 호출**되는 것을 알 수 있습니다. +- 즉, **명령에 따라서 다른 행동을 수행**한다는 것입니다. + + + +### 4-3. 상태 패턴 + +[![state](https://user-images.githubusercontent.com/79291114/153312562-2f70656c-3cd2-4537-8acd-5bfeea7dcfe6.PNG)](https://user-images.githubusercontent.com/79291114/153312562-2f70656c-3cd2-4537-8acd-5bfeea7dcfe6.PNG) + +객체 내부 상태 변경에 따라 객체의 행동이 달라지는 패턴 + +- 말 그대로 **객체 내부 상태에 따라서 다른 행동을 수행**한다는 것입니다. +- 다이어그램을 보면, **Client가 changeState로 상태를 변경해줌에 따라 ConcreteState가 바뀌면서 다른 행동**을 하게됩니다. + + + + + +## 5. 마치며 + +일단 전략 패턴, 커맨드 패턴, 상태 패턴과 같이 비슷한 패턴은 생김새로 구분하는 것이 아니라, 목적으로 구분해야 한다. + +커맨드 패턴은 명령에 따라 커맨드 구현체를 교체하여 다른 행동을 할 수 있기 때문에 유용하게 사용할 수 있다. +