-
디자인 패턴컴퓨터/정보처리기사 2025. 4. 22. 22:31728x90반응형
- 디자인 패턴이란?
소프트웨어 설계를 효율적으로 하기 위한 재사용 가능한 설계 방식
생성 패턴 구조 패턴 (ABCDFFP) 행위 패턴 Abstract Factory Adapter Command Builder Bridge Interpreter Factory Method Composite Iterator Prototype Decorator Mediator Singleton Facade Observer Flyweight Chain of Responsibility Proxy Memento Strategy Template Method Visitor 행위 패턴(시험 빈칸 문제)
클래스나 객체들이 서로 상호작용하는 방법이나 책임 분배 방법을 정의하는 패턴
하나의 객체로 수행할 수 없는 작업을 여러 객체로 분배하면서 결합도를 최소화 할 수 있도록 도와준다.
반복적으로 사용되는 객체들의 상호작용을 패턴화한 것으로 클래스나 객체들이 상호작용하는 방법과 책임을 분산하는 패턴
책임 연쇄, 반복자, 옵저버 패턴 등이 있다.
Chain of Responsibility
요청을 처리할 수 있는 객체를 여러 개로 두어서 요청하는 객체와 처리하는 객체 사이의 결합도를 없앤다. 요청을 처리할 수 있는 객체를 만날 때까지 객체 고리(chain)을 따라서 요청을 전달한다.
비유: 고객센터 전화했을 때, 자동 응답기 -> 상담사 -> 엔지니어
각 핸들러에서 요청을 처리할 수 있는지 여부를 따지고 불가능하면 다음 핸들러로 떠넘기는 과정 진행
url 파싱하는 핸들러 예제
setNext하고
process 실행 후
nextHandler.run()
Command
여러 기능을 실행할 수 있도록 재사용성이 높은 클래스를 설계하는 패턴이다.
(매개변수를 사용하여 여러 요구사항을 처리할 수 있음)
요청을 큐에 저장하거나 로그로 기억하고 재실행할 수 있다
Command 패턴은 쉽게 말하면,
**“요청(명령)을 객체로 캡슐화해서 저장하고 실행할 수 있게 하는 패턴”**이야.
즉,
• 요청을 나중에 실행하거나,
• **요청 취소(undo)**를 하거나,
• 여러 요청을 큐에 넣어 순차적으로 실행할 수 있게 해줘.
⸻
일상 예시로 설명
리모컨 예시
• 리모컨의 버튼을 누르면 TV가 켜짐
• 버튼 하나마다 “명령”이 정해져 있음
여기서:
• TV: 실제 작업을 하는 Receiver
• 버튼 누름 행위: 명령을 전달하는 Invoker
• 버튼에 저장된 명령: 캡슐화된 Command 객체
⸻
핵심 구성요소 3가지
구성요소 역할
Command 실행할 명령을 객체로 추상화 (execute() 메서드 포함)
Receiver 명령을 실제로 수행하는 대상 (예: TV, 전등)
Invoker 명령을 실행하거나 큐에 저장하는 객체 (예: 리모컨)
⸻
코드 예시 (TypeScript 스타일)
// Receiver
class Light {
turnOn() {
console.log("불을 켰어요!");
}
turnOff() {
console.log("불을 껐어요!");
}
}
// Command 인터페이스
interface Command {
execute(): void;
}
// Concrete Command들
class LightOnCommand implements Command {
constructor(private light: Light) {}
execute() {
this.light.turnOn();
}
}
class LightOffCommand implements Command {
constructor(private light: Light) {}
execute() {
this.light.turnOff();
}
}
// Invoker
class RemoteControl {
private command?: Command;
setCommand(command: Command) {
this.command = command;
}
pressButton() {
this.command?.execute();
}
}
// 사용 예
const light = new Light();
const turnOn = new LightOnCommand(light);
const turnOff = new LightOffCommand(light);
const remote = new RemoteControl();
remote.setCommand(turnOn);
remote.pressButton(); // 불을 켰어요!
remote.setCommand(turnOff);
remote.pressButton(); // 불을 껐어요!
⸻
어디에 쓰일까?
• 실행 취소(Undo) 기능이 필요한 경우
• 작업 예약, 큐잉, 매크로 만들기
• UI 버튼에 명령 매핑 (예: 에디터의 Ctrl+Z, Ctrl+Y)
• 예: 게임 명령, 메뉴 시스템, 작업 이력 관리 등
⸻
요약
• Command 패턴은 행동 자체를 객체로 만드는 것
• 덕분에 저장, 취소, 재실행, 큐 처리 같은 유연한 처리가 가능해
• 개발에서 동작을 나중에 실행하거나 저장하고 싶을 때 유용함
더 깊게 보고 싶으면 undo 구현, 명령 큐 등으로 이어서 설명해줄게!
Iterator(반복자)
반복이 필요한 자료구조를 모두 동일한 인터페이스를 통해 접근할 수 있도록 하는 패턴이다.
자료 구조와 같이 접근이 잦은 객체에 대해 동일한 인터페이스를 사용하도록 하는 패턴 (next와 같은 공통 인터페이스)
내부 표현 방법의 노출 없이 순차적인 접근이 가능하다
Mediator (중재자) 패턴
객체간의 상호작용을 캡슐화하여 복잡성을 줄이기 위해 사용하는 패턴이다.
객체 간 직접 통신을 막고, 중재자 객체를 통해 통신하도록 한다.
객체 간의 복잡한 통신을 중간에 중재자를 두어 간단하게 만드는 패턴
결합도(Coupling)를 줄여 유지 보수성을 높인다
한 객체의 변경이 여러 객체에 영향을 주지 않도록 설계
MVC 패턴의 Controller 역할과 유사 (Controller가 View와 Model 간의 중재 역할을 함)
핵심 개념
• 객체들이 직접 서로 참조하거나 통신하지 않고,
• 중재자(Mediator) 객체를 통해서만 통신하게 만들어.
• 객체들끼리의 의존성을 낮추고, 코드가 깔끔해진다
예시
상황: 채팅방
• 유저 A, B, C가 있어.
• A가 B에게 메시지를 보내고 싶어.
• 그런데 A가 B를 직접 참조하지 않고, 채팅방(ChatRoom) 이라는 중재자를 통해서 메시지를 보냄.
// 중재자
class ChatRoom {
sendMessage(user: User, message: string) {
console.log(`${user.name}: ${message}`);
}
}
// 유저
class User {
constructor(public name: string, private chatRoom: ChatRoom) {}
send(message: string) {
this.chatRoom.sendMessage(this, message);
}
}
const chatRoom = new ChatRoom();
const alice = new User("Alice", chatRoom);
const bob = new User("Bob", chatRoom);
alice.send("안녕, Bob!"); // ChatRoom이 중재해서 출력
포인트
• User끼리는 서로 모름
• ChatRoom이 모든 메시지를 중개
• 그래서 User들이 서로 복잡하게 얽히지 않음
⸻
언제 쓰면 좋을까?
• 객체 간의 통신이 너무 복잡해서 얽히고설킬 때
• 객체 수가 많아질수록 의존성 관리가 어려울 때
• 예: UI 컴포넌트 간 이벤트 통신, 항공 교통 제어 시스템 등
- Memento
- 객체의 상태를 저장해두었다가 복원해야 할 경우 사용하는 패턴이다. (캡슐화 사용)
메멘토 패턴(Memento Pattern)은 어떤 객체의 상태를 저장해뒀다가 나중에 다시 복원할 수 있게 해주는 디자인 패턴이야.
즉, “되돌리기(undo)” 기능을 구현할 때 자주 쓰여!
⸻
예시로 쉽게 설명
상황: 글쓰기 앱 (메모장)
• 사용자가 글을 쓰다가, “저장” 버튼을 누르면 현재 글 상태를 저장함
• 실수로 글을 지웠을 때, “되돌리기” 버튼을 누르면 이전 상태로 복원됨
⸻
주요 구성요소 3가지
1. Originator (원본): 상태를 가진 객체. 예: 글쓰기 앱
2. Memento (기억): 상태를 저장하는 객체. 내부 구조는 외부에서 볼 수 없음
3. Caretaker (관리자): Memento를 보관만 하는 객체. 내부 상태는 모름
⸻
코드 예시 (TypeScript 스타일)
// 1. Memento
class TextMemento {
constructor(private text: string) {}
getText() {
return this.text;
}
}
// 2. Originator
class TextEditor {
private text: string = "";
write(text: string) {
this.text += text;
}
save(): TextMemento {
return new TextMemento(this.text);
}
restore(memento: TextMemento) {
this.text = memento.getText();
}
getText() {
return this.text;
}
}
// 3. Caretaker
const editor = new TextEditor();
const history: TextMemento[] = [];
editor.write("안녕하세요. ");
history.push(editor.save()); // 상태 저장
editor.write("저는 메멘토 패턴을 설명하고 있어요.");
history.push(editor.save()); // 또 저장
editor.write(" 실수로 이 문장은 잘못 썼어요.");
console.log(editor.getText());
// 안녕하세요. 저는 메멘토 패턴을 설명하고 있어요. 실수로 이 문장은 잘못 썼어요.
editor.restore(history[1]); // 복원!
console.log(editor.getText());
// 안녕하세요. 저는 메멘토 패턴을 설명하고 있어요.
⸻
요약
• **상태 저장용 객체(Memento)**를 만들어 두고
• 되돌릴 수 있도록 저장해두는 구조
• 내부 구조(캡슐화)는 보존하면서 상태를 외부에 넘기지 않고 저장하는 게 핵심
Observer
(기출 지문)
한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들에게 연락이 가서 자동으로 내용이 갱신되는 방식
일대다의 의존성을 정의하는 패턴
상호 작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 패턴
객체 사이에 일대다 종속성을 정의하고 한 객체의 상태가 변하면 종속된 다른 객체에 통보를 하고 자동으로 수정이 일어나게 한다.
한 객체의 상태가 변화하면 객체에 상속되어 있는 다른 객체들에게 변화된 상태를 전달해주는 패턴이다.
일대다 관계를 가지며, 주로 분산된 시스템 간에 이벤트를 생성·발행(Publish)하고, 이를 수신(Subscribe)해야 할 때 이용한다.
State
객체의 내부 상태에 따라 행위를 변경할 수 있게 한다. (특정 메소드가 객체의 상태에 따라 다른 기능을 수행함)
Strategy
클래스별로 캡슐화되어 있는 객체들을 교체할 수 있도록 함으로써 같은 작업을 다른 알고리즘으로 사용할 수 있도록 하는 패턴이다.
Template method
작업의 일부분을 캡슐화해서 전체 일을 수행하는 구조는 그대로 유지하면서 특정 부분을 바꾸는 패턴이다. (알고리즘의 각 단계 처리를 서브 클래스에서 재정의할 수 있게 함)
템플릿 메서드(Template Method) 패턴은 여러 클래스에서 공통으로 사용하는 메서드를 템플릿화 하여 상위 클래스에서 정의하고, 하위 클래스마다 세부 동작 사항을 다르게 구현하는 패턴이다.
즉, 변하지 않는 기능(템플릿)은 상위 클래스에 만들어두고 자주 변경되며 확장할 기능은 하위 클래스에서 만들도록 하여, 상위의 메소드 실행 동작 순서는 고정하면서 세부 실행 내용은 다양화 될 수 있는 경우에 사용된다.
abstract class AbstractTemplate {
// 템플릿 메소드 : 메서드 앞에 final 키워드를 붙이면 자식 클래스에서 오버라이딩이 불가능함.
// 자식 클래스에서 상위 템플릿을 오버라이딩해서 자기마음대로 바꾸도록 하는 행위를 원천 봉쇄
public final void templateMethod() {
// 상속하여 구현되면 실행될 메소드들
step1();
step2();
if(hook()) { // 안의 로직을 실행하거나 실행하지 않음
// ...
}
step3();
}
// 1. 템플릿 메서드가 실행할 구현화한 하위 알고리즘 클래스 생성
AbstractTemplate templateA = new ImplementationA();
// 2. 템플릿 실행
templateA.templateMethod();
항목 Visitor 패턴 Bridge 패턴 분류 행위(Behavioral) 패턴 구조(Structural) 패턴 목적 객체 구조는 그대로 두고, 동작(연산)을 분리해서 쉽게 확장 추상화와 구현을 독립적으로 분리해서 확장성 확보 사용 상황 다양한 연산을 여러 객체에 적용해야 할 때 구현부가 자주 바뀌거나 추상화 계층을 분리하고 싶을 때 핵심 구조 Visitor 인터페이스, ConcreteVisitor, Element Abstraction, Implementor, RefinedAbstraction, ConcreteImplementor 장점 새로운 기능을 객체 수정 없이 추가 가능 추상화와 구현을 독립적으로 확장 가능 Visitor 패턴
기존 클래스 구조를 변경하지 않고 새로운 동작(메서드)을 추가하는 방식
각 클래스들의 데이터 구조에서 처리 기능을 분리하여 별도로 구성함으로써, 클래스를 수정하지 않고도 새로운 연산의 추가가 가능함
기능 추가에 유리
Visitor 패턴이란?
객체 구조는 그대로 두고, 거기에 수행할 “기능”을 분리해서 외부에서 추가할 수 있게 하는 패턴이야.
⸻
비유로 이해해 보기
예를 들어, 동물원에 여러 동물(코끼리, 사자, 원숭이)이 있어.
각 동물 클래스에는 accept(visitor)라는 메서드가 있고,
visitor는 이 동물들을 방문하면서 각각에 대해 다른 행동(예: 먹이 주기, 건강 체크 등)을 해.
즉, 동물 클래스에는 “행동”을 직접 안 넣고,
Visitor 클래스들이 와서 그 행동을 수행하게 만들어.
⸻
왜 쓰냐?
• 클래스는 그대로 두고, 새로운 기능만 추가하고 싶을 때 유용해.
• 예를 들어, 동물 클래스는 건들지 않고 “건강검진 기능”이나 “동물 이름 출력 기능”을 따로 만들 수 있어.
⸻
구조 (간단하게)
interface Animal {
void accept(Visitor visitor); // 방문자를 받아들이는 메서드
}
class Lion implements Animal {
public void accept(Visitor visitor) {
visitor.visit(this); // 방문자가 나한테 맞는 visit()을 호출
}
}
interface Visitor {
void visit(Lion lion);
// 다른 동물도 있으면 visit(Elephant elephant); 등 추가
}
class FeedingVisitor implements Visitor {
public void visit(Lion lion) {
System.out.println("사자에게 고기를 줍니다.");
}
}
객체지향 설계 원칙 중 “개방-폐쇄 원칙 (OCP)“
• 기존 코드를 수정하지 않고 (폐쇄)
• 기능을 확장 (개방) 할 수 있게 해주는 구조이기 때문이야.
여러 객체 구조에 대해 **방문자(Visitor)**를 보내 공통된 방식으로 동작을 외부에 정의함
예: 문서 편집기에서 다양한 요소(텍스트, 이미지, 표 등)에 대해 다른 렌더링 방식 적용 시
interface Visitor {
void visit(Text text);
void visit(Image image);
}
class Text {
void accept(Visitor visitor) { visitor.visit(this); }
}
📌 사용 사례
- 컴파일러의 AST(추상 구문 트리)
- 파일 시스템 탐색기 (각 파일 타입별로 다른 동작 수행)
📌 Visitor 패턴의 장점
✅ 기존 클래스를 변경하지 않고 새로운 기능 추가 가능
✅ OCP(개방-폐쇄 원칙) 준수
"객체 구조(Object Structure)를 변경하지 않고, 새로운 기능(방문자) 를 추가하는 패턴"
→ Visitor(방문자)는 특정 객체(Element)를 방문하면서 기능을 수행하는 개념
각 객체는 Visitor가 오면(accept) 자기 자신을 넘겨주고, Visitor는 그 객체에 맞는 연산을 수행한다
📌 Visitor 패턴 동작 방식
1️⃣ Visitor(방문자) 가 Element(객체 구조) 를 방문
2️⃣ 각 Element는 accept(visitor) 메서드를 통해 Visitor의 기능을 실행
3️⃣ Visitor는 Element 내부의 데이터에 접근하여 특정 연산 수행- 할인 시스템 → DiscountVisitor
- 세금 시스템 → TaxVisitor
- 데이터 분석 시스템 → AnalyticsVisitor
📌 Visitor(방문자)가 객체(Element)를 방문하면서 기능을 수행하는 구조를 반영
// 방문자(Visitor) 인터페이스
interface Visitor {
void visit(Book book);
void visit(Electronics electronics);
}
// 방문자 구현 (할인 적용)
class DiscountVisitor implements Visitor {
public void visit(Book book) {
System.out.println("책 할인 적용");
}
public void visit(Electronics electronics) {
System.out.println("전자제품 할인 적용");
}
}
// 요소(Element) 인터페이스
interface Item {
void accept(Visitor visitor);
}
// 요소 클래스 (책)
class Book implements Item {
public void accept(Visitor visitor) {
visitor.visit(this); // Visitor가 방문했을 때 자기 자신 전달
}
}
// 요소 클래스 (전자제품)
class Electronics implements Item {
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
// 실행 코드
public class VisitorPatternDemo {
public static void main(String[] args) {
Item book = new Book();
Item electronics = new Electronics();
Visitor discountVisitor = new DiscountVisitor();
book.accept(discountVisitor); // 책 할인 적용
electronics.accept(discountVisitor); // 전자제품 할인 적용
}
}
✔ 각 Item(책, 전자제품)은 바뀌지 않고, Visitor(할인 정책)가 방문하면서 새로운 기능이 추가됨
Interpreter 패턴
언어에 따라서 문법에 대한 표현을 정의한다.
특정 문법을 가진 언어를 해석하는 데 사용한다
"특정 문법을 해석하고 실행하는 구조를 제공하는 패턴"
→ 언어를 만들거나, 수식 평가 기능을 구현할 때 사용📌 사용 사례
- SQL, 정규식, 수학 수식 해석기
- 프로그래밍 언어 인터프리터
📌 Interpreter 패턴의 장점
✅ 자연어 처리, SQL, 컴파일러 등에서 활용 가능
✅ 문법을 쉽게 확장 가능📌 단점
❌ 복잡한 문법을 처리할 때 속도가 느려질 수 있음
❌ 유지보수가 어렵고, 다른 패턴(SQL Parser 등)으로 대체되는 경우 많음생성 패턴
클래스 정의와 객체 생성 방식을 구조화, 캡슐화
객체의 생성과 참조 과정을 추상화함으로써 시스템을 개발할 때 부담을 덜어준다.
Abstract factory > 관련된 여러 객체 생성
abstraction factory?
(기출 지문)
구체적인 클래스에 의존하지 않고, 인터페이스를 통해 서로 연관, 의존하는 객체들의 그룹으로 생성하여 추상적으로 표현한다.
키트(Kit) 패턴이라고도 불린다
연관된 서브 클래스를 묶어 한 번에 교체하는 것이 가능하다.
여러개의 연관된 서브 클래스를 특정 그룹으로 묶어 한번에 수정할 수 있도록 만든 패턴 (추상 팩토리 패턴은 팩토리 메소드 패턴을 확장한 캡슐화 방식)
https://inpa.tistory.com/entry/GOF-💠-추상-팩토리Abstract-Factory-패턴-제대로-배워보자
최상위 공장 클래스 (GUIFactory)
서브 공장 클래스 (WindowsFactory, MacFactory)
각 타입의 제품 구현체들 (WindowsButton, WindowsCheckbox, MacButton, MacCheckbox)
추상화된 인터페이스만을 이용하여 구체적인 제품, 공장에 대해서는 모른다
예제 보면, 팩토리에 여러 제품 생성할 수 있는 메서드 있음
interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
구체적인 타입을 감추고 객체 생성
new WindowsFactory();
factory.createButton();
factory.createCheckbox();
서로 관련된 객체들을 그룹(제품군) 단위로 생성
팩토리 클래스를 그룹화하여 객체 생성을 캡슐화
여러 개의 팩토리 메서드를 포함한다
팩토리 메서드 패턴은 객체 생성 이후 해야 할 일의 공통점을 정의하는데 초점을 맞추는 반면,
추상 팩토리 패턴은 생성해야 할 객체 집합 군의 공통점에 초점
팩토리 메서드 패턴 > 단일 객체 생성
객체 생성을 서브 클래스에서 처리하도록 분리하여 캡슐화한 패턴
상위 클래스에서 인터페이스만 정의하고 실제 생성은 서브 클래스가 담당한다.
다른 이름으로 가상 생성자(Virtual Constructor) 이라고도 불린다.
객체 생성을 하위 클래스에서 결정 (팩토리 메서드를 오버라이딩)
단일 제품을 생성하는 데 집중 (한 번에 하나의 객체 생성)
ex. 데이터베이스 드라이버를 선택적으로 생성하는 경우
factory = new ButtonFactory();
btn = (Button) factory.createOperation("Window");
btn.render();
추상클래스에 팩토리 메서드가 있다
abstract class DatabaseFactory {
abstract Database createDatabase(); // 팩토리 메서드
public void connectToDatabase() { // 공통 메서드 Database db = createDatabase(); db.connect(); }
}
Builder
객체 생성에 있어서 복잡한 과정들을 분리해서 생산 단계를 캡슐화하여 구축 공정을 동일하게 이용하도록 하는 패턴 (복잡한 객체를 단계적으로 생성)
Prototype
원본 객체를 복제하여 중복 객체를 생성하는 패턴 (비용 절감 효과)
기존 객체를 복제하여 새로운 객체를 생성하는 디자인 패턴
new 키워드를 사용하지 않고 기존 객체를 복사해서 생성한다.
언제 사용하나?
객체 생성 비용이 높을 때
객체의 속성이 동일하지만 일부 값만 변경해서 여러 개를 만들어야 할 때
클래스를 상속받지 않고, 독립적으로 객체를 복제하고 싶을 때
// 1. Prototype 인터페이스 정의
interface Prototype {
Prototype clone(); // 복제 기능 제공
}
// 2. 구체적인 프로토타입 클래스
class ConcretePrototype implements Prototype {
private String data;
public ConcretePrototype(String data) {
this.data = data;
}
// 복제 메서드 구현
@Override
public Prototype clone() {
return new ConcretePrototype(this.data);
}
public void showData() {
System.out.println("데이터: " + data);
}
}
// 3. 클라이언트 코드
public class PrototypePatternExample {
public static void main(String[] args) {
ConcretePrototype original = new ConcretePrototype("원본 데이터");
original.showData(); // 출력: 데이터: 원본 데이터
// 원본 객체를 복제하여 새로운 객체 생성
ConcretePrototype clone = (ConcretePrototype) original.clone();
clone.showData(); // 출력: 데이터: 원본 데이터
}
}
1️⃣ 복잡한 객체일 경우 깊은 복사가 필요 → 내부 객체까지 복제해야 함
2️⃣ 클래스마다 clone() 구현이 필요
** 자바스크립트 프로토타입이 프로토타입 패턴인가? => 지피티가 아니라고 함
GoF의 Prototype 패턴
GoF(갱 오브 포) 디자인 패턴에서 말하는 Prototype 패턴은
- **“기존에 존재하는 객체를 복제(clone)하여 새로운 객체를 생성하는 방식”**을 말합니다.
- 클래스(청사진)를 기반으로 객체를 찍어내지 않고, 복제될 객체 자체를 프로토타입으로 삼아 새로운 객체를 만드는 방법입니다.
- “생성 비용이 큰 객체”나 “복제가 더 적합한 경우” 등에 유용합니다.
자바스크립트의 Prototype 기반 상속
자바스크립트의 ‘프로토타입’은
- **“객체가 다른 객체를 상속(참조)하여, 속성과 메서드 등을 물려받는 언어적 메커니즘”**입니다.
- 각 객체는 내부적으로 [[Prototype]] 링크(또는 __proto__)로 상위 객체를 참조하고, 상위 객체가 또 다른 상위 객체를 참조함으로써 체인이 이어집니다(프로토타입 체인).
- 객체를 복제한다기보다는, **“이미 존재하는 객체를 참조하면서 속성을 찾는 구조”**라고 볼 수 있습니다.
차이점 요약
- 의도
- 동작 방식
- 사용 목적
따라서 자바스크립트의 프로토타입 시스템은 GoF의 Prototype 패턴과 이름이 비슷할 뿐,
기본적인 아이디어와 구현 디테일 면에서 "완전히 같은 개념"으로 보기는 어렵다고 정리할 수 있습니다.Singleton
한 클래스에 한 객체만 존재하도록 제한하는 패턴 (Static으로 선언)
하나의 객체를 생성하면 생성된 객체를 어디서든 참조할 수 있지만,
여러 프로세스가 동시에 참조할 수 없는 패턴
불필요한 메모리 낭비를 최소화 할 수 있음
🔹싱글톤 패턴과 프로세스 동시 접근 문제
싱글톤 패턴은 하나의 프로세스 내에서는 단일 인스턴스를 보장하지만,
여러 개의 프로세스가 실행될 경우, 프로세스마다 독립적인 인스턴스를 생성할 수 있다
이유
(1) 싱글톤 객체는 프로세스의 메모리 공간(Heap)에 저장된다
(2) 프로세스가 다르면 Heap 메모리가 분리되므로 다른 인스턴스를 가질 수 있다
(3) 즉, 싱글톤 패턴은 기본적으로 “프로세스 간 공유”를 보장하지 않는다
🔹 여러 프로세스에서도 싱글톤을 공유하려면?
1️⃣ 파일 기반 접근 (Shared Memory, IPC 활용)
- 파일이나 데이터베이스를 활용하여 싱글톤 데이터를 공유
- 예: Redis, 파일 기반 락, 네트워크 소켓 사용
2️⃣ 운영체제 수준의 싱글톤 (OS 전역 Singleton)
- Named Mutex(윈도우), Semaphore(리눅스) 같은 OS 자원을 활용
- 예: 윈도우에서는 CreateMutex(), 리눅스에서는 shmget() 사용
✅ 결론: 프로세스 간 싱글톤 공유 가능 여부
환경 싱글톤 유지 여부 설명 싱글 스레드 ✅ 유지됨 기본적으로 같은 인스턴스 유지 멀티 스레드 (같은 프로세스) ✅ 유지됨 동기화 (synchronized) 필요 멀티 프로세스 (다른 프로세스) ❌ 유지 안 됨 각 프로세스마다 개별 인스턴스 생성됨 멀티 프로세스 (IPC 활용) ✅ 유지 가능 Redis, 파일, OS 자원 활용 시 공유 가능 📌 즉, 싱글톤 패턴은 기본적으로 "여러 프로세스가 동시에 참조할 수 없는 패턴"이 맞지만,
별도의 방법(IPC, Redis, 파일 시스템)을 사용하면 여러 프로세스에서 공유 가능!구조 패턴
: 클래스나 객체를 조합해 더 큰 구조를 만드는 패턴이다. 복잡한 형태의 구조를 갖는 시스템을 개발하기 쉽게 만들어주는 패턴이다.
Adaptor
호환성이 없는 인터페이스 때문에 함께 사용할 수 없는 클래스를 함께 사용하도록 하는 패턴
ex) 국내 가전제품을 외국에서 사용할 때, 동일한 전압을 사용하지 않아서 변환코드가 필요한 경우에 Adaptor 구조 패턴을 사용함Bridge ⭐️
구현부에서 추상층을 분리하여, 서로가 독립적으로 확장할 수 있도록 구성한 패턴
기능과 구현을 두 개의 별도 클래스로 구현한다는 특징이 있다.
구조 분리에 유리
기능 계층과 구현 계층을 분리해서, 둘을 독립적으로 확장할 수 있게 하는 디자인 패턴이야.
즉, **추상적인 부분(기능)**과 **구체적인 구현(방법)**을 따로 관리해서 유연하게 만드는 구조
예를 들어, 리모컨이 있다고 해보자.
리모컨은 TV도 켜고, 라디오도 켤 수 있어.
• 리모컨 = 추상 클래스 (기능 계층)
• TV나 라디오 = 구현 클래스 (구현 계층)
기존 구조에서는 TV리모컨, 라디오리모컨 이런 식으로 모든 조합을 클래스로 만들어야 해.
근데 Bridge 패턴을 쓰면?
리모컨 기능과 전자기기 구현을 분리해서 새로운 기기나 기능을 추가해도 서로 안 건드려도 돼!
**추상화(Abstraction)**과 **구현(Implementor)**을 분리해서 독립적으로 변경 가능하게 함
예: 리모컨(추상화)과 TV(구현체) → 다양한 리모컨이 다양한 TV를 조작할 수 있도록
interface TV {
void on();
void off();
}
class RemoteControl {
protected TV tv;
RemoteControl(TV tv) { this.tv = tv; }
void turnOn() { tv.on(); }
}
기능 클래스와 구현 클래스를 연결하고, 구현 클래스에서 추상 계층을 분리하여 결합도를 낮추고 독립적으로 변형할 수 있도록 해주는 패턴
인터페이스를 직접 구현하는 대신 추상적인 인터페이스와 실제 구현을 연결하는 "Bridge" 역할을 함
기능(추상 클래스)과 구현(구체 클래스)을 분리하여 독립적으로 확장할 수 있도록 하는 디자인 패턴
상속 대신 구성(Composition)을 활용하여 결합도를 낮추는 패턴
ex. TV/스피커를 리모컨과 분리하여 확장
추상화 계층: 기능을 정의하는 추상 클래스, 구현체를 가지고 있음
구현체 인터페이스: 실제 동작을 수행하는 인터페이스
구현체 클래스: 구현체 인터페이스를 구현한 클래스
의존성 주입을 기반으로 한 디자인패턴처럼 보인다
// 구현 인터페이스 (Implementor) 역할
interface Device {
void turnOn();
void turnOff();
}
// 구체적인 구현체 (Concrete Implementor)
class TV implements Device {
public void turnOn() { System.out.println("TV를 켭니다."); }
public void turnOff() { System.out.println("TV를 끕니다."); }
}
class Radio implements Device {
public void turnOn() { System.out.println("라디오를 켭니다."); }
public void turnOff() { System.out.println("라디오를 끕니다."); }
}
// 추상화 (Abstraction) 계층
abstract class RemoteControl {
protected Device device;
public RemoteControl(Device device) {
this.device = device;
}
abstract void togglePower();
}
// 추상화의 확장 => 브릿지
class BasicRemote extends RemoteControl {
public BasicRemote(Device device) { // 의존성 주입
super(device);
}
public void togglePower() {
System.out.println("전원 버튼을 누릅니다.");
device.turnOn();
device.turnOff();
}
}
// 클라이언트 코드
public class Client {
public static void main(String[] args) {
Device tv = new TV();
RemoteControl remote = new BasicRemote(tv);
remote.togglePower(); // TV를 켭니다. TV를 끕니다.
Device radio = new Radio();
remote = new BasicRemote(radio);
remote.togglePower(); // 라디오를 켭니다. 라디오를 끕니다.
}
}
Composite
단일 객체와 복합 객체가 클라이언트에서 동일하게 사용되도록 해주는 패턴
트리 구조로 객체를 구성하여 단일 객체와 복합 객체(여러 개의 객체)를 동일한 방식으로 처리할 수 있도록 하는 패턴
ex. File, Directory 객체
Decorator
기존 객체의 메소드에 새로운 독립적인 기능을 추가해서 확장하도록 하는 패턴
Facade (하나의 진입점)
Facade 인터페이스를 제공하여 Facade 객체를 통해서만 모든 관계가 이루어질 수 있도록 인터페이스를 단순화한다. (클래스 간의 의존관계 줄어들고 복잡성도 낮아짐)
복잡한 서브시스템(하위 모듈)들을 단순한 인터페이스로 제공하는 디자인 패턴
여러 개의 클래스를 하나의 단순한 API로 감싸서 사용하기 쉽게 만들어 주는 패턴
클라이언트가 직접 여러 클래스를 조작하지 않아도 된다
코드 결합도 낮춤 -> 서브 시스템 변경이 있어도 클라이언트 코드 영향 최소화
# 코드 짤 때 자주 사용하는 패턴인 듯
✅ 개념
- 클라이언트가 여러 개의 클래스를 직접 다루는 대신 하나의 진입점(Facade 클래스)을 통해 쉽게 사용할 수 있도록 함
- 내부 시스템의 변경을 클라이언트 코드에 영향을 주지 않고 캡슐화 가능
📌 사용 예시
- JDBC (Java Database Connectivity)에서 Connection, Statement, ResultSet 같은 복잡한 객체들을 DriverManager가 감싸서 사용하도록 제공
- Spring Framework에서 다양한 서브 모듈을 감싸서 쉽게 사용할 수 있도록 제공하는 것
// 복잡한 서브 시스템 (각각의 클래스를 직접 사용하려면 복잡함)
// 1️⃣ 서브시스템 (복잡한 내부 시스템)
class TV {
void turnOn() { System.out.println("TV 켜짐"); }
void turnOff() { System.out.println("TV 꺼짐"); }
}
class Speaker {
void setVolume(int level) { System.out.println("스피커 볼륨: " + level); }
}
class DVDPlayer {
void playMovie(String movie) { System.out.println(movie + " 재생 시작"); }
}
// 2️⃣ Facade (단순한 인터페이스 제공)
class HomeTheaterFacade {
private TV tv;
private Speaker speaker;
private DVDPlayer dvdPlayer;
public HomeTheaterFacade(TV tv, Speaker speaker, DVDPlayer dvdPlayer) {
this.tv = tv;
this.speaker = speaker;
this.dvdPlayer = dvdPlayer;
}
public void watchMovie(String movie) {
System.out.println("영화 모드 ON");
tv.turnOn();
speaker.setVolume(10);
dvdPlayer.playMovie(movie);
}
public void endMovie() {
System.out.println("영화 모드 OFF");
tv.turnOff();
}
}
// 3️⃣ 클라이언트 코드
public class FacadePatternExample {
public static void main(String[] args) {
TV tv = new TV();
Speaker speaker = new Speaker();
DVDPlayer dvdPlayer = new DVDPlayer();
HomeTheaterFacade homeTheater = new HomeTheaterFacade(tv, speaker, dvdPlayer);
homeTheater.watchMovie("어벤져스");
homeTheater.endMovie();
}
}
Flyweight
유사한 작은 객체를 공유해서 사용함으로써 메모리를 절약
객체를 공유하여 메모리 사용을 줄이는 디자인 패턴
같은 속성을 가진 객체를 여러 개 만들지 않고, 한 개의 객체를 재사용하여 성능을 최적화하는 구조 패턴
1️⃣ Flyweight (공유 객체 인터페이스)
- 공유할 속성을 정의한 인터페이스 또는 추상 클래스
2️⃣ ConcreteFlyweight (구체적인 공유 객체) - 실제 공유되는 객체
3️⃣ FlyweightFactory (팩토리, 객체 생성 및 관리) - 이미 생성된 객체를 관리하고, 동일한 객체가 요청되면 기존 객체 반환
4️⃣ Client (클라이언트 코드) - Factory를 통해 Flyweight 객체를 요청하고 사용
import java.util.*;
// 1️⃣ Flyweight (공유 객체 인터페이스)
interface Character {
void display(String font); // 외부 속성(Font)은 공유하지 않음
}
// 2️⃣ ConcreteFlyweight (구체적인 공유 객체)
class ConcreteCharacter implements Character {
private char symbol; // 공유 속성 (내부 상태)
public ConcreteCharacter(char symbol) {
this.symbol = symbol;
}
@Override
public void display(String font) { // 외부 속성(Font)은 매개변수로 전달
System.out.println("문자: " + symbol + ", 폰트: " + font);
}
}
// 3️⃣ FlyweightFactory (객체 재사용을 위한 팩토리)
class CharacterFactory {
private Map<Character, ConcreteCharacter> pool = new HashMap<>();
public Character getCharacter(char symbol) {
if (!pool.containsKey(symbol)) {
pool.put(symbol, new ConcreteCharacter(symbol)); // 새로운 객체 생성 후 저장
}
return pool.get(symbol);
}
}
// 4️⃣ 클라이언트 코드 (객체를 공유하여 사용)
public class FlyweightPatternExample {
public static void main(String[] args) {
CharacterFactory factory = new CharacterFactory();
Character c1 = factory.getCharacter('A'); // 새로운 'A' 객체 생성
Character c2 = factory.getCharacter('B'); // 새로운 'B' 객체 생성
Character c3 = factory.getCharacter('A'); // 기존 'A' 객체 재사용
c1.display("Arial"); // 문자: A, 폰트: Arial
c2.display("Times New Roman"); // 문자: B, 폰트: Times New Roman
c3.display("Verdana"); // 문자: A, 폰트: Verdana (c1과 같은 객체)
}
}
Proxy 패턴
복잡한 시스템을 개발하기 쉽도록 클래스나 객체들을 조합하는 패턴에 속하며,
대리자라는 이름으로도 불린다.
내부에서는 객체 간의 관계를 단순하게 정리해주고, 외부에서는 객체의 세부적인 내용을 숨기는 역할을 한다.
어떤 객체에 대한 접근을 제어하기 위해 대리자(Proxy) 객체를 사용하는 디자인 패턴
실제 객체 대신 프록시 객체가 중간에서 요청을 처리하여
성능 최적화, 접근 제한, 로깅 등의 기능을 수행할 수 있다.
- 지연 초기화 (Lazy Loading)
- 보안 및 접근 제어
- 로깅 및 성능 모니터링
- 캐싱 및 네트워크 요청 최적화
✅ 프록시 패턴의 특징
✔ 실제 객체 대신 프록시 객체가 대리 역할을 함
✔ 불필요한 자원 낭비를 방지하고 성능을 최적화
✔ 접근 제어, 로깅, 캐싱, 지연 초기화(Lazy Loading) 등에 활용자바스크립트 프록시: https://ko.javascript.info/proxy
반응형'컴퓨터 > 정보처리기사' 카테고리의 다른 글
운영체제 이론 (0) 2025.04.22 네트워크 및 데이터 통신 (0) 2025.04.22 OSI 7계층, TCP/IP 4계층 (0) 2025.04.22 IPv6, IPv4, 서브넷 마스크 (0) 2025.04.22 DCL, 데이터베이스 구현, 스토리지 관리 (0) 2025.04.22