From 1900532e4a841cb35b8b08926566dc5f8777de8c Mon Sep 17 00:00:00 2001 From: EuiSung <52964858+gowoonsori@users.noreply.github.com> Date: Wed, 2 Mar 2022 22:51:45 +0900 Subject: [PATCH 1/3] =?UTF-8?q?Add:=20=ED=8C=A9=ED=86=A0=EB=A6=AC=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=BD=94=EB=93=9C=20=EC=98=88=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code.md" | 325 ++++++++++++++++++ 1 file changed, 325 insertions(+) create mode 100644 "\352\260\235\354\262\264\354\203\235\354\204\261/2\354\243\274\354\260\250-\355\214\251\355\206\240\353\246\254\353\251\224\354\206\214\353\223\234/code.md" diff --git "a/\352\260\235\354\262\264\354\203\235\354\204\261/2\354\243\274\354\260\250-\355\214\251\355\206\240\353\246\254\353\251\224\354\206\214\353\223\234/code.md" "b/\352\260\235\354\262\264\354\203\235\354\204\261/2\354\243\274\354\260\250-\355\214\251\355\206\240\353\246\254\353\251\224\354\206\214\353\223\234/code.md" new file mode 100644 index 0000000..fc5126c --- /dev/null +++ "b/\352\260\235\354\262\264\354\203\235\354\204\261/2\354\243\274\354\260\250-\355\214\251\355\206\240\353\246\254\353\251\224\354\206\214\353\223\234/code.md" @@ -0,0 +1,325 @@ +### 예제 상황 +회원가입 서비스를 만들고자 할때 회원종류를 일반 회원과 기업회원을 구분하여 회원가입 시키고자 함. + +```java +//회원의 경우 컬럼(속성)은 달라질 수 있으나 동일한 행동을 수행하는 부분을 인터페이로 분리 +public interface Member { + public void validateIdentity() throws Exception; //회원의 속성 validate + public void register() throws Exception; //회원가입 + public String toString(); //String으로 직렬화 메서드 +} + +//기업 회원 +public class CompanyMember implements Member{ + private String csn; //사번 + private String name; //기업명 + private boolean isPaid; //유료서비스 결제 여부 + + public CompanyMember(String csn, String name, boolean isPaid) { + this.csn = csn; + this.name = name; + this.isPaid = isPaid; + } + + @Override + public void validateIdentity() throws Exception{ + System.out.println("기업 회원 property validate"); + } + + @Override + public void register() throws InterruptedException { + System.out.println("기업 회원 회원가입 로직 수행 ..."); + Thread.sleep(1); //회원가입 로직 수행중임을 가정하기 위해 1초 sleep + System.out.println("기업 회원 회원가입 완료"); + } + + @Override + public String toString() { + return "CompanyMember{" + + "csn='" + csn + '\'' + + ", name='" + name + '\'' + + ", isPaid=" + isPaid + + '}'; + } +} + + +//개인 회원 +public class PersonalMember implements Member{ + private String name; //이름 + private String phoneNumber; //전화번호 + private String email; //이메일 + + public PersonalMember(String name, String phoneNumber, String email) { + this.name = name; + this.phoneNumber = phoneNumber; + this.email = email; + } + + @Override + public void validateIdentity() throws Exception{ + System.out.println("일반 회원 property validate"); + } + + @Override + public void register() throws InterruptedException { + System.out.println("일반 회원 회원가입 로직 수행"); + Thread.sleep(1); //회원가입 로직 수행중임을 가정하기 위해 1초 sleep + System.out.println("일반 회원 회원가입"); + } + + @Override + public String toString() { + return "PersonalMember{" + + "name='" + name + '\'' + + ", phoneNumber='" + phoneNumber + '\'' + + ", email='" + email + '\'' + + '}'; + } +} + + +//인증 서비스 +//Controller에서 직접 사용자를 생성하고 로직을 수행하지 않고 회원가입의 로직을 다른 계층에게 위임할 수 있도록 서비스 계층 추가 +public class AuthService { + //... 로그인, 로그아웃 등 메서드들 생략 + + //회원가입 메서드 + public Member signUp(String type, Map userData) throws Exception { + Member member = null; + + //type을 input으로 회원을 구분하여 생성 + switch (type){ + case "p": + member = new PersonalMember((String)userData.get("name"),(String)userData.get("phoneNumber"),(String)userData.get("email")); + break; + case "c": + member = new CompanyMember((String)userData.get("csn"),(String)userData.get("name"),false); + break; + default: + throw new Exception(); //지원하지 않은 type이면 exception + } + + member.validateIdentity(); //회원가입 전 회원 속성 validate 로직 수행 + member.register(); //회원가입 로직 수행 + return member; + } +} + +//Client +public class Controller { + //빠른 메서드 실행을 위해 main메서드에서 수행 + public static void main(String[] args) throws Exception { + AuthService authService = new AuthService(); //Service 생성 + Member member = authService.signUp("p", Map.of("name","홍길동","phoneNumber","010-1111-1111","email","example@example.com")); //SignUp 수행 + System.out.println(member); //회원정보 String으로 변환후 출력 + } +} + +//Console Print +일반 회원 property validate +일반 회원 회원가입 로직 수행 +일반 회원 회원가입 +PersonalMember{name='홍길동', phoneNumber='010-1111-1111', email='example@example.com'} +``` +위와 같이 회원별로 나누어 개인 회원에게 회원가입등의 행동을 책임지게 해고 Service 계층을두어 switch문으로 특정 회원을 생성하여 로직을 수행함으로써 개발을 진행했다. + +이렇게 개발을 완료했으나 추후에 기능을 추가함에 있어 세가지 `문제점`에 직면하게 되었다. + +1. AuthService의 signUp의 메서드와 같이 특정 type을 통해 회원을 생성하는 로직이 다른 메서드에도 필요하다면 해당 switch문을 반복해야 한다는 점. +2. 새로운 회원종류를 추가해야한다면 모든 switch 문의 코드를 수정해야 한다는 점. +3. AuthService가 Member의 구체적인 클래스들과 밀접하게 연결되어 많은 종속성을 가진다는 점 + + +
+ +위 문제를 해결하기 위해 Factory클래스를 만들어 해당 클래스에서 객체 생성 로직을 담당하도록 옮겨보자. + +### Simple Factory +```java +//Service내의 객체 생성 로직 부분을 Factory로 분리 +public class MemberFactory { + + //Member 생성 메서드 + public Member createMember(String type, Map userData) throws Exception { + Member member = null; + switch (type){ + case "p": + member = new PersonalMember((String)userData.get("name"),(String)userData.get("phoneNumber"),(String)userData.get("email")); + break; + case "c": + member = new CompanyMember((String)userData.get("csn"),(String)userData.get("name"),false); + break; + default: + throw new Exception(); + } + + member.validateIdentity(); //회원가입 전 회원 속성 validate 로직 수행 + return member; + } +} + +//인증 서비스 +public class AuthService { + private MemberFactory memberFactory; //MemberFactory 생성자 주입 + + public AuthService(MemberFactory memberFactory) { + this.memberFactory = memberFactory; + } + + public Member signUp(String type, Map userData) throws Exception { + Member member = memberFactory.createMember(type,userData); //Factory 통해 Member 생성 + member.register(); + return member; + } +} + +//컨트롤러 +public class Controller { + public static void main(String[] args) throws Exception { + AuthService authService = new AuthService(new MemberFactory()); //기존 코드에서 MemberFactory를 주입해주도록 수정 + Member member = authService.signUp("p", Map.of("name","홍길동","phoneNumber","010-1111-1111","email","example@example.com")); + System.out.println(member); + } +} +``` +객체 생성로직을 Factory로 분리하여 공통 로직을 한곳에 모았기 때문에 여러 메서드를 찾아 돌아다니면서 case를 추가해주어야 하는 문제점은 해결이 되었다. 하지만, 회원의 종류가 추가될때 Factory의 클래스가 변경이 되어야 하는 문제점은 그대로이다. + +이처럼 단순히 Factory를 추가해주는 방법은 UML 다이어그램과 설계 형태가 다르듯이 Factory Method 패턴이 아니라 단순한 Factory 패턴이다. + +이를 Factory Method패턴을 이용해서 리팩토링해보자. + +```java +//Product +//기존에 존재하던 Member 인터페이스는 UML 다이어그램의 Product 역할 +public interface Member { + public void validateIdentity(); + public void register() throws Exception; + public String toString(); +} + +//Member의 구현체로 Concrete Product 역할 +public class PersonalMember implements Member{ + private String name; + private String phoneNumber; + private String email; + + public PersonalMember(String name, String phoneNumber, String email) { + this.name = name; + this.phoneNumber = phoneNumber; + this.email = email; + } + + @Override + public void validateIdentity() { + System.out.println("일반 회원 property validate"); + } + + @Override + public void register() throws InterruptedException { + System.out.println("일반 회원 회원가입 로직 수행"); + Thread.sleep(1); + System.out.println("일반 회원 회원가입"); + } + + @Override + public String toString() { + return "PersonalMember{" + + "name='" + name + '\'' + + ", phoneNumber='" + phoneNumber + '\'' + + ", email='" + email + '\'' + + '}'; + } +} + +//Member의 구현체로 Concrete Product 역할 +public class CompanyMember implements Member{ + private String csn; + private String name; + private boolean isPaid; + + public CompanyMember(String csn, String name, boolean isPaid) { + this.csn = csn; + this.name = name; + this.isPaid = isPaid; + } + + @Override + public void validateIdentity() { + System.out.println("기업 회원 property validate"); + } + + @Override + public void register() throws InterruptedException { + System.out.println("기업 회원 회원가입 로직 수행 ..."); + Thread.sleep(1); + System.out.println("기업 회원 회원가입 완료"); + } + + @Override + public String toString() { + return "CompanyMember{" + + "csn='" + csn + '\'' + + ", name='" + name + '\'' + + ", isPaid=" + isPaid + + '}'; + } +} + +//생성했던 Factory를 수정하여 회원객체 생성은 하위클래스에게 위임하고 validate와 같은 공통 로직을 수행후에 최종적으로 Member를 생성하도록 수정 +//UML다이어그램의 Creator역할 +public abstract class MemberFactory { + public Member createMember(Map userData) throws Exception{ + Member member = createFromMap(userData); //하위 객체에게 생성 위임 + member.validateIdentity(); //공통 로직 수행 + return member; //최종 객체 반환 + } + + protected abstract Member createFromMap(Map userData); //Factory Method + //하위객체에게 이 메서드를 통해 생성위임 +} + +//Concrete Creator +//일반회원 생성 Factory +public class PersonalMemberFactory extends MemberFactory{ + protected Member createFromMap(Map userData) { + return new PersonalMember((String)userData.get("name"),(String)userData.get("phoneNumber"),(String)userData.get("email")); + } +} + +//Concrete Creator +//기업회원 생성 Factory +public class CompanyMemberFactory extends MemberFactory{ + protected Member createFromMap(Map userData) { + return new CompanyMember((String)userData.get("csn"),(String)userData.get("name"),false); + } +} + +//인증 Service 계층 +//상세 객체 생성역할을 Factory에게 위임했으므로 signUp에서 type input 제거 +public class AuthService { + private MemberFactory memberFactory; + + public AuthService(MemberFactory memberFactory) { + this.memberFactory = memberFactory; + } + + //기존의 메서드에서 type 매개인자 제거 + public Member signUp(Map userData) throws Exception { + Member member = memberFactory.createMember(userData); + member.register(); + return member; + } +} + +//Client +public class Controller { + public static void main(String[] args) throws Exception { + AuthService authService = new AuthService(new PersonalMemberFactory()); //Service를 생성할때 ConcreteFactory를 주입해줌으로써 특정 Member구현체 생성 + + Member member = authService.signUp(Map.of("name","홍길동","phoneNumber","010-1111-1111","email","example@example.com")); + System.out.println(member); + } +} +``` +이제 새로운 회원종류인 `ManageMentMember`를 추가하고자 한다면 `ManageMentMember` 클래스를 추가해주고 MemberFactory를 상속하여 ManageMentMember를 반환하는 Factory를 추가해주면 기존에 존재하는 `코드의 수정없이` 확장이 가능해졌다. \ No newline at end of file From cfdac079784ff5e396f8f293f5756a7f82180980 Mon Sep 17 00:00:00 2001 From: EuiSung <52964858+gowoonsori@users.noreply.github.com> Date: Wed, 2 Mar 2022 22:52:57 +0900 Subject: [PATCH 2/3] =?UTF-8?q?Fix:=20=ED=8C=8C=EC=9D=BC=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../summary/code.md" | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename "\352\260\235\354\262\264\354\203\235\354\204\261/2\354\243\274\354\260\250-\355\214\251\355\206\240\353\246\254\353\251\224\354\206\214\353\223\234/code.md" => "\352\260\235\354\262\264\354\203\235\354\204\261/2\354\243\274\354\260\250-\355\214\251\355\206\240\353\246\254\353\251\224\354\206\214\353\223\234/summary/code.md" (100%) diff --git "a/\352\260\235\354\262\264\354\203\235\354\204\261/2\354\243\274\354\260\250-\355\214\251\355\206\240\353\246\254\353\251\224\354\206\214\353\223\234/code.md" "b/\352\260\235\354\262\264\354\203\235\354\204\261/2\354\243\274\354\260\250-\355\214\251\355\206\240\353\246\254\353\251\224\354\206\214\353\223\234/summary/code.md" similarity index 100% rename from "\352\260\235\354\262\264\354\203\235\354\204\261/2\354\243\274\354\260\250-\355\214\251\355\206\240\353\246\254\353\251\224\354\206\214\353\223\234/code.md" rename to "\352\260\235\354\262\264\354\203\235\354\204\261/2\354\243\274\354\260\250-\355\214\251\355\206\240\353\246\254\353\251\224\354\206\214\353\223\234/summary/code.md" From e70f2690cd87c63dc593852f29909ed021efb054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=EC=84=AD?= Date: Fri, 4 Mar 2022 08:56:16 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[Add]=20=ED=8C=A9=ED=86=A0=EB=A6=AC=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=A0=95=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...4 \353\251\224\354\206\214\353\223\234.md" | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 "\352\260\235\354\262\264\354\203\235\354\204\261/2\354\243\274\354\260\250-\355\214\251\355\206\240\353\246\254\353\251\224\354\206\214\353\223\234/summary/\355\214\251\355\206\240\353\246\254 \353\251\224\354\206\214\353\223\234.md" diff --git "a/\352\260\235\354\262\264\354\203\235\354\204\261/2\354\243\274\354\260\250-\355\214\251\355\206\240\353\246\254\353\251\224\354\206\214\353\223\234/summary/\355\214\251\355\206\240\353\246\254 \353\251\224\354\206\214\353\223\234.md" "b/\352\260\235\354\262\264\354\203\235\354\204\261/2\354\243\274\354\260\250-\355\214\251\355\206\240\353\246\254\353\251\224\354\206\214\353\223\234/summary/\355\214\251\355\206\240\353\246\254 \353\251\224\354\206\214\353\223\234.md" new file mode 100644 index 0000000..c33e148 --- /dev/null +++ "b/\352\260\235\354\262\264\354\203\235\354\204\261/2\354\243\274\354\260\250-\355\214\251\355\206\240\353\246\254\353\251\224\354\206\214\353\223\234/summary/\355\214\251\355\206\240\353\246\254 \353\251\224\354\206\214\353\223\234.md" @@ -0,0 +1,43 @@ +# 팩토리 메소드 + +> 다양한 구현체 (Product)가 있고, 그 중에서 특정한 구현체를 만들 수 있는 다양한 팩토리(Creator)를 제공할 수 있다. + +![](https://user-images.githubusercontent.com/42997924/144359079-e4f23b8c-d345-49b8-85a5-fd65a1e180ab.png) + +부모(상위) 클래스에 알려지지 않은 **구체적 클래스를 생성하는 패턴**이며. 자식(하위) 클래스가 어떤 객체를 **생성할지를 결정하도록 하는 패턴**이기도 하다. + +## 팩토리 메소드는 언제 사용되는가? + +- 생성할 객체 타입을 예측할 수 없을 때 +- **생성할 객체를 기술하는 책임을 서브클래스에게 정의**하고자 할 때 +- 객체 생성의 책임을 서브클래스에 위임시키고 **서브클래스에 대한 정보를 은닉**하고자 할 때 + +## 예제 코드 + +//언제 사용되는지를 가지고 예제 코드를 설명 해줘야 함. + +## 패턴의 장/단점 + +장점: + +- creator 그리고 concreteProduct 간의 긴밀한 복잡도를 피할 수 있다. +- 제품 생성 코드를 프로그램 한 곳으로 이동시켜서 만드는 코드에 대해서 쉽게 유지 보수가 가능해진다. SRP(단일 책임 원칙)의 역할을 잘지킨 Case +- 기존 클라이언트 코드를 건들지 않고도 새로운 코드를 추가가능해진다. OCP(개방-폐쇄 원칙)를 잘 지킨 Case + +단점: + +- Code 자체의 복잡도가 증가한다. 이패턴을 적용하기위해서는 서브 클래스를 많이 도입할 수 밖에 없기 때문에, 가장 베스트 케이스인 기존 Creator 클래스 계층에 이 패턴을 사용하는 경우가 아니라면, 많이 복잡해질 수 있다. + +## 비슷한 패턴 + +- **추상 팩토리 패턴, 프로토 타입 패턴, 빌더패턴**이 더 유연하고, 더 복잡한 방식으로 팩토리 메소드 패턴에서 발전한 패턴이다. + +- **추상 팩토리** 패턴은 팩토리 메소드의 집합을 기반으로 되어있다. 비슷하게 프로토타입의 경우는 이런 클래스들의 메소드들을 구성해서 사용해야 한다. + +- **이터레이터** 패턴과 함께 팩토리 메소드 패턴을 사용하면, Collection들과 호환되는 여러 유형의 다른 이터레이터들을 반환할 수 있도록할 수 있다. + +- **프로토 타입** 패턴은 상속을 기반으로 하지 않아서, 결점이 없으나 프로토타입 패턴은 복사된 객체의 복잡한 초기화가 필요하다. 반면, 팩토리 메소드 패턴의 경우는 상속기반이지만, 복잡한 초기화는 필요 없다. + +- **템플릿 메소드 패턴**은 팩토리 메소드 패턴에서 분화되어서 나왔으며, 큰 탬플릿 메소드의 단계적 역할을 팩토리 메소드가 맡을 수 있다. + +