디자인 패턴
개인적으로 개발자가 직접 구현할만한 패턴이라고 느끼는 것들.
팩토리 메소드, 템플릿 메소드, 전략 패턴, 싱글톤, 컴포지트 패턴
전략 패턴
요약 - 결제 방식 동적으로 변경해서 사용하기.
interface PaymentStrategy {
void pay(int amount);
}
class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(int amount) {
System.out.println("Paid " + amount + " using Credit Card.");
}
}
class PayPalPayment implements PaymentStrategy {
@Override
public void pay(int amount) {
System.out.println("Paid " + amount + " using PayPal.");
}
}
class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void checkout(int amount) {
paymentStrategy.pay(amount);
}
}
public class StrategyPatternExample {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
PaymentStrategy creditCardPayment = new CreditCardPayment();
cart.setPaymentStrategy(creditCardPayment);
cart.checkout(100);
PaymentStrategy paypalPayment = new PayPalPayment();
cart.setPaymentStrategy(paypalPayment);
cart.checkout(200);
}
}
Interface를 사용하는 디자인 패턴이다. State 패턴이랑 크게 다를건 없다.
대표적으로 List 처럼 쓰는 것을 말하는데 ArrayList, LinkedList 등등을 끼워서 사용할 수 있는 패턴을 말한다.
Interface는 Abstract와는 다른데, Abstract는 추상메소드가 아닌 메소드도 가질 수 있지만 Interface는 오로지 추상메소드로만 이루어져있기 때문에 상속을 받으면 항상 같은 메소드만을 구현해줘야 하는 특성 때문에 '전략'에 맞춰서 다른 클래스를 끼워서 쓸 수 있다는 점에서 전략 패턴이라고 불린다.
팩토리 메소드 패턴
요약 - 객체 생성 위임
팩토리 메소드 패턴(Factory Method Pattern)은 객체 생성을 위한 패턴 중 하나로, 객체 생성을 별도의 메소드(팩토리 메소드)로 분리하는 디자인 패턴입니다.
정적 팩토리 메소드 패턴과 같은걸 말한다.
단점이 있다면 객체 생성이 이것저것 너무 많아져서 복잡해질 수 있다라는 점.
템플릿 메소드 패턴
요약 - 순서유지!
템플릿 메소드 패턴은 메소드 순서에 대한 강제화가 필요할 때 조금 더 유용하다. 그렇다고 해서 템플릿 메소드 패턴의 추상화가 만능이라고 생각해서 모든 것을 추상화 처리하는 것은 좋지 않다. 전체-부분에 대한 개념에서는 추상화보단 컴포지트 패턴이 좋다.
템플릿 메소드 패턴의 단점은 장점이자 단점인건데, 알고리즘 구조를 고정시키므로, 서브클래스에서의 변화를 제한할 수 있다. 즉, 알고리즘의 수정이 어려울 수 있다.
전략 패턴은 하나의 메소드가 여러 방식으로 사용되는 방식이었다면 템플릿 메소드 패턴은 여러개의 메소드가 순서대로 처리 될 때 쓴다. 가령 운동이라고 치면 스트레칭 -> 운동이라는 순서를 지켜주는 것과 마찬가지이다. 스트레칭과 운동의 방식은 달라질 수 있어도 순서는 달라질 수 없을 때 사용한다.
abstract class CoffeeMaker {
// 템플릿 메소드
public final void makeCoffee() {
boilWater();
brewCoffeeGrounds();
pourInCup();
addCondiments();
}
protected abstract void boilWater();
protected abstract void brewCoffeeGrounds();
protected abstract void pourInCup();
// 훅 메소드 (선택적으로 오버라이딩 가능)
protected void addCondiments() {
// 기본적으로 아무것도 추가하지 않음
}
}
class AmericanoMaker extends CoffeeMaker {
protected void boilWater() {
System.out.println("물 끓이기");
}
protected void brewCoffeeGrounds() {
System.out.println("커피를 내리다");
}
protected void pourInCup() {
System.out.println("컵에 따르다");
}
}
class LatteMaker extends CoffeeMaker {
protected void boilWater() {
System.out.println("물 끓이기");
}
protected void brewCoffeeGrounds() {
System.out.println("커피를 내리다");
}
protected void pourInCup() {
System.out.println("컵에 따르다");
}
protected void addCondiments() {
System.out.println("우유 추가");
}
}
커맨드 패턴
요약 - 직접 구현을 객체 안에 숨겨서 사용한다. 안써도 구현이 돼서 중요도는 떨어진다.
기능들을 명령화 시켜서 사용하는 것을 의미한다. 명령들이 캡슐화 되어 보안에 좋은 장점이 있다.
퍼사드 패턴
요약 - 잘 안쓴다. 안 써도 그리 문제가 없다.
퍼사드 패턴은 여러 클래스를 동시 사용해야 가능한 구조 일 때 여러 클래스를 다시 하나의 클래스로 묶는 행위를 말한다.
그렇게 하면 개발자는 하나의 클래스만 사용하면 되는데 이 클래스가 퍼사드(프랑스어로 외벽) 패턴으로 만들어진 클래스라고 한다.
사실 장점이라고 한다면 클래스를 하나만 사용한다는 특징인데, 굳이 이걸 써서 얻을 장점이 거의 없다고 한다.
옵저버 패턴
옵저버 패턴은 한 객체의 상태 변경을 다른 객체들에게 통지하는 패턴입니다. 주로 이벤트 처리나 상태 감시에 사용됩니다. 아래는 간단한 코드 예시입니다:
import java.util.ArrayList;
import java.util.List;
interface Observer {
void update(String message);
}
class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
public void update(String message) {
System.out.println(name + " received message: " + message);
}
}
class Subject {
private List<Observer> observers = new ArrayList<>();
private String message;
public void addObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void setMessage(String message) {
this.message = message;
notifyObservers();
}
private void notifyObservers() {
for (Observer observer : observers) {
observer.update(message);
}
}
}
public class ObserverPatternExample {
public static void main(String[] args) {
Subject subject = new Subject();
Observer observer1 = new ConcreteObserver("Observer 1");
Observer observer2 = new ConcreteObserver("Observer 2");
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.setMessage("Hello, observers!");
}
}
데코레이터 패턴
데코레이터 패턴은 객체에 추가적인 기능을 동적으로 덧붙이는 패턴으로, 상속 없이도 기능 확장이 가능합니다. 예를 들어, 커피에 다양한 토핑을 추가할 수 있는 커피 메뉴를 만들 때, 데코레이터 패턴을 사용할 수 있습니다.
interface Coffee {
double cost();
String description();
}
class SimpleCoffee implements Coffee {
public double cost() {
return 2.0;
}
public String description() {
return "커피";
}
}
class MilkDecorator implements Coffee {
private Coffee coffee;
public MilkDecorator(Coffee coffee) {
this.coffee = coffee;
}
public double cost() {
return coffee.cost() + 0.5;
}
public String description() {
return coffee.description() + " + 우유";
}
}
어댑터 패턴
스프링에서 거의 쓸 일 없다고 보면 된다.