떼닝로그

[1.1] 디자인 패턴과 프로그래밍 패러다임 - 디자인 패턴 본문

개발로그/면접을 위한 CS 전공지식 노트

[1.1] 디자인 패턴과 프로그래밍 패러다임 - 디자인 패턴

떼닝 2024. 8. 20. 22:29

디자인 패턴과 프로그래밍 패러다임

디자인 패턴

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

1.1.1 싱글톤 패턴

 

- 싱글톤 패턴 : 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴

- 데이터베이스 연결 모듈에 많이 사용

- 하나의 인스턴스를 다른 모듈들이 공유하며 사용하기 때문에 비용 절약 가능하나, 의존성이 높음

 

자바에서의 싱글톤 패턴

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
*/

 

 

- 위 a, b의 hashcode값이 동일한 것으로 보아, 두 개는 같은 instance를 공유하고 있음을 확인할 수 있음

 

싱글톤 패턴의 단점

- TDD(Test Driven Developmnet)를 진행할 땐 주로 단위 테스트를 시행하는데, 단위 테스트는 테스트가 서로 독립적이어야 하며 테스트를 어떤 순서로든 실행할 수 있어야 함

- 싱글톤 패턴은 각 테스트마다 독립적인 인스턴스를 만들기가 어렵기 때문에 힘듦

 

의존성 주입

- 싱글톤 패턴을 사용함으로써 모듈 간의 결합이 강해지는데, 이를 해결하기 위해 의존성 주입(DI, Dependency Injection) 사용

- 의존성 : 종속성. A가 B에 의존성이 있다는 것은 곧 B의 변경 사항에 대해 A 또한 변해야 함을 의미

 

- 의존성 주입자(Dependency Injector)의 활용으로 메인 모듈이 간접적으로 의존성을 주입하는 형태로 만들 수 있음

- 메인 모듈(상위 모듈)이 하위 모듈에 대한 의존성이 떨어지는 것을 Decoupling이 된다 라고 표현.

 

의존성 주입의 장점

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

- 구현할 때 추상화 레이어를 넣고 이를 기반으로 구현체를 넣어 주기 때문에 애플리케이션 의존성 방향이 일관되고, 애플리케이션을 쉽게 추론할 수 있으며, 모듈 간의 관계들이 명확해짐

 

의존성 주입의 단점

- 모듈들이 더욱 더 분리되므로 클래스 수가 늘어나 복잡성이 증가될 수 있으며, 약간의 런타임 페널티가 생기기도 함

 

의존성 주입 원칙

- 상위 모듈은 하위 모듈에서 어떠한 것도 가져오지 않아야 한다.

- 둘 다 추상화에 의존해야 하며, 이 때 추상화는 세부 사항에 의존하지 않아야 한다.

 

1.1.2 팩토리 패턴

- 팩토리 패턴 : 객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴이자, 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴

 

- 상위 클래스와 하위 클래스의 분리로 느슨한 결합을 가지며, 상위 클래스에서는 인스턴스 생성 방식에 대해 모르기 때문에 더 많은 유연성

- 객체 생성 로직이 따로 떼어져있기 때문에 코드를 리팩터링할 때에도 한 곳만 고치면 됨. 유지 보수성 증가

 

자바의 팩토리 패턴

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
*/

 

- if ("Latte".equalsIgnoreCase(type))을 통해 문자열 비교 기반으로 로직 구성

 

1.1.3 전략 패턴

- 전략 패턴(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
        card.pay(new LUNACardStrategy("kundol@example.com", "pukubababo"));
        
        // pay by KAKAOCard
        card.pay(new KAKAOCardStrategy("Ju Hongchul", "123456789", "123", "12/01"));
    }
}

/*
400 paid using LUNACard
400 paid using KAKAOCard
*/

 

1.1.4 옵저버 패턴

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

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

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

- 주체와 객체를 따로 두지 않고 상태가 변경되는 객체를 기반으로 구축하기도 함

- 옵저버 패턴을 활용한 서비스의 대표적인 예시는 트위터

 

 

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

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

 

자바에서의 옵저버 패턴

- 주체이자 객체인 topic을 기반으로 옵저버 패턴 구현

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 {
    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();
        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) : 부모 인터페이스를 자식 클래스에서 재정의하여 구현하는 것. 반드시 부모 클래스의 메서드를 재정의하여 구현해야 함. 인터페이스를 기반으로 구현

 

1.1.5 프록시 패턴과 프록시 서버

프록시 패턴

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

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

 

프록시 서버

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

 

1.1.6 이터레이터 패턴

- 이터레이터 패턴(iterator pattern) : 이터레이터를 사용하여 컬렉션의 요소들에 접근하는 디자인 패턴. 하나의 인터페이스로 순회 가능

- 이터레이터 프로토콜 : 이터러블한 객체들을 순회할 때 쓰이는 규칙

- 이터러블한 객체 : 반복 가능한 객체로 배열을 일반화한 객체

 

1.1.7 노출모듈 패턴

- 노출모듈 패턴(revealing module pattern) : 즉시 실행 함수를 통해 private, public 같은 접근 제어자를 만드는 패턴.

- 자바스크립트는 접근 제어자가 존재하지 않고 전역 범위에서 스크립트가 실행되어 노출모듈 패턴으로 private, public 접근 제어자 구현

 

- public : 클래스에 정의된 함수에서 접근 가능하며 자식 클래스와 외부 클래스에서 접근 가능한 범위

- protected : 클래스에 정의된 함수에서 접근 가능, 자식 클래스에서 접근 가능하지만 외부 클래스에서 접근 불가능한 범위

- private : 클래스에 정의된 함수에서 접근 가능하지만 자식 클래스와 외부 클래스에서 접근 불가능한 범위

- 즉시 실행 함수 : 함수를 정의하자마자 바로 호출되는 함수. 초기화 코드, 라이브러리 내 전역 변수의 충돌 방지 등에 사용

 

1.1.8 MVC 패턴

- MVC 패턴 : 모델(Model), 뷰(View), 컨트롤러(Controller)로 이루어진 디자인 패턴 (ex. Spring)

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

- 재사용성과 확장성이 용이, 애플리케이션이 복잡해질수록 모델과 뷰의 관계가 복잡해짐

 

모델 (Model)

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

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

뷰 (View)

- 모델을 기반으로 사용자가 볼 수 있는 화면을, inputbox, checkbox, textarea 등 사용자 인터페이스 요소

- 모델이 가지고 있는 정보를 따로 저장하지 않고, 단순히 화면에 표시하는 정보만 가지고 있어야 함

- 변경이 일어날 시 컨트롤러에 이를 전달해야 함

컨트롤러 (Controller)

- 하나 이상의 모델과 하나 이상의 뷰를 잇는 다리 역할, 이벤트 등 메인 로직 담당

- 모델의 뷰의 생명 주기 관리

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

 

1.1.9 MVP 패턴

- MVP 패턴은 MVC 패턴에서 파생된 것으로, MVC의 Controller가 Presenter로 교체된 패턴

- 뷰와 프레젠터는 1:1 관계로, MVC 패턴보다 더 강한 결합을 지닌 디자인 패턴

 

1.1.10 MVVM 패턴

- MVVM 패턴은 MVC의 Controller가 View를 더 추상화한 View Model로 바뀐 패턴 (ex. Vue.js)

- 뷰와 뷰모델 사이의 양방향 데이터 바인딩을 지원

- UI를 별도의 코드 수정 없이 재사용 가능하며, 단위 테스팅하기 쉬움

 

 

Comments