밤빵's 개발일지
[TIL]20241120 FACADE 패턴 본문
FACADE 패턴은 시스템의 복잡한 부분을 감추고, 단순화된 인터페이스를 제공함으로써 사용자가 쉽게 사용할 수 있도록 돕는 디자인 패턴이다. 이번 개발일지를 통해 FACADE 패턴의 개념을 학습하고 이를 실제 코드에 어떻게 적용할 수 있는지 이해해보려고 한다.
▶FACADE 패턴?
FACADE 패턴은 복잡한 서브 시스템이나 여러 클래스들로 구성된 코드를 단순화하기 위해 사용하는 디자인 패턴이다. 이 패턴은 여러 개의 클래스들이 서로 복잡하게 상호작용하는 시스템을 단순하게 사용할 수 있는 단일 인터페이스를 제공한다. 복잡한 내부 구조를 숨기고 외부에서는 단순한 메서드 호출만으로도 다양한 기능을 사용할 수 있게 만들어 준다.
이 패턴의 목적은 코드의 복잡성을 줄이고 유지보수성을 높이는 것이다. 개발자가 직접 여러 클래스와 상호작용하기보다, FACADE를 통해 단순한 인터페이스로 상호작용하게 하여 코드의 가독성과 사용성을 높인다.
▶예시 코드 분석
내가 작성한 GamingSetupService 클래스의 예시 코드로, 이 코드는 여러 DTO와 엔티티, 레포지토리 등을 사용해 게임 설정을 관리하는 서비스이다. 현재 이 코드는 서비스 내부에서 다양한 데이터 변환과 검증 로직을 직접 처리하고 있어 코드가 조금 복잡하고, 유지보수가 어렵게 느껴진다. ( 정말 굴러가게만 만들어놨더니........😵💫)
package com.sample.pugbrecords.gamingSetup.service;
import com.sample.pugbrecords.gamingSetup.dto.GamingSetupRequestDto;
import com.sample.pugbrecords.gamingSetup.dto.GamingSetupResponseDto;
import com.sample.pugbrecords.gamingSetup.entity.GamingSetup;
import com.sample.pugbrecords.gamingSetup.repository.GamingSetupRepository;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
public class GamingSetupService {
private final GamingSetupRepository gamingSetupRepository;
public GamingSetupService(GamingSetupRepository gamingSetupRepository) {
this.gamingSetupRepository = gamingSetupRepository;
}
public List<GamingSetupResponseDto> getAllGamingSetups() {
return gamingSetupRepository.findAll().stream()
.map(this::convertToResponseDto)
.collect(Collectors.toList());
}
public Optional<GamingSetupResponseDto> getGamingSetupByPlayer(String player) {
return gamingSetupRepository.findByPlayer(player).map(this::convertToResponseDto);
}
public GamingSetupResponseDto createGamingSetup(GamingSetupRequestDto gamingSetupRequestDto) {
GamingSetup gamingSetup = convertToEntity(gamingSetupRequestDto);
GamingSetup savedSetup = gamingSetupRepository.save(gamingSetup);
return convertToResponseDto(savedSetup);
}
public Optional<GamingSetupResponseDto> updateGamingSetup(String player, GamingSetupRequestDto gamingSetupRequestDto) {
Optional<GamingSetup> existingSetup = gamingSetupRepository.findByPlayer(player);
if (existingSetup.isPresent()) {
GamingSetup gamingSetup = existingSetup.get();
updateEntityFromDto(gamingSetupRequestDto, gamingSetup);
GamingSetup updatedSetup = gamingSetupRepository.save(gamingSetup);
return Optional.of(convertToResponseDto(updatedSetup));
}
return Optional.empty();
}
public Optional<GamingSetupResponseDto> partialUpdateGamingSetup(String player, GamingSetupRequestDto gamingSetupRequestDto) {
Optional<GamingSetup> existingSetup = gamingSetupRepository.findByPlayer(player);
if (existingSetup.isPresent()) {
GamingSetup gamingSetup = existingSetup.get();
updateEntityFromDto(gamingSetupRequestDto, gamingSetup);
GamingSetup updatedSetup = gamingSetupRepository.save(gamingSetup);
return Optional.of(convertToResponseDto(updatedSetup));
}
return Optional.empty();
}
public boolean deleteGamingSetup(String player) {
Optional<GamingSetup> existingSetup = gamingSetupRepository.findByPlayer(player);
if (existingSetup.isPresent()) {
gamingSetupRepository.delete(existingSetup.get());
return true;
}
return false;
}
private GamingSetup convertToEntity(GamingSetupRequestDto dto) {
return new GamingSetup(
dto.getPlatform(),
dto.getPlayer(),
dto.getDpi(),
dto.getGeneralSens(),
dto.getVerticalSens(),
dto.getAimSens(),
dto.getAimSens1x(),
dto.getAimSens2x(),
dto.getAimSens3x(),
dto.getAimSens4x(),
dto.getAimSens6x(),
dto.getAimSens8x(),
dto.getAimSens15x(),
dto.getResolution(),
dto.getFov(),
dto.getMonitor(),
dto.getMouse(),
dto.getMousepad(),
dto.getKeyboard(),
dto.getHeadset(),
dto.getSoundCard(),
dto.getMonitorLink(),
dto.getMouseLink(),
dto.getMousepadLink(),
dto.getKeyboardLink(),
dto.getHeadsetLink()
);
}
private GamingSetupResponseDto convertToResponseDto(GamingSetup entity) {
return new GamingSetupResponseDto(
entity.getId(),
entity.getPlatform(),
entity.getPlayer(),
entity.getDpi(),
entity.getGeneralSens(),
entity.getVerticalSens(),
entity.getAimSens(),
entity.getAimSens1x(),
entity.getAimSens2x(),
entity.getAimSens3x(),
entity.getAimSens4x(),
entity.getAimSens6x(),
entity.getAimSens8x(),
entity.getAimSens15x(),
entity.getResolution(),
entity.getFov(),
entity.getMonitor(),
entity.getMouse(),
entity.getMousepad(),
entity.getKeyboard(),
entity.getHeadset(),
entity.getSoundCard(),
entity.getMonitorLink(),
entity.getMouseLink(),
entity.getMousepadLink(),
entity.getKeyboardLink(),
entity.getHeadsetLink()
);
}
private void updateEntityFromDto(GamingSetupRequestDto dto, GamingSetup entity) {
if (dto.getPlatform() != null && !dto.getPlatform().isEmpty()) entity.setPlatform(dto.getPlatform());
if (dto.getPlayer() != null && !dto.getPlayer().isEmpty()) entity.setPlayer(dto.getPlayer());
if (dto.getDpi() != 0) entity.setDpi(dto.getDpi());
if (dto.getGeneralSens() != 0) entity.setGeneralSens(dto.getGeneralSens());
if (dto.getVerticalSens() != 0) entity.setVerticalSens(dto.getVerticalSens());
if (dto.getAimSens() != 0) entity.setAimSens(dto.getAimSens());
if (dto.getAimSens1x() != 0) entity.setAimSens1x(dto.getAimSens1x());
if (dto.getAimSens2x() != 0) entity.setAimSens2x(dto.getAimSens2x());
if (dto.getAimSens3x() != 0) entity.setAimSens3x(dto.getAimSens3x());
if (dto.getAimSens4x() != 0) entity.setAimSens4x(dto.getAimSens4x());
if (dto.getAimSens6x() != 0) entity.setAimSens6x(dto.getAimSens6x());
if (dto.getAimSens8x() != 0) entity.setAimSens8x(dto.getAimSens8x());
if (dto.getAimSens15x() != 0) entity.setAimSens15x(dto.getAimSens15x());
if (dto.getResolution() != null && !dto.getResolution().isEmpty()) entity.setResolution(dto.getResolution());
if (dto.getFov() != 0) entity.setFov(dto.getFov());
if (dto.getMonitor() != null && !dto.getMonitor().isEmpty()) entity.setMonitor(dto.getMonitor());
if (dto.getMouse() != null && !dto.getMouse().isEmpty()) entity.setMouse(dto.getMouse());
if (dto.getMousepad() != null && !dto.getMousepad().isEmpty()) entity.setMousepad(dto.getMousepad());
if (dto.getKeyboard() != null && !dto.getKeyboard().isEmpty()) entity.setKeyboard(dto.getKeyboard());
if (dto.getHeadset() != null && !dto.getHeadset().isEmpty()) entity.setHeadset(dto.getHeadset());
if (dto.getSoundCard() != null && !dto.getSoundCard().isEmpty()) entity.setSoundCard(dto.getSoundCard());
}
}
▶FACADE 패턴 적용해보기
위 코드는 다양한 데이터 변환과 검증, 데이터베이스 접근이 한 클래스에 집중되어 있어 코드가 복잡하고, 수정할 때 많은 부분을 건드려야 할 가능성이 있다. 이러한 복잡성을 줄이기 위해 FACADE 패턴을 적용해볼 수 있다.
▶FACADE 클래스 추가
FACADE 패턴을 적용하기 위해 GamingSetupFacade라는 클래스를 추가해 본다. 이 클래스는 여러 복잡한 작업들을 간단한 메서드로 제공하는 역할을 하게 된다.
public class GamingSetupFacade {
private final GamingSetupService gamingSetupService;
public GamingSetupFacade(GamingSetupService gamingSetupService) {
this.gamingSetupService = gamingSetupService;
}
public GamingSetupResponseDto createSetupWithValidation(GamingSetupRequestDto requestDto) {
// 이곳에서 입력 데이터에 대한 추가적인 검증 로직을 수행할 수 있다.
if (requestDto.getPlayer() == null || requestDto.getPlayer().isEmpty()) {
throw new IllegalArgumentException("플레이어 이름은 필수입니다.");
}
return gamingSetupService.createGamingSetup(requestDto);
}
public List<GamingSetupResponseDto> getAllSetups() {
return gamingSetupService.getAllGamingSetups();
}
public Optional<GamingSetupResponseDto> getSetupByPlayer(String player) {
return gamingSetupService.getGamingSetupByPlayer(player);
}
}
→ 이 FACADE 클래스는 기존의 GamingSetupService에서 제공하는 다양한 메서드를 단순화된 인터페이스로 제공하여, 사용자가 쉽게 게임 설정을 생성하거나 조회할 수 있게 해준다. 이렇게 FACADE 클래스를 추가함으로써, 서비스 클래스의 복잡한 내부 로직을 감추고 간단한 메서드로 외부에 노출함으로써 코드의 가독성을 높이고, 사용자 코드에서의 복잡성을 줄일 수 있다.
▶FACADE 패턴의 장점과 단점
- 장점
- 복잡성 감소: 여러 클래스를 한 번에 다루는 복잡한 로직을 FACADE 클래스를 통해 단순화할 수 있다.
- 유지보수 용이: FACADE를 통해 기능을 노출함으로써, 내부 로직을 수정해도 외부에서는 단순한 인터페이스만 유지하면 되기 때문에 유지보수가 용이하다.
- 코드 가독성 향상: 복잡한 내부 로직을 감춤으로써, 클라이언트 코드가 훨씬 깔끔해진다.
- 단점
- 추상화의 위험성: 내부 로직이 FACADE로 감춰지면서, FACADE 내부에서 일어나는 세부적인 동작을 파악하기 어려울 수 있다.
- 오버헤드: 단순히 단순화를 위해 FACADE를 추가하면, 코드의 계층이 늘어나 오히려 불필요한 복잡도가 추가될 수 있다.
FACADE 패턴은 복잡한 시스템을 간단한 인터페이스로 감싸서 사용자에게 제공하는 패턴으로복잡한 시스템을 간단하게 사용할 수 있게 해주는 유용한 디자인 패턴임을 알게 되었다. 이번 예시에서는 복잡한 게임 설정 서비스 로직을 FACADE를 통해 감싸서, 사용자가 더 쉽게 접근할 수 있도록 만들었다. 이를 통해 코드의 복잡성을 줄이고 유지보수성을 높이는 것이 가능했다. FACADE 패턴을 사용해보면서 느낀 점은, 이 패턴이 코드의 복잡한 부분을 숨겨서 더 단순하게 만들 수 있다는 점이다. 그러나 추상화가 너무 많아지면 내부 동작을 이해하기 어렵게 될 수 있다는 단점도 명확히 인식할 수 있었다.
'개발Article' 카테고리의 다른 글
[TIL]20241122 synchronized (1) | 2024.11.22 |
---|---|
[TIL]20241121 Jackson @JsonInclude (1) | 2024.11.21 |
[TIL]20241118 Spring Security에서 권한별 URL 접근 제어에 대한 고민 (1) | 2024.11.19 |
[TIL]20241117 TDD (1) | 2024.11.17 |
[TIL]20241116 @Data를 써도 될까? (0) | 2024.11.16 |