디자인 패턴
📌 소프트웨어 개발에서 자주 발생하는 문제를 해결하기 위한 표준화된 방법이나 절차
| 생성 패턴 (Creational Patterns) | 객체 생성과 관련된 패턴 |
| 구조 패턴 (Structural Patterns) | 객체와 클래스 간의 관계를 다룸 |
| 행위 패턴 (Behavioral Patterns) | 객체와 객체 간의 상호작용과 책임 분배에 관한 패턴 |
1. 생성 패턴 (Creational Patterns)
▶️Singleton (싱글턴), Factory Method (팩토리 메소드), Abstㅋract Factory (추상 팩토리), Builder (빌더), Prototype (프로토타입)
싱글톤 패턴 (Singleton Pattern)
👉 클래스의 인스턴스가 오직 하나만 존재
사용 예시: 데이터베이스 연결 객체, 로그 시스템 등
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
종류
▪️EagerSingleton: 클래스가 로드 될 때 즉시 인스턴스 생성
public class EagerSingleton {
// 클래스 로딩 시 바로 객체를 생성
private static final EagerSingleton INSTANCE = new EagerSingleton();
// private 생성자 - 외부에서 객체 생성 불가
private EagerSingleton() { }
// public 메서드로 인스턴스를 반환
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
👍장점
- 멀티 스레드 환경에서 동기화 고려X
- 빠른 초기화
👎단점
- 불필요한 리소스 사용
- 실제로 인스턴트가 필요하지 않은 경우에도 메모리 차지
▪️LazySingleton: 실제 필요해질 때까지 인스턴스 생성 지연
synchronized를 이용한 지연 초기화
public class LazySingleton {
private static LazySingleton instance; // 인스턴스를 담을 정적 변수
private LazySingleton() { } // private 생성자 (외부에서 인스턴스를 생성할 수 없도록 함)
public static synchronized LazySingleton getInstance() {
// 인스턴스가 아직 생성되지 않았다면 생성
if (instance == null) {
instance = new LazySingleton();
}
return instance; // 이미 생성된 인스턴스를 반환
}
}
👍장점
- 지연 초기화
- 실제 필요할 때만 생성되므로 메모리 낭비를 줄일 수 있음
👎단점:
- 성능 저하(동기화 오버헤드)
- 인스턴트를 생성할 때마다 synchronized가 적용되기 때문에, 여러 스레드가 동시에 호출하면 성능이 떨어질 수 있음
▪️Double-Checked Locking: 멀티 스레드 동시 접근 시에도 안전하게 하나만 생성
public class DoubleCheckedLockingSingleton {
// volatile 키워드로 인스턴스 변수 선언
private static volatile DoubleCheckedLockingSingleton instance;
// private 생성자, 외부에서 인스턴스를 생성할 수 없음
private DoubleCheckedLockingSingleton() { }
// 인스턴스를 반환하는 정적 메서드
public static DoubleCheckedLockingSingleton getInstance() {
// 첫 번째 체크: 인스턴스가 null이면 동기화된 블록에 들어가도록 함
if (instance == null) {
// 동기화된 블록으로 진입
synchronized (DoubleCheckedLockingSingleton.class) {
// 두 번째 체크: 다른 스레드가 이미 인스턴스를 생성했을 수 있기 때문에 다시 확인
if (instance == null) {
// 인스턴스를 생성
instance = new DoubleCheckedLockingSingleton();
}
}
}
// 생성된 인스턴스를 반환
return instance;
}
}
💡volatile: 모든 스레드가 최신 값을 즉시 읽을 수 있도록 보장
👍장점
- 성능 최적화
- nstance가 이미 생성된 경우에는 synchronized 블록을 실행하지 않기 때문에 성능이 개선.
- 스레드 안전: 멀티스레드 환경에서도 안전하게 작동하며, 하나의 인스턴스만 생성.
👎단점
- 복잡성
- 다른 단순한 싱글톤 구현 방식보다 코드가 더 복잡
2. 구조 패턴 (Structural Patterns)
▶️ Adapter (어댑터), Bridge (브리지), Composite (컴포지트), Decorator (데코레이터), Facade (파사드), Flyweight (플라이웨이트), Proxy (프록시)
어댑터 패턴 (Adapter Pattern)
👉호환되지 않는 인터페이스를 가진 클래스 들을 연결하는 패턴
사용 예시: 구형 코드와 새로운 API를 연결할 때.
class OldSystem {
public void oldMethod() {
System.out.println("Old system method");
}
}
interface NewSystem {
void newMethod();
}
class Adapter implements NewSystem {
private OldSystem oldSystem;
public Adapter(OldSystem oldSystem) {
this.oldSystem = oldSystem;
}
@Override
public void newMethod() {
oldSystem.oldMethod();
}
}
//Adaptee -- 기존 코드나 시스템 (집에서 가져간 드라이기) -- 해외여행가서 직접사용 불가!!
class Power220v{
public void connect(){
System.out.println("내가 집에서 가져간 드라이기로 머리 말림!! ");
}
}
//target - 클라이언트가 기대하는 인터페이스 110v
interface Power110v{
void supply();
}
//Adapter - Adaptee의 인터페이스를 Target의 인터페이스로 변환해주는 클래스
class PowerAdater implements Power110v{
private Power220v power220v;
public PowerAdater(Power220v power220v){
this.power220v = power220v;
}
@Override
public void supply() {
System.out.println("어댑터를 사용해서 220v를 110v 로 변환.... ");
power220v.connect();
}
}
public class AdapterDemo {
public static void main(String[] args) {
Power220v power220v = new Power220v(); //집에서 가져간 드라이기
Power110v adapter = new PowerAdater(power220v);
adapter.supply();
}
}
데코레이터 패턴 (Decorator Pattern)
👉객체에 동적으로 추가적인 기능을 부여할 수 있는 패턴
👉기존 클래스를 수정하지 않고도 기능을 확장할 수 있음
사용 예시: UI 컴포넌트에 동적으로 스타일을 추가하는 경우.
//커피주문
//에스프레소 + 물 = 아메리카노..
//에스프레소 + 우유
//에스프레소 + 우유 + 시럽
//에스프레소 + 우유 + 시럽 + 생크림
//모든 커피의 기본이 되는 공통 인터페이스 (컴포넌트 인터페이스)
interface Coffee{
String getDescription(); //커피설명
int getCost(); //가격
}
// 가장 기본적인 커피 (에스프레소) 구현 - (기본 컴포넌트)
class Espresso implements Coffee{
@Override
public String getDescription() {
return "에스프레소";
}
@Override
public int getCost() {
return 3000;
}
}
//장식이 되는 클래스를 구현
//표준 - 데코레이터 추상
abstract class CoffeeDecorator implements Coffee{
protected Coffee decoratorCoffee;
public CoffeeDecorator(Coffee coffee){
this.decoratorCoffee = coffee;
}
@Override
public String getDescription() {
return decoratorCoffee.getDescription();
}
@Override
public int getCost() {
return decoratorCoffee.getCost();
}
}
//구체적인 장식 클래스 구현 -- 추가할 옵션 클래스를 구현해서 커피 기능을 확장함.
//우유 추가
class MilkDecorator extends CoffeeDecorator{
protected Coffee decoratorCoffee;
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription()+"우유";
}
@Override
public int getCost() {
return super.getCost()+500;
}
}
//시럽 추가
class SyrupDecorator extends CoffeeDecorator{
protected Coffee decoratorCoffee;
public SyrupDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription()+"시럽";
}
@Override
public int getCost() {
return super.getCost()+500;
}
}
public class DecoratorDemo {
public static void main(String[] args) {
//기본커피
Coffee espresso = new Espresso();
System.out.println(espresso.getDescription() + " 가격 : "+ espresso.getCost() + "원");
Coffee milkCoffee = new MilkDecorator(espresso);
System.out.println(milkCoffee.getDescription()+ " 가격 : "+milkCoffee.getCost() + "원");
Coffee SyrupMilkCoffee = new SyrupDecorator(milkCoffee);
System.out.println(SyrupMilkCoffee.getDescription()+ " 가격 : "+SyrupMilkCoffee.getCost() + "원");
}
}
Facade 패턴
👉복잡한 시스템의 인터페이스를 단순화하여 사용자가 더 쉽게 접근할 수 있도록 도와주는 패턴
클라이언트가 복잡한 내부 시스템을 이해하지 않고도 쉽게 사용할 수 있게 해 줌
다양한 기능을 제공하는 여러 클래스들을 하나의 Facade 클래스가 감싸고 있어, 클라이언트 코드가 복잡한 클래스들을 직접 호출할 필요가 없다.
Facade = “건물의 정면(앞면)”
복잡한 내부 구조를 감추고, 깔끔한 정면만 보이게 함
사용 예시: 복잡한 결제 시스템, 서버 관리 시스템, 홈 시어터 시스템
class DVDPlayer {
public void on() {
System.out.println("DVD Player is on");
}
public void play(String movie) {
System.out.println("Playing movie: " + movie);
}
public void stop() {
System.out.println("DVD Player stopped");
}
public void off() {
System.out.println("DVD Player is off");
}
}
class Projector {
public void on() {
System.out.println("Projector is on");
}
public void off() {
System.out.println("Projector is off");
}
}
class Screen {
public void down() {
System.out.println("Screen is down");
}
public void up() {
System.out.println("Screen is up");
}
}
class SoundSystem {
public void on() {
System.out.println("Sound system is on");
}
public void off() {
System.out.println("Sound system is off");
}
}
class HomeTheaterFacade {
private DVDPlayer dvdPlayer;
private Projector projector;
private Screen screen;
private SoundSystem soundSystem;
public HomeTheaterFacade(DVDPlayer dvdPlayer, Projector projector, Screen screen, SoundSystem soundSystem) {
this.dvdPlayer = dvdPlayer;
this.projector = projector;
this.screen = screen;
this.soundSystem = soundSystem;
}
public void watchMovie(String movie) {
System.out.println("Get ready to watch a movie...");
screen.down();
projector.on();
soundSystem.on();
dvdPlayer.on();
dvdPlayer.play(movie);
}
public void endMovie() {
System.out.println("Shutting down movie theater...");
screen.up();
projector.off();
soundSystem.off();
dvdPlayer.stop();
dvdPlayer.off();
}
}
public class Main {
public static void main(String[] args) {
DVDPlayer dvdPlayer = new DVDPlayer();
Projector projector = new Projector();
Screen screen = new Screen();
SoundSystem soundSystem = new SoundSystem();
HomeTheaterFacade homeTheater = new HomeTheaterFacade(dvdPlayer, projector, screen, soundSystem);
homeTheater.watchMovie("Inception"); // 영화 보기 시작
homeTheater.endMovie(); // 영화 끝내기
}
}
3. 행위 패턴 (Behavioral Patterns)
▶️ Chain of Responsibility (책임 연쇄), Command (커맨드), Interpreter (인터프리터), Iterator (이테레이터), Mediator (중재자), Memento (메멘토), Observer (옵저버), State (상태), Strategy (전략), Template Method (템플릿 메소드), Visitor (방문자)
옵저버 패턴 (Observer Pattern)
👉 객체의 상태 변화가 있을 때 그 객체에 의존하는 다른 객체들에게 자동으로 통지하는 패턴.
사용 예시: 이벤트 리스너 시스템, GUI에서 버튼 클릭 시 다른 컴포넌트에 알림을 주는 경우.
주제(Subject): 상태가 변화하면 이를 관찰하고 있는 옵저버들에게 변경 사항을 알리는 객체. (예: 뉴스 발행자)
옵저버(Observer): Subject의 상태 변화를 감지하고 이에 반응하는 객체들. (예: 구독자)
💡"한 객체(주제)가 바뀌면, 그 변화에 관심 있는 다른 객체들(옵저버)이 자동으로 업데이트된다."
import java.util.ArrayList;
import java.util.List;
// Subject (주체) 역할을 하는 WeatherData 클래스
class WeatherData {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList<>();
}
// 옵저버 등록
public void registerObserver(Observer observer) {
observers.add(observer);
}
// 옵저버 제거
public void removeObserver(Observer observer) {
observers.remove(observer);
}
// 옵저버에게 알림
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
// 날씨 정보 설정
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
notifyObservers(); // 날씨 데이터가 변경되면 옵저버들에게 알림
}
}
// Observer (옵저버) 인터페이스
interface Observer {
void update(float temperature, float humidity, float pressure);
}
// DisplayElement (디스플레이 요소) 인터페이스
interface DisplayElement {
void display();
}
// CurrentConditionsDisplay (현재 조건 표시) 클래스
class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private WeatherData weatherData;
public CurrentConditionsDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this); // 날씨 데이터를 옵저버로 등록
}
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
@Override
public void display() {
System.out.println("현재 온도: " + temperature + "°C, 습도: " + humidity + "%");
}
}
// StatisticsDisplay (통계 표시) 클래스
class StatisticsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private WeatherData weatherData;
public StatisticsDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this); // 날씨 데이터를 옵저버로 등록
}
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
@Override
public void display() {
System.out.println("평균 온도: " + temperature + "°C, 평균 습도: " + humidity + "%");
}
}
// 사용 예시
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
// 두 개의 옵저버(디스플레이 화면)를 등록
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
// 날씨 정보 업데이트
weatherData.setMeasurements(25, 65, 1013);
weatherData.setMeasurements(28, 70, 1012);
}
}
Strategy(전략) 패턴
👉동일한 목적의 작업을 다양한 방법(전략)으로 수행할 수 있도록 설계
전략"이란, **“한 가지 일을 하는 여러 가지 다른 방법”**을 의미
사용 예시:검색 알고리즘, 게임에서의 AI 행동 선택, 온라인 쇼핑몰의 할인 정책
interface PriceStrategy {
double calculatePrice(double price);
}
class NoDiscount implements PriceStrategy {
@Override
public double calculatePrice(double price) {
return price; // 할인 없음
}
}
class PercentageDiscount implements PriceStrategy {
private double percentage;
public PercentageDiscount(double percentage) {
this.percentage = percentage;
}
@Override
public double calculatePrice(double price) {
return price - (price * percentage / 100);
}
}
class FixedDiscount implements PriceStrategy {
private double discountAmount;
public FixedDiscount(double discountAmount) {
this.discountAmount = discountAmount;
}
@Override
public double calculatePrice(double price) {
return price - discountAmount;
}
}
class PriceCalculator {
private PriceStrategy priceStrategy;
// 전략을 설정하는 메서드
public void setPriceStrategy(PriceStrategy priceStrategy) {
this.priceStrategy = priceStrategy;
}
// 가격 계산 메서드
public double calculate(double price) {
return priceStrategy.calculatePrice(price);
}
}
public class Main {
public static void main(String[] args) {
PriceCalculator priceCalculator = new PriceCalculator();
// 가격에 대해 전략을 설정
priceCalculator.setPriceStrategy(new NoDiscount());
System.out.println("No Discount: " + priceCalculator.calculate(100.0));
priceCalculator.setPriceStrategy(new PercentageDiscount(10)); // 10% 할인
System.out.println("10% Discount: " + priceCalculator.calculate(100.0));
priceCalculator.setPriceStrategy(new FixedDiscount(15)); // 15달러 할인
System.out.println("Fixed Discount: " + priceCalculator.calculate(100.0));
}
}
'JAVA' 카테고리의 다른 글
| [JAVA] StringBuilder (0) | 2025.07.06 |
|---|---|
| BigInteger (0) | 2025.03.23 |
| 서블릿(Servlet), JSP(Java Server Pages), 서블릿 컨테이너 간단 정리 (0) | 2025.01.23 |
| [JAVA] 예외 (4) | 2025.01.03 |