디자인패턴
[Java][디자인 패턴] 11. 파사드 패턴 (Facade Pattern)
파사드는 요즘과 같이 협업과 대형 시스템을 개발하고 배포하는 데 자주 응용되는 패턴이다. (API 등)
파사드 패턴은 강력한 결합 구조를 해결하기 위해 코드의 의존성을 줄이고 느슨한 결합으로 구조를 변경한다.
파사드 패턴이란?
- 파사드 패턴이란 서브 시스템을 보다 쉽게 쓸 수 있도록 높은 수준의 인터페이스를 정의하는 작업으로, 이미 수많은 API 서비스와 라이브러리, 패키지에서 파사드 패턴 응용 중이다.
- 파사드 패턴은 강력한 결합 구조를 해결하기 위해 코드의 의존성을 줄이고 느슨한 결합으로 구조를 변경한다.
- 파사드 패턴은 메인 시스템과 서브 시스템 중간에 위치하는데, 새로운 인터페이스 계층을 추가하며 시스템 간 의존성을 해결한다. 인터페이스 계층은 메인 시스템과 서브 시스템의 연결 관계를 대신 처리한다.
- 서브 시스템을 호출, 결합할 수 있는 인터페이스를 제공한다. 인터페이스는 한 개 일 수 있고 여러 개 일 수도 있고 또는 이를 함수 형태로 제공하기도 한다.
- 객체의 내부 구조를 상세히 알 필요는 없다. 파사드는 시스템의 연결성과 종속성을 최소화하는 것을 목적으로 한다.
최소 지식 원칙 (디미터 / 데메테르 원칙)
- 최소 지식만 적용해 객체의 상호 작용을 설정하면 유지 보수가 용이해진다.
- 불필요한 객체의 생성 루틴과 재호출을 코드에 코드에 삽입해 코드의 가독성과 복잡성을 증가시키지 않도록 한다.
- 최소 지식의 원칙
- 자기 자신만의 객체 사용
- 메서드에 전달된 매개변수 사용
- 메서드에서 생성된 객체 사용
- 객체에 속하는 메서드 사용
public class Car {
// 클래스의 구성 요소
// 구성 요소의 메서드는 호출해도 된다.
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start(CarKey key) {
// 새로운 객체 생성
// 내부에서 생성된 객체의 메서드는 호출해도 된다.
Doors doors = new Doors();
// 매개 변수로 전달된 객체의 메서드는 호출해도 된다.
boolean authorized = key.turns();
if (authorized) {
// 객체의 구성 요소의 메서드는 호출해도 된다.
engine.start();
// 객체 내에 있는 메서드는 호출해도 된다.
this.updateDashboardDisplay();
// 직접 생성하거나 인스턴스를 만든 객체의 메서드는 호출해도 된다.
doors.locks();
}
}
private void updateDashboardDisplay() {
}
}
- 참고 자료: 쉽게 배워 바로 써먹는 디자인 패턴 책 (링크)
파사드 패턴 구조
모든 사람들이 Car라는 인터페이스의 실제 구현체 내용을 모르더라도 사용할 수 있도록 하고, 강한 객체 간의 결합을 낮추고 유연한 구조를 가지고 갈 수 있도록 Door, Engine 클래스도 모두 인터페이스 화했다. Java 코드만을 이용해서 파사드를 구현하자니 이렇게 인터페이스화 하는 구조를 파사드 패턴으로 정의했다.
실제 우리가 Spring 등을 통해 API를 개발한다고 하면 그것도 이미 파사드 패턴을 응용한다고 볼 수 있다. 특히 MSA 구조로 모듈을 모두 쪼개 두고 개발한다면 View-Controller 단의 의존도를 낮추는 것뿐만 아니라 모듈 간의 결합도도 낮추고 다른 서버 장애가 나도 영향을 낮출 수 있다. Feign 통신으로 다른 모듈 간에 API 호출을 한다거나, 클라이언트 단에서 서버 API를 호출하는 등 이러한 것은 Spring이 파사드 패턴으로 중간에서 모듈 간의 결합도를 낮추고 시스템의 연결성과 종속성을 낮춘다고 볼 수 있다.
파사드 패턴 코드
1. Car 인터페이스
public interface Car {
void open(String key);
void drive(String key);
void stop();
String getName();
}
2. Doors 인터페이스, Engine 인터페이스
public interface Doors {
void locks(String key);
void unlocks(String key);
CarKey getKey();
}
public interface Engine {
void start();
void stop();
EngineStatus status();
}
3. Doors 인터페이스에서 사용하는 CarKey, Engine 인터페이스에서 사용하는 EngineStatue 클래스
public class CarKey {
private String key;
public CarKey(String key) {
this.key = key;
}
public boolean turns(String key) {
return this.key.equals(key);
}
}
public enum EngineStatus {
DRIVE("DRIVE", "운행"),
STOP("STOP", "정지");
private final String code;
private final String displayName;
EngineStatus(String code, String displayName) {
this.code = code;
this.displayName = displayName;
}
public String getCode() {
return code;
}
public String getDisplayName() {
return displayName;
}
}
4. Doors, Engine 인터페이스의 구체 클래스
public class HDoors implements Doors {
private boolean lock;
private CarKey key;
public HDoors(CarKey key) {
this.key = key;
this.lock = true;
}
@Override
public void locks(String key) {
if (!this.key.turns(key)) {
throw new CarKeyNotMatchException();
}
System.out.println("문이 닫혔습니다.");
}
@Override
public void unlocks(String key) {
if (!this.key.turns(key)) {
throw new CarKeyNotMatchException();
}
System.out.println("문이 열렸습니다..");
}
public CarKey getKey() {
return key;
}
}
public class CarKeyNotMatchException extends IllegalArgumentException {
public CarKeyNotMatchException() {
super("키가 맞지 않습니다.");
System.out.println("CarKeyNotMatchException");
}
}
public class HEngine implements Engine {
EngineStatus engineStatus;
public HEngine() {
this.engineStatus = EngineStatus.STOP;
}
@Override
public void start() {
this.engineStatus = EngineStatus.DRIVE;
}
@Override
public void stop() {
this.engineStatus = EngineStatus.STOP;
}
@Override
public EngineStatus status() {
return this.engineStatus;
}
}
5. Car 구체 클래스
public class HCar implements Car {
// 클래스의 구성 요소
// 구성 요소의 메서드는 호출해도 된다.
private Engine engine;
private String name;
private Doors doors;
public HCar(Engine engine, String key) {
this.engine = engine;
this.name = "현대 자동차";
this.doors = new HDoors(new CarKey(key));
}
@Override
public void open(String key) {
doors.unlocks(key);
}
@Override
public void drive(String key) {
boolean authorized = this.doors.getKey().turns(key);
if (authorized) {
engine.start();
this.updateDashboardDisplay();
doors.locks(key);
} else {
throw new CarKeyNotMatchException();
}
}
@Override
public void stop() {
engine.stop();
this.updateDashboardDisplay();
}
private void updateDashboardDisplay() {
System.out.println(getName() + " " + engine.status().getDisplayName());
}
@Override
public String getName() {
return this.name;
}
}
6. Car 테스트 코드
class CarTest {
@Test
@DisplayName("현대자동차 운행 테스트")
void carDriveTest() {
String key = "CAR_SECRET_KEY";
Car car = new HCar(new HEngine(), key);
car.open(key);
car.drive(key);
car.stop();
}
@Test
@DisplayName("현대자동차 잘못된 키 테스트")
void carDriveInvalidTest() {
String key = "CAR_SECRET_KEY";
Car car = new HCar(new HEngine(), key);
assertThrows(CarKeyNotMatchException.class, () -> car.open("INVALID_KEY"));
}
}
파사드 패턴의 효과
- 서브 시스템 보호
- 서브 시스템의 구성 요소를 직접 호출하지 않으므로 잘못된 사용을 방지
- 내부 구조와 외부 사용을 구분하므로 추후 서브시스템 업그레이드에 자유로움
- 확장성
- 코드 변경 시 상위 시스템에는 파사드를 이용하므로 서브 시스템이 변경되어도 큰 변화를 느낄 수 없음.
- 확장성을 고려하면서 서브 시스템 기능을 유지할 수 있도록 완충하는 역할
- 결합도 감소
- 서브 시스템이 복잡하고 종속성이 강할 때는 파사드 패턴 이용
- 복잡한 종속적 결함도를 낮추고 독립적인 코드 유지 가능
- 계층화
- 서브 시스템이 계층화된 구조를 갖더라도 파사드는 계층 단계별로 접근하여 행위를 호출할 수 있음.
- 이식성
- 여러 작업을 하나의 묶음으로 처리하여 복잡한 클래스 단순화
- 서브 시스템을 공통적으로 사용할 수 있도록 이식성을 향상
- 공개 인터페이스
- 외부에 공개되는 기능과 공개되지 않는 기능을 구분 가능
- 인터페이스를 제공함과 동시에 서브 시스템의 기능을 캡슐 화하여 일시적으로 특정 기능을 감추는 효과를 얻을 수 있음
Github 코드
참고 자료
1.쉽게 바로 써먹는 디자인 패턴 책
http://www.yes24.com/Product/Goods/93173296
'개발 > 디자인패턴' 카테고리의 다른 글
[Java][디자인 패턴] 13. 프록시 패턴 (Proxy Pattern) (0) | 2022.03.17 |
---|---|
[Java][디자인 패턴] 12. 플라이웨이트 패턴 (Flyweight Patten) (0) | 2022.03.16 |
[Java][디자인 패턴] 10. 장식자 패턴 (Decorator Pattern) (0) | 2022.03.09 |
[Java][디자인 패턴] 9. 복합체 패턴 (Composite Pattern) (0) | 2022.03.08 |
[Java][디자인 패턴] 8. 브리지 패턴 (Bridge Pattern) (1) | 2022.03.07 |