BACK/JAVA

[JAVA] 디자인 패턴 정리

연듀 2022. 11. 11. 16:26

 

 

 

디자인 패턴이란 ?

프로그램을 설계할 때 발생했던 문제점들을 객체 간의 상호 관계 등을 이용하여 해결할 수 있도록 하나의 "규약" 형태로 만들어 놓은 것을 의미한다.

 

 

 

1. 싱글톤 패턴

 

하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴

하나의 인스턴스를 만들어 놓고 해당 인스턴스를 다른 모듈들이 공유하며 사용하기 때문에 인스턴스를 생성할 때 드는 비용이 줄어드는 장점이 있다.

하지만 의존성이 높아지고 테스트가 서로 독립적인 단위 테스트를 주로 하는 TDD를 할때 걸림돌이 된다는 단점이 있다. 

스프링 컨테이너 객체 관리, DB 커넥션, 스레드 풀 객체 생성시 사용된다. 

 

class Singleton{
    private static class singleInstanceHolder{
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance(){
        return singleInstanceHolder.INSTANCE;
    }
}

public class Main {
    public static void main(String[] args) {
        Singleton a = Singleton.getInstance();
        Singleton b = Singleton.getInstance();
        System.out.println(a.hashCode());
        System.out.println(b.hashCode());
        if (a == b) {
            System.out.println(true);
        }

        /*
        1435804085
        1435804085
        true
        */
    }
}

 

 

2. 팩토리 패턴

 

객체 생성 부분을 떼어내 추상화한 패턴

객체를 직접 생성하지 않고 객체를 생성하는 팩토리 객체를 사용한다. 

상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정한다.

상위 클래스와 하위 클래스가 분리돼 느슨한 결합을 가지며 유연성을 가지고 유지 보수성이 증가된다.

예로는 스프링의 DI가 있다.

 

 

abstract class Coffee{
    public abstract int getPrice();

    @Override
    public String toString() {
        return "Hi this coffee is " + this.getPrice();
    }
}

class CoffeeFactory{
    public static Coffee getCoffee(String type, int price){
        if("Latte".equalsIgnoreCase(type)) return new Latte(price);
        else if("Americano".equalsIgnoreCase(type)) return new Americano(price);
        else return new DefaultCoffee();
    }
}

class DefaultCoffee extends Coffee{
    private int price;
    public DefaultCoffee(){
        this.price = -1;
    }

    @Override
    public int getPrice() {
        return this.price;
    }
}
class Latte extends Coffee{
    private int price;
    public Latte(int price){
        this.price = price;
    }

    @Override
    public int getPrice() {
        return this.price;
    }
}

class Americano extends Coffee{
    private int price;
    public Americano(int price){
        this.price = price;
    }

    @Override
    public int getPrice(){
        return this.price;
    }
}

public class Main{
    public static void main(String[] args) {
        Coffee latte = CoffeeFactory.getCoffee("Latte", 4000);
        Coffee ame = CoffeeFactory.getCoffee("Americano", 3000);
        System.out.println("factory latte :: "+latte);
        System.out.println("factory ame :: "+ame);

       /* factory latte :: Hi this coffee is 4000
        factory ame :: Hi this coffee is 3000*/
    }
}

 

 

 

3. 전략 패턴

객체의 행위를 바꾸고 싶은 경우 직접 수정하지 않고 전략이라고 부르는 캡슐화한 알고리즘을 컨텍스트 안에서 바꿔주면서 상호 교체가 가능하게 만드는 패턴

전략 객체를 생성해 컨텍스트에 전략 객체를 주입하여 해당 인터페이스를 구현하도록 한다. 

 

import java.util.ArrayList;
import java.util.List;

// 결제 방식의 전략만 바꿔서 두가지 방식으로 결제하는 것을 구현 
interface PaymentStrategy{
    public void pay(int amount);
}

class KAKAOCardStrategy implements PaymentStrategy{
    private String name;
    private String cardNumber;
    private String cvv;
    private String dateOfExpiry;

    public KAKAOCardStrategy(String name, String cardNumber, String cvv, String dateOfExpiry) {
        this.name = name;
        this.cardNumber = cardNumber;
        this.cvv = cvv;
        this.dateOfExpiry = dateOfExpiry;
    }

    @Override
    public void pay(int amount){
        System.out.println(amount + " paid using KAKAOCard.");
    }
}

class LUNACardStrategy implements PaymentStrategy{
    private String emailId;
    private String password;

    public LUNACardStrategy(String emailId, String password) {
        this.emailId = emailId;
        this.password = password;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid using LUNACard.");
    }
}

class Item{
    private String name;
    private int price;

    public Item(String name, int price) {
        this.name = name;
        this.price = price;
    }
    public String getName() {
        return name;
    }
    public int getPrice() {
        return price;
    }
}

class ShoppingCart{
    List<Item> items;

    public ShoppingCart(){
        this.items = new ArrayList<Item>();
    }

    public void addItem(Item item){
        this.items.add(item);
    }

    public void removeItem(Item item){
        this.items.remove(item);
    }

    public int calculateTotal(){
        int sum = 0;
        for(Item item : items) {
            sum+=item.getPrice();
        }
        return sum;
    }

    public void pay(PaymentStrategy paymentMethod){
        int amount = calculateTotal();
        paymentMethod.pay(amount);
    }
}

public class Main{
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();

        Item A = new Item("kundolA", 100);
        Item B = new Item("kundolB", 300);

        cart.addItem(A);
        cart.addItem(B);

        cart.pay(new LUNACardStrategy("kundol@example.com","password"));
        cart.pay(new KAKAOCardStrategy("kdy","1234","123","12/01"));

        /*400 paid using LUNACard.
        400 paid using KAKAOCard.*/
    }
}

 

 

4. 옵저버 패턴

 

주체가 어떤 객체(subject)의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 패턴

주로 이벤트 기반 시스템에 사용하며 MVC 패턴에도 사용된다. 

 

import java.util.ArrayList;
import java.util.List;

interface Subject{
    public void register(Observer obj);
    public void unregister(Observer obj);
    public void notifyObservers();
    public Object getUpdate(Observer obj);

}

interface Observer{
    public void update();
}

class Topic implements Subject{ // topic: 주제이자 객체 
    private List<Observer> observers;
    private String message;

    public Topic(){
        this.observers = new ArrayList<>();
        this.message = "";
    }

    @Override
    public void register(Observer obj) {
        if(!observers.contains(obj)) observers.add(obj);
    }

    @Override
    public void unregister(Observer obj) {
        observers.remove(obj);
    }

    @Override
    public void notifyObservers() {
        this.observers.forEach(Observer::update);
    }

    @Override
    public Object getUpdate(Observer obj) {
        return this.message;
    }

    public void postMessage(String msg){
        System.out.println("Message sended to Topic: "+msg);
        this.message = msg;
        notifyObservers();
    }
}

class TopicSubscriber implements Observer{
    private String name;
    private Subject topic;

    public TopicSubscriber(String name, Subject topic) {
        this.name = name;
        this.topic = topic;
    }

    @Override
    public void update() {
        String msg = (String) topic.getUpdate(this);
        System.out.println(name+":: got message >> "+msg);
    }
}
public class Main{
    public static void main(String[] args) {
        Topic topic = new Topic();
        // 옵저버를 선언할 때 해당 이름과 어떠한 토픽의 옵저버가 될 것인지 정함
        Observer a = new TopicSubscriber("a", topic);
        Observer b = new TopicSubscriber("b", topic);
        Observer c = new TopicSubscriber("c", topic);
        // 옵저버들을 옵저버 목록에 등록 
        topic.register(a);
        topic.register(b);
        topic.register(c);

        topic.postMessage("hello!!"); // 옵저버들에게 메세지 전달

     /*   Message sended to Topic: hello!!
                a:: got message >> hello!!
                b:: got message >> hello!!
                c:: got message >> hello!!*/
    }
}

 

5. 프록시 패턴

대상 객체에 접근하기 전 그 접근에 대한 흐름을 가르체 객체 앞단의 인터페이스 역할을 하는 디자인 패턴.

프록시 서버로도 활용된다.

(프록시 서버: 클라이언트가 자신을 거쳐 다른 네트워크에 접속할 수 있도록 해주는 서버)

 

 

6. 이터레이터 패턴

이터레이터를 사용하여 컬렉션의 요소들에 접근하는 디자인 패턴.

컬렉션 구현 방법을 노출시키지 않으면서도 그 집합체안에 들어있는 모든 항목에 접근할 수 있게 해 주는 방법을 제공해 준다.

 

7. MVC 패턴

모델, 뷰, 컨트롤러로 이루어진 디자인패턴.

애플리케이션의 구성 요소를 세가지 역할로 구분하여 개발 프로세스에서 각각의 구성 요소에만 집중해 개발할 수 있다.

재사용성과 확장성이 용이하지만, 모델과 뷰의 의존성이 높아 애플리케이션이 복잡해질수록 모델과 뷰의 관계가 복잡해지는 단점이 있다.

 

모델: 애플리케이션의 데이터인 데이터베이스, 상수, 변수 등

뷰: 모델을 기반으로 사용자가 볼 수 있는 화면

컨트롤러: 모델과 뷰를 잇는 다리 역할. 이벤트 등 메인 로직

 

8. MVP 패턴

MVC에서 컨트롤러가 프레젠터(presenter)로 교체된 패턴

뷰와 프레젠터는 일대일 관계로 MVC패턴 보다 더 강한 결합을 지닌다.

 

 

9. MVVM 패턴

MVC에서 컨트롤러가 뷰모델(view model)로 바뀐 패턴

커맨드 패턴과 뷰와 뷰모델사이의 데이터 바이딩을 사용해 구현하여 뷰와 모델의 의존성을 없앤다.