구분 | 설명 | 비유 |
---|---|---|
라이브러리 | 내가 호출해서 사용하는 도구. 제어 흐름을 개발자가 관리함 | ✂️ 가위를 내가 직접 사용하는 것 |
프레임워크 | 프레임워크가 내 코드를 호출함. 제어 흐름을 프레임워크가 관리함 | ✈️ 비행기를 타고 이동하는 것 |
💡 제어의 역전(Inversion of Control, IoC)
이 개념은 프레임워크를 설명할 때 자주 등장합니다.
프로그램을 설계할 때 발생하는 문제점들을 객체 간의 관계를 통해 해결할 수 있도록 정형화된 설계 방식
💡 디자인 패턴은 재사용 가능하고 검증된 설계 방식으로, 개발자의 의사소통을 원활하게 하고 유지보수를 쉽게 만들어 줍니다.
🗃️ "하나의 객체를 모두가 공유" — 메모리 절약 + 일관성 보장
class Singleton {
private static class SingleInstanceHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static synchronized 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
는 동일한 인스턴스를 참조하므로 ==
비교 결과가 true
입니다.
A가 B의 기능을 사용하고 있다면, A는 B에 의존하고 있다고 합니다.
즉, B가 변경되면 A도 영향을 받게 됩니다.
유연한 변경 → 모듈 교체가 쉬움
유지보수 용이
테스트 쉬움 → 테스트용 객체를 쉽게 주입 가능
마이그레이션 용이
💡 예: MySQL에서 Oracle로 DB 교체 시, DI 구조라면 쉽게 DB 객체 교체 가능
📌 객체 생성 코드를 분리함으로써, 클래스 간의 결합도를 낮추고 유지보수성을 향상시킵니다.
역할 | 설명 |
---|---|
Coffee | 추상 클래스 (Product) – 인터페이스 제공 |
Latte , Americano , DefaultCoffee | 구체 클래스 (Concrete Product) – 실제 객체 생성 및 로직 정의 |
CoffeeFactory | 팩토리 클래스 (Creator) – 어떤 객체를 생성할지 결정하고 생성 책임을 가짐 |
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 CoffeeTest {
public static void main(String[] args) {
Coffee coffee1 = CoffeeFactory.getCoffee("Latte", 4500);
Coffee coffee2 = CoffeeFactory.getCoffee("Americano", 4000);
Coffee coffee3 = CoffeeFactory.getCoffee("Mocha", 0); // 정의되지 않은 커피
System.out.println(coffee1); // Hi this coffee is 4500
System.out.println(coffee2); // Hi this coffee is 4000
System.out.println(coffee3); // Hi this coffee is -1
}
}
CoffeeFactory.getCoffee(type, price)
만 호출하면 적절한 커피 객체가 생성됨 ☕Latte
, Americano
클래스의 세부 구현을 몰라도 객체 생성 가능📌 정리
팩토리 패턴은 “무엇을 만들지 알지만, 어떻게 만들지는 몰라도 된다”는 철학을 기반으로 합니다.
→ 객체 생성이 자주 바뀌거나, 확장 가능한 구조가 필요한 경우 특히 유용합니다.
구성 요소 | 설명 |
---|---|
PaymentStrategy | 전략(Strategy) 인터페이스 – 공통 행위 정의 |
KAKAOCardStrategy , LUNACardStrategy | 구체 전략 – 각기 다른 행위(결제 방식) 구현 |
ShoppingCart | 컨텍스트(Context) – 전략 객체를 주입받아 내부에서 행위를 위임 |
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<>();
}
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);
cart.pay(new LUNACardStrategy("kundol@example.com", "pukubababo"));
cart.pay(new KAKAOCardStrategy("Ju hongchul", "123456789", "123", "12/01"));
}
}
400 paid using LUNACard.
400 paid using KAKAOCard.
PaymentStrategy
는 결제 알고리즘을 추상화한 인터페이스KAKAOCardStrategy
, LUNACardStrategy
는 전략 객체, 각각의 결제 방식 구현ShoppingCart
는 전략 객체를 전달받아 pay()
동작을 위임예: 팔로우한 유저가 글을 올리면 알림이 오는 구조
구성 요소 | 역할 설명 |
---|---|
Subject | 옵저버 등록/해제/알림 처리 인터페이스 |
Observer | 상태 변경 시 알림을 받는 객체 인터페이스 |
Topic | 실제 주체, 상태가 변경되면 옵저버에게 알림 |
MyObserver | 실제 옵저버 클래스, update() 메서드 구현 |
// Observer 인터페이스
interface Observer {
void update();
void setSubject(Subject sub);
}
// Subject 인터페이스
interface Subject {
void register(Observer obj);
void unregister(Observer obj);
void notifyObservers();
Object getUpdate(Observer obj);
}
// Subject 구현체
class Topic implements Subject {
private List<Observer> observers;
private String message;
private boolean changed;
public Topic() {
observers = new ArrayList<>();
message = "";
changed = false;
}
@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() {
if (!changed) return;
for (Observer obj : observers) {
obj.update();
}
changed = false;
}
@Override
public Object getUpdate(Observer obj) {
return message;
}
public void postMessage(String msg) {
System.out.println("Message Posted: " + msg);
this.message = msg;
this.changed = true;
notifyObservers();
}
}
// Observer 구현체
class MyObserver implements Observer {
private String name;
private Subject topic;
public MyObserver(String name) {
this.name = name;
}
@Override
public void update() {
String msg = (String) topic.getUpdate(this);
System.out.println(name + ": Consuming message → " + msg);
}
@Override
public void setSubject(Subject sub) {
this.topic = sub;
}
}
// 실행
public class ObserverPatternDemo {
public static void main(String[] args) {
Topic topic = new Topic();
Observer user1 = new MyObserver("User1");
Observer user2 = new MyObserver("User2");
Observer user3 = new MyObserver("User3");
user1.setSubject(topic);
user2.setSubject(topic);
user3.setSubject(topic);
topic.register(user1);
topic.register(user2);
topic.register(user3);
topic.postMessage("옵저버 패턴 알림 메시지입니다.");
}
}
Message Posted: 옵저버 패턴 알림 메시지입니다.
User1: Consuming message → 옵저버 패턴 알림 메시지입니다.
User2: Consuming message → 옵저버 패턴 알림 메시지입니다.
User3: Consuming message → 옵저버 패턴 알림 메시지입니다.
postMessage()
를 호출하면 내부 메시지가 변경되고, 이때 notifyObservers()
를 통해 모든 옵저버에게 통보update()
를 통해 최신 메시지를 받아 처리Vue.js 양방향 바인딩: 데이터 변화 → DOM 자동 반영
Model-View-Controller(MVC) 구조:
extends
)implements
)구분 | 추상 클래스 | 인터페이스 |
---|---|---|
상속 방식 | extends | implements |
변수 | 인스턴스 변수 사용 가능 | 상수만 선언 가능 (Java 8 이후 default 메서드 허용) |
상속 수 | 단일 상속만 가능 | 다중 구현 가능 |
용도 | 일부 구현 + 공통 기능 제공 | 명세(기능 선언) 중심 구조 |
구성 요소 | 설명 |
---|---|
Image 인터페이스 | 공통 인터페이스 (실제 객체와 프록시 객체가 구현) |
RealImage | 실제 동작을 수행하는 객체 |
ProxyImage | 실제 객체 대신 동작을 제어하는 프록시 객체 |
interface Image {
void display();
}
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk(); // 무거운 작업
}
private void loadFromDisk() {
System.out.println("Loading " + filename);
}
@Override
public void display() {
System.out.println("Displaying " + filename);
}
}
class ProxyImage implements Image {
private RealImage realImage;
private String filename;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename); // 지연 초기화
}
realImage.display();
}
}
public class ProxyPatternDemo {
public static void main(String[] args) {
Image image = new ProxyImage("myPhoto.jpg");
System.out.println("이미지를 처음 요청할 때:");
image.display(); // 실제 객체 생성 + 표시
System.out.println("이미지를 다시 요청할 때:");
image.display(); // 기존 객체로 표시
}
}
이미지를 처음 요청할 때:
Loading myPhoto.jpg
Displaying myPhoto.jpg
이미지를 다시 요청할 때:
Displaying myPhoto.jpg
ProxyImage
는 내부적으로 RealImage
를 필요할 때만 생성기능 | 설명 |
---|---|
캐싱 | 자주 요청되는 데이터를 프록시 서버에 저장하여 서버에 직접 요청하지 않고 응답 가능 |
보안 | 예: CloudFlare를 앞단에 두어 서버 IP와 포트를 숨김, 악의적 트래픽 차단 가능 |
HTTPS 지원 | NGINX나 CloudFlare를 통해 손쉽게 인증서를 적용해 HTTPS 구축 가능 |
CORS 해결 | 프록시 서버가 요청을 중계함으로써 포트가 다른 요청을 우회 가능 |
로드밸런싱 | 여러 서버에 요청을 분산하여 트래픽 부하를 분산 |
localhost:3000
에서 실행 시, 백엔드(localhost:12010
)와 포트가 달라 CORS 에러 발생vite.config.js
나 vue.config.js
에서 프록시 설정을 통해 문제 해결Iterator
)를 통해 요소를 순회 가능자바에서는
java.util.Iterator
인터페이스가 이 패턴을 구현하고 있음
구성 요소 | 설명 |
---|---|
Iterator | 순회 동작을 정의하는 인터페이스 (hasNext() , next() ) |
Aggregate | 컬렉션 인터페이스 또는 추상 클래스 |
ConcreteAggregate | 실제 데이터를 보관하는 클래스 |
ConcreteIterator | 실제 순회 동작을 구현하는 클래스 |
자바에서는 대부분의 컬렉션이 자체적으로
Iterator
를 반환하는.iterator()
메서드를 제공
import java.util.*;
public class JavaIteratorExample {
public static void main(String[] args) {
// List 순회
List<String> list = Arrays.asList("Apple", "Banana", "Cherry");
Iterator<String> listIterator = list.iterator();
System.out.println("List 순회:");
while (listIterator.hasNext()) {
System.out.println(listIterator.next());
}
// Set 순회
Set<Integer> set = new HashSet<>(Arrays.asList(10, 20, 30));
Iterator<Integer> setIterator = set.iterator();
System.out.println("\nSet 순회:");
while (setIterator.hasNext()) {
System.out.println(setIterator.next());
}
// Map 순회 (EntrySet 기준)
Map<String, Integer> map = new HashMap<>();
map.put("Alice", 90);
map.put("Bob", 85);
map.put("Charlie", 95);
Iterator<Map.Entry<String, Integer>> mapIterator = map.entrySet().iterator();
System.out.println("\nMap 순회:");
while (mapIterator.hasNext()) {
Map.Entry<String, Integer> entry = mapIterator.next();
System.out.println(entry.getKey() + " → " + entry.getValue());
}
}
}
List 순회:
Apple
Banana
Cherry
Set 순회:
10
20
30
Map 순회:
Alice → 90
Bob → 85
Charlie → 95
.iterator()
를 통해 순회 로직을 통일구성 요소 | 역할 | 스프링 예시 |
---|---|---|
Model | 애플리케이션의 데이터, 비즈니스 로직 관리 | @Service , @Repository , Entity |
View | 사용자에게 보여지는 UI 요소 | Thymeleaf , HTML , JSON |
Controller | 사용자 요청을 받고 결과를 반환하는 중간 제어자 | @Controller , @RestController |
[1] 사용자 요청: "/hello"
↓
[2] Controller (@GetMapping("/hello"))
↓
[3] Model(Service/Repository)에서 데이터 조회
↓
[4] View 반환 (HTML or JSON)
watch
, computed
, v-model
프로그래밍을 바라보는 사고 방식 또는 설계 방법론.
문제를 어떤 관점으로 해결할지에 대한 체계.
주요 패러다임:
- 선언형: 무엇을 할 것인지 중심 (함수형)
- 명령형: 어떻게 할 것인지 중심 (절차지향, 객체지향)
특징 | 설명 |
---|---|
순수 함수 | 동일 입력 → 동일 출력. 외부 상태 변화 없음 (전역 변수 사용 X) |
고차 함수 | 함수를 인자로 전달하거나 반환함 (함수를 값처럼 사용) |
일급 객체 | 함수를 변수에 할당하거나 매개변수로 전달 가능해야 함 |
일급 객체 조건
- 변수에 할당 가능
- 매개변수로 전달 가능
- 함수의 반환값으로 사용 가능
Function<T, R>
, 람다식map
, filter
, reduce
특징 | 설명 |
---|---|
추상화 | 핵심만 뽑아냄 (ex. 엘리베이터 → "올라가기", "내려가기") |
캡슐화 | 데이터 + 메서드 은닉, 외부는 인터페이스만 접근 |
상속성 | 코드 재사용, 유지보수성 향상 |
다형성 | 동일 인터페이스에 여러 구현 가능 (오버라이딩, 오버로딩) |
원칙 | 설명 |
---|---|
SRP (단일 책임 원칙) | 클래스는 하나의 책임만 |
OCP (개방-폐쇄 원칙) | 확장에 열려 있고, 변경에는 닫혀 있어야 함 |
LSP (리스코프 치환 원칙) | 하위 클래스는 상위 클래스의 역할을 대체할 수 있어야 함 |
ISP (인터페이스 분리 원칙) | 하나의 인터페이스는 하나의 역할에 집중해야 함 |
DIP (의존 역전 원칙) | 구현이 아닌 추상(인터페이스)에 의존해야 함 |
const ret = [1, 2, 3, 4, 5, 11, 12];
let a = 0;
for (let i = 0; i < ret.length; i++) {
a = Math.max(ret[i], a);
}
console.log(a); // 12
정답은 없다.
상황, 문제 도메인, 성능, 유지보수, 협업 방식 등에 따라 적절한 패러다임을 선택하는 것이 핵심
예시: