반응형
Adapter Pattern
- 어댑터 패턴이란?
- 호환성이 없는 인터페이스 때문에 함께 동작할 수 없는 클래스들을 어댑터를 통해 함께 작동할 수 있도록 변환해주는 구조 패턴
- 이미 구축되어 있는 것을 새로운 어떤 것에 사용하려고 할 때 양 쪽 간의 호환성을 유지해주기 위해 사용
- 이미 구축되어 있는 것을 새로운 어떤 것에 사용하려고 할 때 양 쪽 간의 호환성을 유지해주기 위해 사용
- 호환성이 없는 인터페이스 때문에 함께 동작할 수 없는 클래스들을 어댑터를 통해 함께 작동할 수 있도록 변환해주는 구조 패턴
- 어댑터 패턴이 필요한 상황 예시
- 주식 시장 모니터링 앱을 만드는데 여러 소스에서 주식 데이터를 XML 형식으로 다운로드 해서 보여준다고 가정
- 어느 시점에 타사의 스마트 분석 라이브러리를 통합하여 개선하려고 했는데 이 라이브러리가 JSON 형식으로 입력을 받음
- 이런 경우 XML 에서 JSON 으로 포맷을 변경해주는 어댑터를 만들어주면 서로 연동 가능해짐
- 어댑터를 통해서만 분석 라이브러리와 통신하도록 함
- 어댑터를 통해서만 분석 라이브러리와 통신하도록 함
- 어댑터 패턴의 구조 (객체 어댑터 구조)
- 어댑터 패턴의 구조 (객체 어댑터 구조) - 구현 예시
class Service{ void specificMethod(int specialData){ System.out.println("기존 서비스 기능 호출 +" + specialData); } } interface Target{ void method(int data); } class Adapter implements Target{ Service adaptee; Adapter(Service adaptee){ this.adaptee = adaptee; } public void method(int data){ adaptee.specificMethod(data); // 위임 } } class Client{ public static void main(String[] args){ Target adapter = new Adapter(new Service()); adapter.method(1); } }
- 어댑터 패턴의 구조 (클래스 어댑터 구조)
- 다중 상속을 지원하는 C++ 같은 언어에서 활용할 수 있음
- 다중 상속을 지원하는 C++ 같은 언어에서 활용할 수 있음
- 어댑터 패턴의 구조 (클래스 어댑터 구조) - 구현 예시
public class Service{ void specificMethod(int specialData){ System.out.println("기존 서비스 기능 호출+ " + specialData); } } public interface Target{ void method(int data); } public class Adapter extends Service implements Target{ @Override public void method(int data){ specificMethod(data); } } public class Client{ public static void main(String[] args){ Target adapter = new Adapter(); adapter.method(1); } }
- 어댑터 패턴 예시
- 어댑터 패턴 예시 구현
// 둥근 구멍 class RoundHole { private int radius; public RoundHole(int radius) { this.radius = radius; } public int getRadius() { return radius; } public boolean fits(RoundPeg peg) { return peg.getRadius() <= this.radius; } } // 둥근 Peg class RoundPeg { private int radius; public RoundPeg(int radius) { this.radius = radius; } public int getRadius() { return radius; } } // 사각형 Peg class SquarePeg { private int width; public SquarePeg(int width) { this.width = width; } public int getWidth() { return width; } } // 어댑터 class SquarePegAdapter extends RoundPeg { private SquarePeg squarePeg; public SquarePegAdapter(SquarePeg squarePeg) { super(0); // RoundPeg 생성자 호출, 실제 radius는 계산됨 this.squarePeg = squarePeg; } @Override public int getRadius() { return (int) (squarePeg.getWidth() * Math.sqrt(2) / 2); } } // 실행 코드 public class Main { public static void main(String[] args) { RoundHole hole = new RoundHole(5); RoundPeg roundPeg = new RoundPeg(5); System.out.println(hole.fits(roundPeg)); // true SquarePeg smallSquarePeg = new SquarePeg(5); SquarePeg largeSquarePeg = new SquarePeg(10); // 어댑터를 사용 SquarePegAdapter smallSquareAdapter = new SquarePegAdapter(smallSquarePeg); SquarePegAdapter largeSquareAdapter = new SquarePegAdapter(largeSquarePeg); System.out.println(hole.fits(smallSquareAdapter)); // true System.out.println(hole.fits(largeSquareAdapter)); // false } }
- 어댑터 패턴 사용 기기
- 새로운 인터페이스가 레거시와 호환되지 않을 때
- 이미 만든 것을 재사용하고자 하나 수정은 하고 싶지 않을 때
- 소프트웨어의 구 버전과 신 버전을 공존 시키고 싶을 때
- 사용 사례
- java.util.Arrays 와 java.io.inputStreamReader
- java.util.Arrays 와 java.io.inputStreamReader
- 장점
- SRP 준수
- 프로그램의 기본 비즈니스 로직에서 인터페이스를 분리할 수 있기 때문
- OCP 준수
- 기존 클래스 코드를 건들이지 않고 클라이언트 인터페이스를 통해 어댑터와 작동하기 때문
- 추가로 필요한 메소드가 있다면 어댑터로 빠르게 구현 가능
- 버그가 발생해도 기존의 클래스에는 버그가 없으므로 어댑터만 중점적으로 조사
- SRP 준수
- 단점
- 새로운 인터페이스와 어댑터 클래스 세트를 도입해야 해서 복잡성 증가
- 때로는 서비스 (adaptee) 클래스를 변경하는 것이 간단할 수도 있음
Bridge Pattern
- 브릿지 패턴이란?
- 큰 클래스 또는 밀접하게 관련된 클래스들의 집합을 두 개의 개별 계층 구조로 나눈 후 각각 독립적으로 개발 할 수 있도록 하는 구조 패턴
- Shape 과 Color 의 조합을 가지는 클래스 구성에서는 새로운 모양과 색상 유형이 추가될 때마다 계층 구조는 기하급수적으로 늘어남
- Shape 과 Color 의 조합을 가지는 클래스 구성에서는 새로운 모양과 색상 유형이 추가될 때마다 계층 구조는 기하급수적으로 늘어남
- 큰 클래스 또는 밀접하게 관련된 클래스들의 집합을 두 개의 개별 계층 구조로 나눈 후 각각 독립적으로 개발 할 수 있도록 하는 구조 패턴
- 브릿지 패턴의 해결 방법
- 모양과 색상 두 가지의 독립적인 차원에서 클래스를 상속 구조로 확장하려고 하기 때문에 발생
- 상속으로 하지 말고 포함 관계로 전환하여 차원 중 하나를 별도의 클래스 계층 구조로 추출하고 포함될 수 있도록 구조를 변경
- Shape(모양) 클래스는 색상 객체를 가리키는 reference 필드를 가짐 (연결된 색상 객체에 모든 색상 관련 작업을 위임할 수 있음 - 이 연결을 브릿지라 함)
- 모양과 색상 두 가지의 독립적인 차원에서 클래스를 상속 구조로 확장하려고 하기 때문에 발생
- 브릿지 패턴에서 역할 구분
- 구조 관점에서 Abstraction 과 Implementation 의 역할이 있음
- Abstraction : 일부 개체에 대한 상위 수준의 제어/기능 레이어
- 자체적으로 실제 작업을 수행하는 것은 아니고, 작업들은 구현 레이어에 위임해야 함
- Implementation : 각 기능에 대한 구현부를 담당하는 레이어
- Abstraction : 일부 개체에 대한 상위 수준의 제어/기능 레이어
- Abstraction 과 Implementation 을 분리하고 브릿지로 연결하여 변화 대응에 독립적으로 확장될 수 있음
- 구조 관점에서 Abstraction 과 Implementation 의 역할이 있음
- 브릿지 패턴에서 역할 구분
- 예시
- Abstraction : 앱의 그래픽 사용자 인터페이스 레이어
- Implementation : 운영 체제의 API
- 예시
- 브릿지 패턴의 구조
- 브릿지 패턴 - Implementation
public interface Device { int getVolume(); void setVolume(int percent); } class TV implements Device { private int volume = 30; public int getVolume() { return this.volume; } public void setVolume(int percent) { // ..... } } class Radio implements Device { private int volume = 30; public int getVolume() { return this.volume; } public void setVolume(int percent) { // ..... } }
- 브릿지 패턴 - Abstraction & Client
class BasicRemote { protected Device device; public BasicRemote(Device device) { this.device = device; } public void volumeDown(){ int current = device.getVolume(); device.setVolume(current-10); } public void volumeUp(){ int current = device.getVolume(); device.setVolume(current+10); } } // Client public class Client{ public static void testDevice(Device device){ BasicRemote basicRemote = new BasicRemote(device); basicRemote.volumeUp(); basicRemote.volumeDown(); } public static void main(String[] args){ testDevice(new Radio()); testDevice(new TV()); } }
- 브릿지 v.s. 어댑터
- 브리지는 일반적으로 사전에 설계되어서 다양한 부분을 독립적으로 개발
- 어댑터는 기존 앱과 사용되어 원래 호환되지 않던 일부 클래스들이 서로 잘 작동하도록 함
- 사용 사례
- JDBC 는 DB 벤더에 상관 없이 쿼리를 요청하고 결과를 받을 수 있음
- 다른 벤더의 DB 를 사용한다고 해도 JDBC 를 이용하는 코드에는 영향 없음
- 실제 DB 에 대한 구체적인 구현은 org.h2.Driver 에 있음
public class JdbcExample{ public static void main(String[] args) throws ClassNotFoundException{ Class.forName("org.h2.Driver"); try(Connection conn = DriverManager.getConnection("jdbc:h2:mem:~/test", "sa", "")){ String sql = "CREATE TABLE ACCOUNT " + "(id INTEGER not NULL, " + " email VARCHAR(255), " + password VARCHAR(255), " + " PRIMARY KEY ( id ))"; Statement statement = conn.createStatement(); statement.execute(sql); }catch(SQLException e){ throw new RuntimeException(e); } } }
- JDBC 는 DB 벤더에 상관 없이 쿼리를 요청하고 결과를 받을 수 있음
- 장점
- OCP 준수
- 새로운 추상화들의 구현들을 상호 독립적으로 둘 수 있기 때문
- SRP 준수
- 추상화의 상위 수준 논리와 구현 플랫폼 세부 정보에 집중할 수 있기 때문
- OCP 준수
- 단점
- 계층 구조가 늘어나 코드 복잡성이 증가할 수 있음
- 단 하나의 클래스만 만들고 확장 가능성이 없다면 불필요한 작업
- 설계 구조 파악이 안되면 코드를 파악하는데 어려움이 있을 수 있음
- 계층 구조가 늘어나 코드 복잡성이 증가할 수 있음
반응형
'3학년 2학기 학사 > 소프트웨어 분석 및 설계' 카테고리의 다른 글
[소프트웨어 분석 및 설계] L19. Facade Pattern, Flyweight Pattern, Proxy Pattern (0) | 2024.12.13 |
---|---|
[소프트웨어 분석 및 설계] L18. Composite Pattern, Decorator Pattern (0) | 2024.12.13 |
[소프트웨어 분석 및 설계] L16. Abstract Factory Pattern, Builder Pattern, Prototype Pattern (0) | 2024.12.13 |
[소프트웨어 분석 및 설계] L15. Factory Method (팩토리 메소드) (0) | 2024.12.12 |
[소프트웨어 분석 및 설계] # L14. 디자인 패턴, 싱글톤 패턴 (0) | 2024.12.11 |