diff --git "a/\352\265\254\354\241\260/4\354\243\274\354\260\250-\353\270\214\353\246\277\354\247\200/summary/example-code.md" "b/\352\265\254\354\241\260/4\354\243\274\354\260\250-\353\270\214\353\246\277\354\247\200/summary/example-code.md" new file mode 100644 index 0000000..e83f7ff --- /dev/null +++ "b/\352\265\254\354\241\260/4\354\243\274\354\260\250-\353\270\214\353\246\277\354\247\200/summary/example-code.md" @@ -0,0 +1,264 @@ +# 브릿지 패턴 예제 + +예제 코드 요구사항 + +- 기존에 채용 공고 페이지에 카카오맵 API를 이용한 회사 위치가 표현되고 있음 +- 하지만 카카오맵 API는 매일 18:00~20:00에 멈춤 현상이 있음 +- 대안으로 네이버 지도 API를 이용한 회사 위치 표현이 필요함 + +아래 코드는 기존 카카오맵 API만 적용됐을 때 코드라 가정하겠습니다. + +```java +/** + * 카카오맵 API + */ +public class KakaoMapAPI { + public void drawMap(String address) { + System.out.printf("카카오 맵에 주소 %s를 표현한다.\n", address); + } +} + +/** + * 채용 공고 페이지 클래스 + */ +public class RecruitPage { + //회사 주소 + private String address; + //카카오맵 API + private final KakaoMapAPI kakaoMapAPI = new KakaoMapAPI(); + + public RecruitPage(String address) { + this.address = address; + } + + //카카오맵에 표현 + public void drawMap() { + kakaoMapAPI.drawMap(address); + } +} + +public class Main { + public static void main(String[] args) { + RecruitPage recruitPage = new RecruitPage("서울시 구로구 디지털로 34길 43"); + //지도에 표현 + recruitPage.drawMap(); + } +} + +결과 +카카오 맵에 주소 서울시 구로구 디지털로 34길 43를 표현한다. +``` + +여기에 네이버맵 API를 적용한다 가정하고 일반적인 방법으로 구현해보면 다음처럼 구현할 수 있습니다. + +```java +/** + * 카카오맵 API + */ +public class KakaoMapAPI { + //카카오맵에 표현 + public void drawMap(String address) { + System.out.printf("카카오 맵에 주소 %s를 표현한다.\n", address); + } +} + +/** + * 네이버 지도 API + */ +public class NaverMapAPI { + private String address; + + //주소 저장 + public void setAddress(String address) { + this.address = address; + } + + //네이버 지도에 표현 + public void draw() { + System.out.printf("네이버 지도에 주소 %s를 표현합니다.\n", this.address); + } +} + +/** + * 채용 공고 페이지 클래스 + */ +public class RecruitPage { + //회사 주소 + private String address; + //카카오맵 API + private final KakaoMapAPI kakaoMapAPI = new KakaoMapAPI(); + //네이버 지도 API + private final NaverMapAPI naverMapAPI = new NaverMapAPI(); + + public RecruitPage(String address) { + //주소 저장 + this.address = address; + } + + //인자에 따라 카카오맵, 네이버 지도를 분기처리 + public void drawCompanyMap(String api) { + if(api.equals("naver")) { + naverMapAPI.setAddress(address); + naverMapAPI.draw(); + } else if(api.equals("kakao")) { + kakaoMapAPI.drawMap(address); + } + } +} + +public class Main { + public static void main(String[] args) { + RecruitPage recruitPage = new RecruitPage("서울시 구로구 디지털로 34길 43"); + //카카오맵 정상 작동 + recruitPage.drawCompanyMap("kakao"); + //카카오맵 멈춤 -> 네이버 지도 호출 + recruitPage.drawCompanyMap("naver"); + } +} + +결과 +카카오 맵에 주소 서울시 구로구 디지털로 34길 43를 표현한다. +네이버 지도에 주소 서울시 구로구 디지털로 34길 43를 표현합니다. +``` + +위 코드를 보면 기존 `RecruitPage` 클래스에 `NaverMapAPI` 클래스 객체를 새로 만든 것을 확인할 수 있다. 그리고 이로 인해 기존 코드 `drawMap()`가 변경됐다. 그렇다면 이후에 또 다른 API를 추가할 때마다 기존 코드가 변경되는 상황이 발생한다. + +즉 `OCP(Open/Closed Principle)`에 위배되는 상황이다. + +이제 브릿지 패턴을 적용해보자. 아래는 예제 코드의 다이어그램이다. + +![Untitled](https://user-images.githubusercontent.com/32676275/156580823-32e4d2e9-fb7a-4e0f-88b3-17f5942fce0e.png) + +```java +/** + * 카카오맵 API + */ +public class KakaoMapAPI { + //맵을 그리는 API 기능 + public void drawMap(String address) { + System.out.printf("카카오 맵에 주소 %s를 표현한다.\n", address); + } +} + +/** + * 네이버 지도 API + */ +public class NaverMapAPI { + //주소 + private String address; + + //주소 저장 + public void setAddress(String address) { + this.address = address; + } + + //맵을 그리는 API 기능 + public void draw() { + System.out.printf("네이버 지도에 주소 %s를 표현합니다.\n", this.address); + } +} + +/** + * 채용 공고에 보여줄 맵을 구현할 인터페이스 + * ** Implementor ** + */ +public interface Map { + //맵을 그리는 기능을 하는 메소드 + public void drawMap(String address); +} + +/** + * 카카오 맵 실제 구현부 + * ** ConcreteImplementor ** + */ +public class KakaoMap implements Map{ + //카카오맵 API를 사용 + private final KakaoMapAPI kakaoMapAPI = new KakaoMapAPI(); + + //맵을 그리는 기능 + @Override + public void drawMap(String address) { + //카카오맵 사용 방법대로 drawMap() 호출 + kakaoMapAPI.drawMap(address); + } +} + +/** + * 네이버 지도 실제 구현체 + * ** ConcreteImplementor ** + */ +public class NaverMap implements Map{ + //네이버 지도 API를 사용 + private final NaverMapAPI naverMapAPI = new NaverMapAPI(); + + //맵을 그리는 기능 + @Override + public void drawMap(String address) { + //네이버 지도 사용 방법대로 setter() 다음 draw() 호출 + naverMapAPI.setAddress(address); + naverMapAPI.draw(); + } +} + +/** + * 지도 API를 사용할 페이지 추상 클래스 + * ** Abstraction ** + */ +public abstract class Page { + protected String address; + protected Map map; //Map(* Implementor *) 객체를 가지고 있음 + + public Page(String address, Map map) { + this.address = address; + this.map = map; + } + + //기업 위치를 맵에 나타낼 메소드 + public abstract void drawCompanyMap(); +} + +/** + * 채용 공고 페이지 클래스 + * ** RefinedAbstraction ** + */ +public class RecruitPage extends Page { + + //생성자 + public RecruitPage(String address, Map map) { + super(address, map); + } + + //기업 위치를 맵에 나타낼 메소드 + @Override + public void drawCompanyMap() { + //각 Map 구현체의 메소드를 수행 + this.map.drawMap(this.address); + } +} + +//메인 클래스 +public class Main { + public static void main(String[] args) { + String address = "서울시 구로구 디지털로 34길 43"; + KakaoMap kakaoMap = new KakaoMap(); + NaverMap naverMap = new NaverMap(); + + //사용할 맵 API를 외부에서 주입해서 사용 + Page recruitPage1 = new RecruitPage(address, kakaoMap); + recruitPage1.drawCompanyMap(); + + Page recruitPage2 = new RecruitPage(address, naverMap); + recruitPage2.drawCompanyMap(); + + /* + 추후 다른 맵API를 사용해도 + Map구현체(KakaoMap, NaverMap 등)를 만들어 주입해주면 된다 + 확장 과정에서 기존 코드가 수정되지 않기 때문에 + OCP(Open/Closed Principle)를 만족하게 된다 + */ + } +} +결과 +카카오 맵에 주소 서울시 구로구 디지털로 34길 43를 표현한다. +네이버 지도에 주소 서울시 구로구 디지털로 34길 43를 표현합니다. +``` \ No newline at end of file diff --git "a/\352\265\254\354\241\260/4\354\243\274\354\260\250-\353\270\214\353\246\277\354\247\200/summary/\353\270\214\353\246\277\354\247\200 \355\214\250\355\204\264.md" "b/\352\265\254\354\241\260/4\354\243\274\354\260\250-\353\270\214\353\246\277\354\247\200/summary/\353\270\214\353\246\277\354\247\200 \355\214\250\355\204\264.md" new file mode 100644 index 0000000..1d39ac6 --- /dev/null +++ "b/\352\265\254\354\241\260/4\354\243\274\354\260\250-\353\270\214\353\246\277\354\247\200/summary/\353\270\214\353\246\277\354\247\200 \355\214\250\355\204\264.md" @@ -0,0 +1,327 @@ +# 브릿지 패턴 + +> **`추상`적인 것과 `구체`적인 것을 분리하여 연결하는 패턴** + +![https://www.notion.so/Users/LeeChnagSup/Library/Application%20Support/marktext/images/2022-03-01-17-45-58-image.png](https://upload.wikimedia.org/wikipedia/commons/thumb/c/cf/Bridge_UML_class_diagram.svg/750px-Bridge_UML_class_diagram.svg.png) + +밀접하게 관련된 클래스 집합을 서로 독립적으로 그리고 두 개별 계층(추상적인 파트와 구체적인 파트)으로 나눠서 서로 분할 할 수 있는 구조로 만들 수 있는 패턴 + +위의 도식에서 각 명칭 용어를 정리하자면 다음과 같다. + +`Abstraction` : 기능 계층의 최상위 클래스이며 추상 인터페이스 + +`RefinedAbstraction` : 기능 계층에서 새로운 부분을 확장할 클래스 + +`Implementor` : `Abstraction`의 기능을 구현하기 위한 인터페이스 정의 + +`ConcreteImplementor` : 실제 기능 구현 클래스 + +## 브릿지 패턴은 언제 사용되는가? + +3D 모양을 구현한 객체가 있다고 하자. 색과 3D모양이 두가지씩(구와 사면체, 빨간색과 파란색) 존재할때는 아래처럼 형태를 구성할 수 있다. + +![](https://refactoring.guru/images/patterns/diagrams/bridge/problem-en.png) + +위처럼 구성시 빨간구체, 빨간 사면체, 파란 구체, 파랑 사면체 4개의 클래스가 구성된다. + +**문제는 우리는 요구사항이 추가 될수 있다는 점이다.** + +*여기에 만약에.. 삼각뿔을 추가한다면? 초록색을 추가한다면?* 코드가 반복돼 복잡도가 증가한다. + +이것을 추상적으로 `모양` 과 `색깔` 로 관리하면 삼각뿔과 초록색 추가가 용이해진다. + +![](https://refactoring.guru/images/patterns/diagrams/bridge/solution-en.png) + +위 구조에서 `Color`에 `Green`을 `Shape`에는 `Triangle`을 추가한 상태에서 `Green`과 `Triangle`을 연결해서 사용하면 된다. + +예제 코드를 살펴보자. + +## 브릿지 패턴 예제 코드 + +예제 코드 요구사항 + +- 기존 채용 공고 페이지에 카카오맵 API를 이용한 회사 위치 표시 +- 매일 18:00~20:00 카카오맵 API 멈춤 현상 발생 +- 대안으로 네이버 지도 API 도입 + +아래 코드는 카카오맵API만 적용됐을 때라 가정하자. + +```java +/** + * 카카오맵 API + */ +public class KakaoMapAPI { + public void drawMap(String address) { + System.out.printf("카카오 맵에 주소 %s를 표현한다.\n", address); + } +} + +/** + * 채용 공고 페이지 클래스 + */ +public class RecruitPage { + //회사 주소 + private String address; + //카카오맵 API + private final KakaoMapAPI kakaoMapAPI = new KakaoMapAPI(); + + public RecruitPage(String address) { + this.address = address; + } + + //카카오맵에 표현 + public void drawMap() { + kakaoMapAPI.drawMap(address); + } +} + +public class Main { + public static void main(String[] args) { + RecruitPage recruitPage = new RecruitPage("서울시 구로구 디지털로 34길 43"); + //지도에 표현 + recruitPage.drawMap(); + } +} + +결과 +카카오 맵에 주소 서울시 구로구 디지털로 34길 43를 표현한다. +``` + +이제 네이버 지도 API를 적용하면 다음처럼 구현할 수 있다. + +```java +/** + * 카카오맵 API + */ +public class KakaoMapAPI { + //카카오맵에 표현 + public void drawMap(String address) { + System.out.printf("카카오 맵에 주소 %s를 표현한다.\n", address); + } +} + +/** + * 네이버 지도 API + */ +public class NaverMapAPI { + private String address; + + //주소 저장 + public void setAddress(String address) { + this.address = address; + } + + //네이버 지도에 표현 + public void draw() { + System.out.printf("네이버 지도에 주소 %s를 표현합니다.\n", this.address); + } +} + +/** + * 채용 공고 페이지 클래스 + */ +public class RecruitPage { + //회사 주소 + private String address; + //카카오맵 API + private final KakaoMapAPI kakaoMapAPI = new KakaoMapAPI(); + //네이버 지도 API + private final NaverMapAPI naverMapAPI = new NaverMapAPI(); + + public RecruitPage(String address) { + //주소 저장 + this.address = address; + } + + //인자에 따라 카카오맵, 네이버 지도를 분기처리 + public void drawCompanyMap(String api) { + if(api.equals("naver")) { + naverMapAPI.setAddress(address); + naverMapAPI.draw(); + } else if(api.equals("kakao")) { + kakaoMapAPI.drawMap(address); + } + } +} + +public class Main { + public static void main(String[] args) { + RecruitPage recruitPage = new RecruitPage("서울시 구로구 디지털로 34길 43"); + //카카오맵 정상 작동 + recruitPage.drawCompanyMap("kakao"); + //카카오맵 멈춤 -> 네이버 지도 호출 + recruitPage.drawCompanyMap("naver"); + } +} + +결과 +카카오 맵에 주소 서울시 구로구 디지털로 34길 43를 표현한다. +네이버 지도에 주소 서울시 구로구 디지털로 34길 43를 표현합니다. +``` + +위 예제에서 `RecruitPage` 클래스에 `NaverMapAPI` 클래스 객체가 새롭게 선언됐고 기존 `drawMap()`가 변경됐다. + +만약 이후에 API 추가되면 기존 코드가 또 변경될 것이다. + +즉 `OCP(Open/Closed Principle)`에 위배되는 상황이다. + +이제 브릿지 패턴을 적용해보자. 아래는 예제의 클래스 다이어그램이다. + +![Untitled](https://user-images.githubusercontent.com/32676275/156580823-32e4d2e9-fb7a-4e0f-88b3-17f5942fce0e.png) + +```java +/** + * 카카오맵 API + */ +public class KakaoMapAPI { + //맵을 그리는 API 기능 + public void drawMap(String address) { + System.out.printf("카카오 맵에 주소 %s를 표현한다.\n", address); + } +} + +/** + * 네이버 지도 API + */ +public class NaverMapAPI { + //주소 + private String address; + + //주소 저장 + public void setAddress(String address) { + this.address = address; + } + + //맵을 그리는 API 기능 + public void draw() { + System.out.printf("네이버 지도에 주소 %s를 표현합니다.\n", this.address); + } +} + +/** + * 채용 공고에 보여줄 맵을 구현할 인터페이스 + * ** Implementor ** + */ +public interface Map { + //맵을 그리는 기능을 하는 메소드 + public void drawMap(String address); +} + +/** + * 카카오 맵 실제 구현부 + * ** ConcreteImplementor ** + */ +public class KakaoMap implements Map{ + //카카오맵 API를 사용 + private final KakaoMapAPI kakaoMapAPI = new KakaoMapAPI(); + + //맵을 그리는 기능 + @Override + public void drawMap(String address) { + //카카오맵 사용 방법대로 drawMap() 호출 + kakaoMapAPI.drawMap(address); + } +} + +/** + * 네이버 지도 실제 구현체 + * ** ConcreteImplementor ** + */ +public class NaverMap implements Map{ + //네이버 지도 API를 사용 + private final NaverMapAPI naverMapAPI = new NaverMapAPI(); + + //맵을 그리는 기능 + @Override + public void drawMap(String address) { + //네이버 지도 사용 방법대로 setter() 다음 draw() 호출 + naverMapAPI.setAddress(address); + naverMapAPI.draw(); + } +} + +/** + * 지도 API를 사용할 페이지 추상 클래스 + * ** Abstraction ** + */ +public abstract class Page { + protected String address; + protected Map map; //Map(* Implementor *) 객체를 가지고 있음 + + public Page(String address, Map map) { + this.address = address; + this.map = map; + } + + //기업 위치를 맵에 나타낼 메소드 + public abstract void drawCompanyMap(); +} + +/** + * 채용 공고 페이지 클래스 + * ** RefinedAbstraction ** + */ +public class RecruitPage extends Page { + + //생성자 + public RecruitPage(String address, Map map) { + super(address, map); + } + + //기업 위치를 맵에 나타낼 메소드 + @Override + public void drawCompanyMap() { + //각 Map 구현체의 메소드를 수행 + this.map.drawMap(this.address); + } +} + +//메인 클래스 +public class Main { + public static void main(String[] args) { + String address = "서울시 구로구 디지털로 34길 43"; + KakaoMap kakaoMap = new KakaoMap(); + NaverMap naverMap = new NaverMap(); + + //사용할 맵 API를 외부에서 주입해서 사용 + Page recruitPage1 = new RecruitPage(address, kakaoMap); + recruitPage1.drawCompanyMap(); + + Page recruitPage2 = new RecruitPage(address, naverMap); + recruitPage2.drawCompanyMap(); + + /* + 추후 다른 맵API를 사용해도 + Map구현체(KakaoMap, NaverMap 등)를 만들어 주입해주면 된다 + 확장 과정에서 기존 코드가 수정되지 않기 때문에 + OCP(Open/Closed Principle)를 만족하게 된다 + */ + } +} +결과 +카카오 맵에 주소 서울시 구로구 디지털로 34길 43를 표현한다. +네이버 지도에 주소 서울시 구로구 디지털로 34길 43를 표현합니다. +``` + +## 패턴의 장/단점 + +장점: + +- 플렛폼 독립적인 클래스나, 앱을 만들 수 있다. +- 클라이언트 코드가 상당히 높은 수준의 추상화로 작동한다. +- OCP(개방-폐쇄 원칙)을 지킨다. 새로운 추상화와 구현을 서로 독립적으로 도입할 수 있다. +- SRP(단일 책임 원칙)을 지킨다. 추상화의 고급 논리와 플렛폼의 세부적인 정보에 초점을 맞출 수 있다. + +단점: + +- 패턴을 매우 응집력 있는 클래스에 적용하게 되면, 코드가 엄청 복잡해질 수 있다. + +## 비슷한 패턴 + +- **브릿지** 패턴은 응용 프로그램의 일부를 서로 독립적으로 개발할 수 있게 끔 설계하지만, 반면 **어뎁터** 패턴은 호환되지 않는 일부 클래스와 함께 잘 작동하도록 기존 앱과 함께 사용된다. + +- **브릿지**, **상태**, **전략**, **어뎁터 패턴의** 어떤 측면에서는 구조적으로 비슷한 형태를 가지고 있다. **실제로 이 모든 패턴은 다른 대상에 작업을 위임하는 형태로 되어있다.** 각각의 패턴은 모두 다른 문제를 해결하는 방식은 다릅니다. 이러한 패턴들은 결국 특정한 방식으로 구조화해서 해결하는 방법은 아니다. + +- 브릿지패턴은 **추상 팩토리** 패턴을 사용할 수 있다. 이 두 패턴을 동시에 사용하기 위해서는 브릿지에 의해 구성된 추상화 특별한 구현에서만 작동할 수 있을 때 유용하다. 이 경우에는 **추상 팩토리**는 이려한 연결들을 캡슐화 할 수 있고, 클라이언트 코드로부터 복잡도를 숨기는 효과를 가지고 있다. + +- 브릿지와 함께 **빌더** 패턴도 혼합할 수 있다. Director 클래스가 추상화의 역할을 맡고 다른 빌더들이 구현부를 맡아주면된다.