디자인 패턴
728x90

디자인 패턴이란?

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

 

싱글톤 패턴(Singleton pattern)

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

보통 데이터베이스 연결 모듈에 사용

하나의 인스턴스를 만들어 놓고 해당 인스턴스를 다른 모듈들이 공유하며 사용 

인스턴스를 생성할 때 드는 비용 감소

의존성 높아짐

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

public class HelloWorld{ 
     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); 
        } 
     }
}
/*
705927765
705927765
true

1. 클래스안에 클래스(Holder), static이며 중첩된 클래스인 singleInstanceHolder를 
기반으로 객체를 선언했기 때문에 한 번만 로드되므로 싱글톤 클래스의 인스턴스는 애플리케이션 당 하나만 존재하며 
클래스가 두 번 로드되지 않기 때문에 두 스레드가 동일한 JVM에서 2개의 인스턴스를 생성할 수 없습니다. 
그렇기 때문에 동기화, 즉 synchronized를 신경쓰지 않아도 됩니다. 
2. final 키워드를 통해서 read only 즉, 다시 값이 할당되지 않도록 했습니다.
3. 중첩클래스 Holder로 만들었기 때문에 싱글톤 클래스가 로드될 때 클래스가 메모리에 로드되지 않고 
어떠한 모듈에서 getInstance()메서드가 호출할 때 싱글톤 객체를 최초로 생성 및 리턴하게 됩니다. 
*/

 

싱글톤 패턴의 단점

TDD(Test Driven Development), 단위 테스트를 할 때 테스트가 독립적이어야 함에도 싱글톤 패턴은 독립적인 인스턴스를 만들기 어렵다

 

의존성 주입(DI)

종속성이라고도 하며, A가 B에 의존성이 있다는 것은 B의 변경 사항에 대해 A 또한 변해야 한다는 것을 의미

메인 모듈과 하위 모듈 사이에 의존성 주입자(dependency injector)를 삽입해 메인 모듈이 간접적으로 의존성을 주입하게 함

메인 모듈(상위 모듈)이 하위 모듈에 대한 의존성이 떨어짐 -> 디커플링이 되다

 

의존성 주입의 장점

모듈들을 쉽게 교체할 수 있는 구조가 되어 테스팅하기 쉽고 마이그레이션하기 수월

추상화 레이어를 넣고 이를 기반으로 구현체를 넣어 주기 때문에

1. 애플리케이션 의존성 방향이 일관

2. 애플리케이션을 쉽게 추론 가능

3. 모듈 간의 관계들이 명확해짐

 

의존성 주입의 단점

1. 모듈들이 더욱 더 분리

2. 클래스 수가 늘어나 복잡성 증가

3. 런타임 페널티

 

팩토리 패턴(Factory pattern)

객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴

상속 관계에 있는 두 클래스에서 상위 클래스중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정

 

상위 클래스와 하위 클래스가 분리됨 -> 느슨한 결합

상위 클래스에서는 인스턴스 생성 방식에 대해 알 필요가 없음 -> 더 많은 유연성

객체 생성 로직이 따로 떼어져 있음 -> 코드 리팩터링 시 한 곳만 고치면 됨 -> 유지보수성 증가

/*
라떼 레시피와 아메리카노 레시피, 우유 레시피라는 구체적인 내용이 들어 있는 하위 클래스가
컨베이어 벨트를 통해 전달되고,
상위 클래스인 바리스타 공장에서
이 레시피들을 토대로 우유 등을 생산하는 생산 공정
*/
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 HelloWorld{ 
     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
*/

 

전략 패턴(Strategy pattern)

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

/*
아이템을 살 때 LUNACard로 사는 것과 KAKAOCard로 사는 것을 방식의 '전략'만 바꾸어 두 가지 방식으로 결제
*/
import java.text.DecimalFormat;
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 nm, String ccNum, String cvv, String expiryDate){
        this.name=nm;
        this.cardNumber=ccNum;
        this.cvv=cvv;
        this.dateOfExpiry=expiryDate;
    }

    @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 email, String pwd){
        this.emailId=email;
        this.password=pwd;
    }
    
    @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 cost){
        this.name=name;
        this.price=cost;
    }

    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 HelloWorld{
    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);
        
        // pay by LUNACard
        cart.pay(new LUNACardStrategy("kundol@example.com", "pukubababo"));
        // pay by KAKAOBank
        cart.pay(new KAKAOCardStrategy("Ju hongchul", "123456789", "123", "12/01"));
    }
}
/*
400 paid using LUNACard.
400 paid using KAKAOCard.
*/

passport의 전략 패턴

인증 모듈을 구현할 때 쓰는 전략 패턴을 활용한 미들웨어 라이브러리

LocalStrategy 전략 -> 서비스 내의 회원가입된 아이디와 비밀번호를 기반으로 인증

OAuth 전략 -> 페이스북, 네이버 등 다른 서비스를 기반으로 인증

 

옵저버 패턴(Observer pattern)

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

주체 -> 객체의 상태 변화를 보고 있는 관찰자

옵저버들 -> 이 객체의 상태 변화에 따라 전달되는 메서드 등을 기반으로 추가 변화 사항이 생기는 객체들

주로 이벤트 기반 시스템에 사용하며 MVC(Model-View-Controller) 패턴에 사용

-> 주체인 모델(model)에서 변경 사항이 생겨 update() 메서드로 옵저버인 뷰(view)에 알려 주고 이를 기반으로 컨트롤러(controller) 작동  

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 HelloWorld { 
    public static void main(String[] args) {
        Topic topic = new Topic(); 
        // 객체 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("amumu is op champion!!"); 
    }
}
/*
Message sended to Topic: amumu is op champion!!
a:: got message >> amumu is op champion!!
b:: got message >> amumu is op champion!!
c:: got message >> amumu is op champion!!
*/

 

자바: 상속과 구현

상속(extends)

자식 클래스가 부모 클래스의 메서드 등을 상속받아 사용하며 자식 클래스에서 추가 및 확장을 할 수 있음

재사용성, 중복성의 최소화

일반클래스, abstract 클래스를 기반으로 구현

 

구현(implements)

부모 인터페이스자식 클래스에서 재정의하여 구현

반드시 부모 클래스의 메서드를 재정의하여 구현해야 함

인터페이스를 기반으로 구현

 

프록시 패턴(proxy pattern)

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

객체의 속성, 변환 등을 보완하며 보안, 데이터 검증, 캐싱, 로깅에 사용

 

프록시 서버(proxy server)

서버와 클라이언트 사이에서 클라이언트가 자신을 통해 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해 주는 컴퓨터 시스템이나 응용 프로그램

프록시 서버에서의 캐싱  

캐시 안에 정보를 담아 두고, 캐시 안에 있는 정보를 요구하는 요청에 대해 다시 저 멀리 있는 원격 서버에 요청하지 않고 캐시 안에 있는 데이터를 활용하는 것

불필요하게 외부와 연결하지 않기 때문에 트래픽을 줄일 수 있음

 

프록시 서버 이용 예시

nginx CloudFlare CORS 에러 프록시 서버를 이용해
CORS 에러 해결

 

이터레이터 패턴(iterator pattern)

이터레이터(iterator)를 이용하여 컬렉션(collection)의 요소들에 접근하는 디자인 패턴

여러 가지 자료형의 구조와는 상관없이 이터레이터라는 하나의 인터페이스로 순회 가능

 

노출모듈 패턴(revealing module pattern)

즉시 실행 함수를 통해 private, public 같은 접근 제어자를 만드는 패턴

 

MVC 패턴

Model, View, Controller로 이루어진 디자인 패턴

개발 프로세스에서 각각의 구성 요소에만 집중해서 개발 가능

재사용성과 확장성 용이

애플리케이션이 복잡해질수록 모델과 뷰의 관계가 복잡해질 수 있음

모델(Model)

애플리케이션의 데이터데이터베이스, 상수, 변수

뷰에서 데이터를 생성하거나 수정하면 컨트롤러를 통해 모델을 생성하거나 갱신

뷰(View)

inputbox, checkbox, textarea 등 사용자 인터페이스 요소

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

모델이 가지고 있는 정보를 따로 저장하지 않아야 하며, 단순히 사각형 모양 등 화면에 표시하는 정보만 가지고 있어야 함

변경이 일어나면 컨트롤러에 전달

컨트롤러(Controller)

하나 이상의 모델과 하나 이상의 뷰를 잇는 다리 역할

이벤트 등 메인 로직

모델과 뷰의 생명주기 관리

모델이나 뷰의 변경 통지를 받으면 이를 해석하여 각각의 구성 요소에 해당 내용에 대해 알려 줌

 

MVP 패턴

MVC 패턴의 Controller가 프레젠터(presenter)로 교체된 패턴

뷰와 프레젠터일대일 관계이기 때문에 MVC 패턴보다 더 강한 결합을 가진

 

MVVM 패턴

MVC 패턴의 Controller가 뷰모델(view model)로 바뀐 패턴

뷰모델이란, 뷰를 더 추상화한 계층이며 커맨드와 데이터 바인딩을 가지는 것이 특징

뷰와 뷰모델 사이의 양방향 데이터 바인딩을 지원하며 UI를 별도의 코드 수정 없이 재사용할 수 있고 단위 테스팅하기 쉬움

 

 

728x90

'CS' 카테고리의 다른 글

CS 예상 질문  (0) 2022.12.13
프로그래밍 패러다임  (0) 2022.12.13