일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- 오블완
- 코딩테스트
- 데이터 사이언스
- 티스토리챌린지
- string
- 파이썬
- 자바
- 프로그래머스
- 클린코드
- IBM
- 알고리즘
- data science methodology
- 데이터사이언스
- 부스트캠프
- 데이터과학
- 코테
- Java
- AI Mathematics
- Python
- Coursera
- Clean Code
- 클린코드 파이썬
- Data Science
- 깨끗한 코드
- 문자열
- programmers
- 소프티어
- Boostcamp AI
- softeer
- 코세라
- Today
- Total
떼닝로그
[1.1] 디자인 패턴과 프로그래밍 패러다임 - 디자인 패턴 본문
디자인 패턴과 프로그래밍 패러다임
디자인 패턴
- 디자인 패턴 : 프로그램을 설계할 때 발생했던 문제점들을 객체 간의 상호 관계 등을 이용하여 해결할 수 있도록, 하나의 "규약" 형태로 만들어 놓은 것
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를 별도의 코드 수정 없이 재사용 가능하며, 단위 테스팅하기 쉬움