본문 바로가기
정보처리기사/이론설명

디자인 패턴

by wildOjisan 2025. 9. 15.

정보처리기사 디자인패턴 정리

 

정보처리기사 디자인패턴 정리

1️⃣ 생성패턴 (5) 기존 코드의 유연성과 재사용을 증가시키는 다양한 객체 생성 메커니즘들을 제공하는 패턴 추상 팩토리 Abstract Factory인터페이스로 서로 관련된 객체 패밀리 생성생성된 객체

jaycode.tistory.com

 

Engineer_Information_Processing_TestClass_2509/Day17_java.txt at main · najongjine/Engineer_Information_Processing_TestClass_2509

 

Engineer_Information_Processing_TestClass_2509/Day17_java.txt at main · najongjine/Engineer_Information_Processing_TestClass_25

Contribute to najongjine/Engineer_Information_Processing_TestClass_2509 development by creating an account on GitHub.

github.com

 

정보처리기사 시험에 빠지지 않게 나오고, 세부적으로 파고들면 무한하게 문제를 생성할수 있고, 말 꼬아서 나오면 맞출수 없는 골치아픈 개념이죠

 

실무에서 디자인패턴을 꼭 알아야 프로젝트가 진행되지는 않습니다

하지만 정보처리기사는 실무를 보강하는 시험이라기보단 이제는 사람들을 떨어트리기 위한 목적으로 변질되어서 어쩔수 없이 공부해야 하는 개념입니다

 

 

디자인 패턴의 이론적인 뜻은 이렇습니다

디자인 패턴(Design Pattern)은 소프트웨어 공학의 소프트웨어 설계에서 공통으로 발생하는 문제에 대해 자주 쓰이는 설계 방법을 정리한 패턴이다.

효율성과 유지보수성, 운용성이 높아지며 프로그램의 최적화에 도움이 된다.

 

 

단순 이론이 이렇다라는 겁니다

 

 

 

디자인 패턴 공통된 특징 -

인터페이스로 틀만 잡음

하위 클래스가 인터페이스를 부모로 두고 상속받음

사용부에선 타입은 인터페이스, 생성은 하위 클래스로 하고 인터페이스에 정의된 method 사용

 

 

생성패턴

Factory Method -

상위에서 객체 인터페이스 정의하고 하위에서 인스턴스 생성

오버로딩하여 객체 생성 클래스 분리

 

예시:

DarkButton, LightButton 같은 실제 하위 클래스들이 많아도,
그냥 create_button() 같은 **공통된 방법(인터페이스)**으로 만들어 쓴다는 뜻이야.

// 버튼 인터페이스
interface Button {
    void click();  // 눌렀을 때 동작
}

// 카카오 버튼
class KakaoButton implements Button {
    public void click() {
        System.out.println("카카오 로그인 버튼이 눌렸습니다!");
    }
}

// 네이버 버튼
class NaverButton implements Button {
    public void click() {
        System.out.println("네이버 로그인 버튼이 눌렸습니다!");
    }
}

// 버튼을 만드는 공장 (Factory Method 사용)
abstract class ButtonFactory {
    public abstract Button createButton();  // 버튼 만드는 메서드
}

// 카카오 버튼을 만드는 공장
class KakaoButtonFactory extends ButtonFactory {
    public Button createButton() {
        return new KakaoButton();
    }
}

// 네이버 버튼을 만드는 공장
class NaverButtonFactory extends ButtonFactory {
    public Button createButton() {
        return new NaverButton();
    }
}

// 테스트용 메인 클래스
public class FactoryMethodTest {
    public static void main(String[] args) {
        ButtonFactory kakaoFactory = new KakaoButtonFactory();
        Button kakao = kakaoFactory.createButton();
        kakao.click();  // 출력: 카카오 로그인 버튼이 눌렸습니다!

        ButtonFactory naverFactory = new NaverButtonFactory();
        Button naver = naverFactory.createButton();
        naver.click();  // 출력: 네이버 로그인 버튼이 눌렸습니다!
    }
}

객체 1개 생성( 버튼 하나 만들기 )

 

 

추상 팩토리 Abstract Factory -

Factory Method 의 확장개념

Factory Method 개념 고대로 가면서 + 인터페이스로 서로 관련된 객체 패밀리 생성

// ▶ 버튼 인터페이스
interface Button {
    void click();
}

// ▶ 체크박스 인터페이스
interface Checkbox {
    void check();
}

// ▶ 카카오 버튼과 체크박스
class KakaoButton implements Button {
    public void click() {
        System.out.println("카카오 버튼 클릭!");
    }
}

class KakaoCheckbox implements Checkbox {
    public void check() {
        System.out.println("카카오 체크박스 체크됨!");
    }
}

// ▶ 네이버 버튼과 체크박스
class NaverButton implements Button {
    public void click() {
        System.out.println("네이버 버튼 클릭!");
    }
}

class NaverCheckbox implements Checkbox {
    public void check() {
        System.out.println("네이버 체크박스 체크됨!");
    }
}

// ▶ 공장 인터페이스
interface UIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

// ▶ 카카오 전용 공장
class KakaoUIFactory implements UIFactory {
    public Button createButton() {
        return new KakaoButton();
    }
    public Checkbox createCheckbox() {
        return new KakaoCheckbox();
    }
}

// ▶ 네이버 전용 공장
class NaverUIFactory implements UIFactory {
    public Button createButton() {
        return new NaverButton();
    }
    public Checkbox createCheckbox() {
        return new NaverCheckbox();
    }
}

// ▶ 클라이언트 코드
public class AbstractFactoryTest {
    public static void main(String[] args) {
        // 공장을 선택 (카카오 or 네이버)
        UIFactory factory = new KakaoUIFactory(); // or new NaverUIFactory();

        // 제품군을 생성
        Button btn = factory.createButton();
        Checkbox chk = factory.createCheckbox();

        // 동작 테스트
        btn.click();   // 출력: 카카오 버튼 클릭!
        chk.check();   // 출력: 카카오 체크박스 체크됨!
    }
}

관련 있는 객체(Button, Checkbox 등)를 한 번에 생성하기 위한 인터페이스 제공

 

 

빌더 Builder -

복잡한 객체 생성과정을 단계별로 처리

Burger myBurger = new BurgerBuilder()
                                .setBun("참깨 번")
                                .setPatty("치킨 패티")
                                .setCheese("체다 치즈")
                                .setVegetable("양파와 토마토")
                                .build();

 

 

 

프로토타입 Prototype -

기존 객체 복제를 통해 새로운 객체 생성

// ▶ Prototype 인터페이스
interface MenuItem extends Cloneable {
    MenuItem clone();  // 복사 기능
    void show();
}

// ▶ 햄버거 메뉴
class Burger implements MenuItem {
    private String name;
    private int price;

    public Burger(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public MenuItem clone() {
        return new Burger(name, price);  // 복사 생성
    }

    public void show() {
        System.out.println("🍔 햄버거: " + name + ", 가격: " + price + "원");
    }
}


public static void main(String[] args) {
    // 원본 객체 생성
    Burger original = new Burger("치즈버거", 5000);

    // 복사본 생성
    Burger copy = (Burger) original.clone();

    // 출력
    original.show();  // 🍔 햄버거: 치즈버거, 가격: 5000원
    copy.show();      // 🍔 햄버거: 치즈버거, 가격: 5000원
}

!!! original 객체의 price를 변경해도 clone된 객체의 price는 바뀌지 않습니다.

 

 

싱글톤 Singleton -

단일 인스턴스 생성 보장. 전역 접근 가능

using UnityEngine;

public class GameManager : MonoBehaviour
{
    // ▶ 싱글톤 인스턴스 (전역 접근 가능)
    public static GameManager Instance;

    // ▶ 게임 점수
    public int score = 0;

    // ▶ Awake는 씬이 시작되자마자 실행됨
    void Awake()
    {
        // 만약 Instance가 비어 있으면 이걸로 설정
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject); // 씬이 바뀌어도 유지됨
        }
        else
        {
            Destroy(gameObject); // 중복 싱글톤 제거
        }
    }

    public void AddScore(int value)
    {
        score += value;
        Debug.Log("점수: " + score);
    }
}

public class Player : MonoBehaviour
{
    void Start()
    {
        // GameManager 싱글톤을 통해 점수 추가
        GameManager.Instance.AddScore(10);
    }
}

 

 

 

구조 패턴

어댑터 Adapter -

서로 안 맞는 코드끼리 맞춰주는 중간 통역사 역할의 클래스

호환되지 않는 인터페이스 통합
필요한 인터페이스로 변환 및 재사용
인터페이스 호환성 문제 해결

 

기존 시스템은 printText(String s) 라는 함수를 쓰는데,
새로 끌어온 라이브러리는 show(String message) 라는 이름을 써.

→ 함수 이름이 다르니까 기존 코드에서 쓸 수 없음.

 

// 1. 기존 인터페이스
interface Printer {
    void printText(String s);
}

// 2. 새 라이브러리 (수정할 수 없음)
class NewLibraryPrinter {
    public void show(String message) {
        System.out.println("신규 라이브러리: " + message);
    }
}

// 3. 어댑터: 기존 인터페이스를 구현하고, 내부에서 새로운 함수를 호출함
class PrinterAdapter implements Printer {
    private NewLibraryPrinter newPrinter;

    public PrinterAdapter(NewLibraryPrinter newPrinter) {
        this.newPrinter = newPrinter;
    }

    public void printText(String s) {
        newPrinter.show(s);  // 이름이 다른 메서드를 여기서 매핑해줌
    }
}
public class Main {
    public static void main(String[] args) {
        NewLibraryPrinter newPrinter = new NewLibraryPrinter();
        Printer printer = new PrinterAdapter(newPrinter);  // 어댑터를 통해 기존 방식으로 사용

        printer.printText("Hello, World!");  // 내부적으로 newPrinter.show() 호출됨
    }
}

어댑터란느 클래스를 만들어서 예전거랑 새로운 객체를 끼워 넣으면

예전부터 썻던 printText ()함수 계속 쓸수 있다는 소리

 

 

브리지 Bridge -

클래스를 기능부와 구현부로 분리한뒤, 두 클래스를 연결. 느슨한 연결.

 

🎮 게임패드 = 기능부 (UI)

  • 버튼 A: 점프
  • 버튼 B: 공격
  • 버튼 X: 구르기
    → 이건 *“무엇을 할 것인가”*만 정한 거야.

🕹️ 게임기 본체 = 구현부 (실제 동작)

  • 닌텐도 스위치
  • PS5
    → 이건 *“어떻게 할 것인가”*를 정한 거지.

문제 상황:

  • 도형 클래스가 있음 (원, 사각형 등)
  • 도형을 그리는 방식도 여러 개가 있음 (Vulkan, DirectX, OpenGL 등)

→ 예전 방식이면, 도형마다 구현 방식마다 클래스를 다 만들어야 해.

CircleWithOpenGL
CircleWithDirectX
RectangleWithOpenGL
RectangleWithDirectX
...

 

✅ 브리지 패턴 적용!

1. 기능부: 추상적인 도형

abstract class Shape {
    protected Drawing drawing;  // Bridge. 구현부를 참조

    public Shape(Drawing drawing) {
        this.drawing = drawing;
    }

    public abstract void draw();  // 도형을 그리는 기능
}

2. 구현부: 실제 그리는 방법 인터페이스

interface Drawing {
    void drawCircle();
    void drawRectangle();
}

 

3. 구현 클래스들 (DirectX, OpenGL 등)

class OpenGLDrawing implements Drawing {
    public void drawCircle() {
        System.out.println("OpenGL로 원 그리기");
    }
    public void drawRectangle() {
        System.out.println("OpenGL로 사각형 그리기");
    }
}

class DirectXDrawing implements Drawing {
    public void drawCircle() {
        System.out.println("DirectX로 원 그리기");
    }
    public void drawRectangle() {
        System.out.println("DirectX로 사각형 그리기");
    }
}

 

4. 실제 도형 클래스들 (기능부 구현)

class Circle extends Shape {
    public Circle(Drawing drawing) {
        super(drawing);
    }

    public void draw() {
        drawing.drawCircle();  // 구현부에 위임
    }
}

class Rectangle extends Shape {
    public Rectangle(Drawing drawing) {
        super(drawing);
    }

    public void draw() {
        drawing.drawRectangle();
    }
}

 

5. 사용!

public class Main {
    public static void main(String[] args) {
        Shape circle1 = new Circle(new OpenGLDrawing());
        circle1.draw();  // OpenGL로 원 그리기

        Shape rect1 = new Rectangle(new DirectXDrawing());
        rect1.draw();  // DirectX로 사각형 그리기
    }
}

 

 

기능부 어떤 기능을 쓸지 정함 (도형, 게임패드, UI 등)
구현부 실제로 어떻게 동작할지 정함 (OpenGL, DirectX, 게임기 등)
브리지 기능과 구현을 서로 독립되게 만들어 놓고, 나중에 연결해서 사용 ( protected Drawing drawing; )

 

 

 

Decorator -

클래스 변경없이 기능을 추가하는 패턴

@Index("t_user_pkey", ["idp"], { unique: true })
@Entity("t_user", { schema: "public" })
export class TUser {
  @PrimaryGeneratedColumn({ type: "integer", name: "idp" })
  idp: number;

  @Column("character varying", {
    name: "username",
    nullable: true,
    length: 50,
    default: () => "''''''",
  })
  username: string | null;

  @Column("character varying", {
    name: "password",
    nullable: true,
    length: 255,
    default: () => "''''''",
  })
  password: string | null;

  @OneToMany(() => TUserRoles, (tUserRoles) => tUserRoles.userIdp)
  tUserRoles: TUserRoles[];
}

TUser 라는 클래스를 만듬.

@뭐뭐 가 decorator.

@Enity 를 붙이니 얘가 DB 테이블이 됨

@OneToMany 를 붙이니 얘가 1:N relation 을 형성함

 

 

파사드 Facade -

복잡한 시스템 단순화

 

 

 

복합체  Composite -

트리 구조로 객체 구성

 

 

 

플라이웨이트 Flyweight -

메모리 사용량을 최소화 하기위해 객체들간 데이터 공유를 극대화. 데이터 중복 생성 안하고 하나 만들어놓고 서로 갔다씀.

 

 

 

프록시 Proxy -

특정 객체로의 접근을 특정 대리자를 통해 진행

🎬 비유: 아이돌 팬미팅 예약

  • 너가 BTS 팬미팅에 가고 싶어.
  • 근데 직접 BTS 회사에 전화 걸어서 "예약해줘요!" 하는 건 불가능해.
  • 대신 예약 대행 업체(= 프록시)한테 부탁하지!

🧑‍🎤 BTS 회사 ←❌ 직접 접근 못 해
📞 예약 대행 업체(프록시) ←⭕ 이걸 통해 예약 진행

예약 대행 업체가 ‘프록시’ 역할을 해주는 거야

 

예시: 이미지 로딩 프록시 .

interface Image {
    void display();
}

 

1. 진짜 이미지 클래스

class RealImage implements Image {
    private String filename;

    public RealImage(String filename) {
        this.filename = filename;
        loadFromDisk(); // 이미지를 디스크에서 불러오는 비용이 큰 작업
    }

    private void loadFromDisk() {
        System.out.println("이미지 로딩: " + filename);
    }

    public void display() {
        System.out.println("이미지 보여주기: " + filename);
    }
}

 

2. 프록시 이미지 클래스

class ProxyImage implements Image {
    private RealImage realImage;
    private String filename;

    public ProxyImage(String filename) {
        this.filename = filename;
    }

    public void display() {
        if (realImage == null) {
            realImage = new RealImage(filename);  // 진짜 이미지가 필요한 순간에만 로딩
        }
        realImage.display();
    }
}

 

3. 사용

public class Main {
    public static void main(String[] args) {
        Image img = new ProxyImage("pikachu.png");

        System.out.println("이미지를 처음 볼 때:");
        img.display(); // 이때만 실제 이미지 로딩

        System.out.println("이미지를 다시 볼 때:");
        img.display(); // 로딩 없이 바로 보여줌
    }
}

 

RealObject 진짜 객체 (예: 이미지, 동영상, 데이터 등)
Proxy 진짜 객체 대신 먼저 등장하는 대리 객체
목적 리소스 절약, 보안, 접근 제어 등

 

 

 

행위 패턴

책임연쇄  Chain of Responsibility -

 

요청에 대한 기능 처리의 연결
순차적 요청 전달 결정

 

 

 

옵저버 Observer -

객체 상태 변경 관찰하고 자동 알림
일대다 의존 관계

 

🎯 옵저버 패턴이 뭐냐면?

**"어떤 일이 벌어지면, 그걸 듣고 반응하는 여러 명이 있는 구조"**야.

쉽게 말해서:

  • "누가 뭔가를 했다!"
  • → 그걸 **보고 있던 애들(옵저버)**이
  • "어! 벌어졌네?" 하고 반응하는 구조야.

 

public interface IEnemyDeathObserver {
    void OnEnemyDeath(Enemy enemy);
}
public class Enemy {
    private List<IEnemyDeathObserver> observers = new();

    public void AddObserver(IEnemyDeathObserver obs) {
        observers.Add(obs);
    }

    public void Die() {
        Console.WriteLine("적이 죽었습니다!");
        foreach (var obs in observers) {
            obs.OnEnemyDeath(this); // 죽었다고 알림
        }
    }
}
public class QuestSystem : IEnemyDeathObserver {
    public void OnEnemyDeath(Enemy enemy) {
        Console.WriteLine("퀘스트 시스템: 적 죽음 확인 → 진행도 +1");
    }
}

public class Logger : IEnemyDeathObserver {
    public void OnEnemyDeath(Enemy enemy) {
        Console.WriteLine("로그 기록: 적이 죽음");
    }
}

 

 

 

커맨드 Command -

요청을 객체로 캡슐화

 

 

 

상태 State

객체 상태를 캡슐화
원시코드 수정 최소화

 

  • Observer (옵저버): "나 바뀌었으니까, 너네(구독자들) 알아서 해." (방송국)
    • 목적: 상태 전달. 1명이 바뀌면 N명에게 소문을 냄.
    • 행동 주체: 소식을 들은 **구독자(Observer)**들이 각자 알아서 행동함.
  • State (스테이트): "나 상태가 변했으니까, 이제부터 내가 다르게 행동할게." (지킬 박사와 하이드)
    • 목적: 행동 변경. 상태에 따라 내(Context) 행동 로직 자체가 바뀜.
    • 행동 주체: 상태를 가진 객체 본인이 다르게 행동함.
  • Observer (유튜버와 구독자)
    • 알림을 받고 영상을 볼지, 무시할지, 악플을 달지는 구독자 마음입니다.
    • 유튜버는 그냥 "영상 올라왔다"고 소리만 지르면 끝입니다.
    • 키워드: 1:N 전파, 통보
    State (게임 캐릭터의 변신)
    • 이제 점프 키를 누르면 예전(작은 마리오)과 다르게 더 높이 뜁니다.
    • '점프'라는 똑같은 입력을 줬는데, 상태(State)에 따라 행동(로직)이 바뀐 겁니다.
    • 키워드: 행동 교체, if-else 제거
  • 마리오가 버섯을 먹어서 '슈퍼 마리오' 상태가 되었습니다.
  • 유튜버(Subject)가 영상을 올리면 구독자(Observer)들에게 알림이 갑니다.

 

 

 

 

반복자  Iterator -

컬렉션 요소 순차 접근 및 처리
내부구조 노출 X

List<Monster> monsters = new List<Monster>();
monsters.Add(new Monster("슬라임"));
monsters.Add(new Monster("고블린"));
monsters.Add(new Monster("드래곤"));

foreach (Monster m in monsters) {
    m.Attack();  // 한 마리씩 꺼내서 공격
}

monsters 리스트가 내부 구조가 어떻게 생겼는지 우리는 몰라도,
그냥 하나씩 꺼내서 처리(Attack) 하고 있어!

 

 

 

전략 Strategy -

알고리즘을 별도로 캡슐화. 여러개의 알고리즘을 선택해서 사용할수 있음.

 

 

중재자 Mediator -

객체간의 통신이 직접 이루어지지 않고 중재자를 통해서 진행. 결합도를 낮춤.

 

 

템플릿 메서드 Template Method -

상위 클래스는 알고리즘 뼈대만 정의, 구체적인건 하위 클래스에서 정의.

 

 

 

메멘토 -

객체 이전 상태 저장 및 복원
작업취소 undo 요청

 

 

 

비지터 Visitor -

객체 구조 변경 없이 새 연산 추가

 

✨ 상황

  • Student, Teacher, Principal 클래스를 이미 만들어 놓았어.
  • 근데 이제 “세금 계산 기능”을 추가하고 싶어.

근데!
기존 클래스를 못 바꾸는 상황이야 (외부 라이브러리거나, 건드리면 위험하거나 등등)

 

1. 방문자 인터페이스

public interface IVisitor {
    void Visit(Student student);
    void Visit(Teacher teacher);
}

 

2. 요소 클래스들 (학생, 선생님)

public class Student {
    public string Name = "철수";
    public void Accept(IVisitor visitor) {
        visitor.Visit(this);  // 방문자에게 나 자신을 넘김
    }
}

public class Teacher {
    public string Name = "영희쌤";
    public void Accept(IVisitor visitor) {
        visitor.Visit(this);
    }
}

 

3. 방문자 클래스 (새 기능 추가)

public class TaxCalculator : IVisitor {
    public void Visit(Student student) {
        Console.WriteLine($"{student.Name} 학생은 세금 없음.");
    }

    public void Visit(Teacher teacher) {
        Console.WriteLine($"{teacher.Name} 선생님은 세금 10만원.");
    }
}

 

4. 사용 예시

Student s = new Student();
Teacher t = new Teacher();

TaxCalculator taxVisitor = new TaxCalculator();

// 각각 방문시키기
s.Accept(taxVisitor);  // 철수 학생은 세금 없음
t.Accept(taxVisitor);  // 영희쌤은 세금 10만원

 

 

 

인터프리터 Interpreter -

문법 규칙을 기반으로 문장 해석