개론
디자인 패턴은 많은 라이브러리나 프레임워크의 설계 기반이 되며, 개발자가 구조적인 사고를 하는 데 중요한 역할을 한다. 따라서 디자인 패턴에 대한 이해는 개발자의 기본 소양이라고 할 수 있다.
라이브러리 vs 프레임워크
공통점 : 공통으로 사용될 수 있는 특정한 기능들을 모듈화 한 것.
차이점 : 제어 흐름에 대한 주도성이 누구에게/어디에 있는지 다름.
- 라이브러리 - 흐름에 대한 주도성이 해당 라이브러리를 사용하는 개발자에게 있다. 사용이 자유롭다.
- 프레임워크 - 그 스스로 제어의 흐름에 대한 주도성을 가진다. 디렉토리 구조, 설정 파일 규칙 등 사용자가 지켜야 할 요소가 존재해 상대적으로 자유롭지 않다.
디자인 패턴이란?
- 소프트웨어를 설계하면서 반복적으로 마주치는 문제들을, 효율적으로 해결할 수 있도록 정형화한 재사용 가능한 설계 방법이다.
디자인 패턴의 종류
싱글톤 패턴(Singleton Pattern)
- 하나의 클래스는 오직 하나의 인스턴스만 가지는 패턴.
- 하나의 클래스를 기반으로 단 하나의 인스턴스를 만들어 이를 기반으로 로직을 만드는 데 사용, db 연결 모듈에 많이 사용한다.
- 스프링 컨테이너는 별도로 싱글톤 패턴을 적용하지 않아도 객체 인스턴스를 싱글톤으로 관리한다.
- 장점 - 하나의 인스턴스를 서로 다른 모듈에서 공유하며 사용하기 때문에 비용이 적게 듦
- 단점
- 코드 간 의존성이 높아짐
- TDD 할 때 걸림돌이 될 수 있음.
- 단위 테스트 시, 테스트가 서로 독립적이어야 하지만 싱글톤 패턴은 미리 생성된 하나의 인스턴스를 기반으로 구현하는 패턴이므로, 각 테스트마다 독립적인 인스턴스를 만들기 어려움
- 모듈 간의 결합을 강하게 만들 수 있음. 이럴 때, 의존성 주입(DI) 를 통해 모듈 간의 결합을 느슨하게 만들어 해결할 수 있음
class TestSingleton {
private static class SingleInstanceHolder {
private static final TestSingleton Instance = new TestSingleton();
}
public static TestSingleton getInstance() {
return SingleInstanceHolder.Instance;
}
}
public class HelloWorld{
public static void main(String []args){
TestSingleton a = TestSingleton.getInstance();
TestSingleton b = TestSingleton.getInstance();
System.out.println("a의 해시코드 값 : "+a.hashCode());
System.out.println("a의 해시코드 값 : "+b.hashCode());
if (a == b){
System.out.println(true);
}
}
}
의존성 주입(Dependency Injection)
- 객체 사이에 인터페이스를 사이에 둬서 클래스 레벨에서는 의존관계가 고정되지 않도록 하고 런타임 시에 관계를 동적으로 주입하여 유연성을 확보하고 결합도를 낮출 수 있게 해주는 방법
- 모듈들이 더욱 분리되기 때문에 클래스 수가 늘어나 복잡성이 증가될 수 있으며, 약간의 런타임 페널티가 생기기도 한다.
- 의존성 주입은 객체 사이의 결합을 느슨하게 하기 위한 설계 기법이며, 의존 역전 원칙(DIP: Dependency Inversion Principle)을 기반으로 한다.
- 의존 역전 원칙(DIP: Dependency Inversion Principle) : 상위 모듈은 하위 모듈에 의존해서는 안 되며, 둘 다 추상화(인터페이스)에 의존해야 한다.
팩토리 패턴(Factory Pattern)
- 객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴
- 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴
- 주로 인터페이스 또는 추상 클래스 기반의 상속 구조에서 활용되며, 하위 클래스에서 객체 생성을 책임지도록 위임한다.
장점
- 유지보수성 증가: 객체 생성 로직이 분리되어 있어 리팩토링이 쉬움
- 확장 용이성: 새로운 클래스가 추가되어도 팩토리만 수정하면 되므로, 코드 수정 범위가 줄어듦
- 결합도 감소: 클라이언트는 인터페이스만 의존하게 되어 느슨한 결합이 가능함
단점
- 클래스 수가 많아질 수 있음
- 객체 생성 구조가 분산되면, 초반 설계 난이도가 올라갈 수 있음
interface Shape {
void draw();
}
class Circle implements Shape {
public void draw() {
System.out.println("원을 그립니다.");
}
}
class Rectangle implements Shape {
public void draw() {
System.out.println("사각형을 그립니다.");
}
}
class ShapeFactory {
public static Shape createShape(String type) {
if ("circle".equalsIgnoreCase(type)) {
return new Circle();
} else if ("rectangle".equalsIgnoreCase(type)) {
return new Rectangle();
} else {
throw new IllegalArgumentException("알 수 없는 타입: " + type);
}
}
}
public class HelloWorld {
public static void main(String[] args) {
Shape circle = ShapeFactory.createShape("circle");
Shape rect = ShapeFactory.createShape("rectangle");
circle.draw();
rect.draw();
}
}
프록시 패턴(Proxy Pattern)
- 프록시 패턴은 어떤 객체에 대한 접근을 제어하기 위해 그 객체의 대리인(Proxy)을 두는 패턴
- 클라이언트는 실제 객체(RealSubject)를 직접 사용하는 것이 아니라, 프록시 객체를 통해 간접적으로 접근한다.
- 객체의 속성, 변환 등을 보완하며 보안, 데이터 검증, 캐싱, 로깅 등에 사용한다.
- 프론트엔드 개발 시, 백엔드 서버와 통신할 때, 마주치는 CORS 에러를 해결하기 위해 프론트엔드에서 프록시 서버를 만들기도 한다.
interface Image {
void display();
}
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println(filename + " 로딩 중...");
}
public void display() {
System.out.println(filename + " 표시 중...");
}
}
class ProxyImage implements Image {
private RealImage realImage;
private String filename;
public ProxyImage(String filename) {
this.filename = filename;
}
public void display() {
if (realImage == null) {
realImage = new RealImage(filename); // Lazy Loading
}
realImage.display();
}
}
public class HelloWorld {
public static void main(String[] args) {
Image image = new ProxyImage("hello.jpg");
System.out.println("이미지 표시 준비");
image.display();
}
}
- ProxyImage 클래스가 RealImage의 대리자 역할을 수행
- 무조건 생성 시점에 로딩하는 것은 비효율적임 → 필요한 경우에만 로딩하도록 구성
- display() 를 호출하기 전까지는 실제 RealImage를 생성하지 않고 (Lazy Loading(지연 초기화))
반응형
'Study' 카테고리의 다른 글
| [Docker] 이미지 load 시 invalid tar header 오류 해결 (1) | 2024.12.10 |
|---|---|
| 카카오 소셜 로그인 과정 (0) | 2024.09.17 |
| H2 DB 설치 및 사용 방법 (0) | 2022.06.16 |