CS/디자인 패턴

(CS) 디자인 패턴 - 싱글톤 패턴

흰색텀블러 2025. 1. 7. 09:40

디자인 패턴

  • 프로그램 설계시 발생했던 문제들을 하나의 "규약"으로 만들어 놓은 것의 집합체.

♤ 들어가기전, 언어에 대해서!

  • 클래스
    • 정의 : 객체 지향 프로그래밍에서 데이터를 정의하고, 메서드(동작)을 묶어서 하나의 설계도를 만든 것
      • 특징
        1. 객체를 생성하기 위한 틀 역할.
        2. 필드(속성)와 메서드(동작)로 구성.
        3. 예를 들어, Car라는 클래스는 자동차의 속성(브랜드, 색상), 동작(운전, 정지)등 을 정의할 수 있습니다.
public class Car {
    String brand;
    String color;

    public void drive() {
        System.out.println("Driving...");
    }
}

 

  • 인스턴스 
    • 정의 : 클래스를 기반으로 메모리에 생성된 실제 객체
      • 특징
        1. 클래스는 설계도이면, 인스턴스는 설계도를 바탕으로 생성된 실체 실체 실체.
        2. 동일한 클래스로 여러 인스턴스 만들 수 있음
public class Main {
    public static void main(String[] args) {
        // Car 클래스의 인스턴스 생성
        Car myCar = new Car();
        myCar.brand = "Toyota";
        myCar.color = "Red";

        myCar.drive(); // 출력: Toyota is driving!
    }
}

 

 

  • 모듈 
    • 정의 : 서로 관련된 코드(클래스, 함수, 변수 등등)의 집합으로 프로그램의 독립적인 단위를 말함.
      • 특징
        1. 큰 프로그램을 여러 부분으로 나누어 개발, 관리, 재사용을 쉽게 함.
        2. 각 모듈은 독립적으로 개발하고 테스트 할 수 있음.
        3. 보통 파일 단위로 나뉜다 (java의 경우, .class)
package vehicle;

public class Car {
    String brand;

    public Car(String brand) {
        this.brand = brand;
    }

    public void drive() {
        System.out.println(brand + " is driving!");
    }
}


// Main.java

import vehicle.Car;

public class Main {
    public static void main(String[] args) {
        Car car = new Car("Honda");
        car.drive(); // 출력: Honda is driving!
    }
}

 

 

  • TDD(Test Driven Development) 
    • 정의 : 테스트를 먼저 작성하고, 그 테스트를 통과하는 코드를 작성하는 개발 방법론
      • 특징
        1. 테스트 작성 → 코드 구현   리팩토링 순서로 진행 
        2. 코드의 품질과 안정성을 높이는데 도움
        3. 테스트가 실패하는 상태에서 개발 시작.
      • 장점
        1. 높은 코드 품질
        2. 빠른 피드백 제공
        3. 리팩토링 시 버그 방지!!
// 1. 테스트 작성
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class CalculatorTest {
    @Test
    public void testAdd() {
        Calculator calc = new Calculator();
        assertEquals(5, calc.add(2, 3)); // 테스트 기대값: 5
    }
}

// 2. 테스트를 통과하는 코드 작성
public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

 

 

  • 단위 테스트 
    • 정의 : SW의 작은단위(보통 하나의 함수 or 메서드)
      • 특징
        1. 객체를 생성하기 위한 틀 역할.
        2. 필드(속성)와 메서드(동작)로 구성.
        3. 예를 들어, Car라는 클래스는 자동차의 속성(브랜드, 색상), 동작(운전, 정지)등 을 정의할 수 있습니다.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class StringUtilsTest {
    @Test
    public void testIsPalindrome() {
        StringUtils utils = new StringUtils();
        assertTrue(utils.isPalindrome("madam")); // "madam"은 팰린드롬
    }
}

class StringUtils {
    public boolean isPalindrome(String input) {
        String reversed = new StringBuilder(input).reverse().toString();
        return input.equals(reversed);
    }
}

 

1. 싱글톤 패턴

  • 하나의 클래스에, 하나의 인스턴스만을 가지는 패턴
  • 하나의 인스턴스를 다른 모듈에 공유하며 사용
  • 인스턴스 생성 비용이 줄어드는 장점, 하지만 의존성이 높아진다는 단점
  • 데이터베이스 연결에 많이 사용

 

의존성이 높아지면?

  1. 유연성 저하
    • 시스템의 일부를 수정하거나 교체하기가 어려워짐 → 시스템 확장 및 유지보수가 복잡해짐
      • A 클래스가 B클래스에 강하게 의존시, B를 변경할때, A도 영향을 받음 ㅠㅠ
        • 예시 : A 클래스가 특정 구현 클래스 의존하면, 새로운 구현을 하거나, 기존 구현을 교체하기 위해 A도 수정을 해야합니다 ㅠㅠ
  2. 테스트 어려움
    • 독립적으로 테스트하기가 어려움
      • 의존하는 다른 클래스나 외부 서비스가 없으면 테스트하는데 복잡해짐
        • A 클래스가 DB와 직접 연결되어 있으면, Unit Test를 실행하기 위해 DB를 또 따로 준비..ㅠ
          • 해결방법 : 의존성 주입(Dependency Injection), 인터페이스를 사용해서 의존성을 느슨하게 만들고, Mock(목) 객체 활용하기!
  3. 재사용성 저하
    • 특정 환경, 기술, 구현에 강하게 묶여있어서 독립적으로 재사용하는 것을 어렵게 만듬
      • A 클래스가 DB나 library에 종속되어 있으면, 다른 프로젝트에서 재사용 하기위해 의존성을 같이 가져와야함.
        • 코드가 특정환경에 고정됨 -> 재사용성이 떨어지고, 프로젝트간 공유가 어려워짐
  4. 변경의 전파 
    • 의존성이 높으면 한 클래싀의 변경이 다른 클래스들로 쉽게 전파됨 → 예상못하는 버그가 생길 수 있음
      • B 클래스가 변경되면 A 클래스도 수정해야할 수 있으며, 수정사항이 A를 의존하는 다른 클래스에 영향
        • 작은 변경사항이 전체에 파급 효과를 일으킬 가능성이 있어, 유지보수가 어려워짐..
  5. 결합도 증가
    • 클래스 간 결합도가 증가해서, 객체 간 관계를 복잡하게 만듬 → 시스템 복잡도 증가로 인해 관리가 어려워짐...
  6. 의존성 관리 비용 증가
    • 의존성 관리를 위해, 빌드 도구(Maven, Gradle)를 통해 외부 라이브러리를 관리해야함.
      • 의존성 출동, 버전 문제 발생

 

해결 방법은?

  1. 의존성 역전 원칙(DIP)
    • 고수준 모듈이 저수준 모듈에 의존하지 않도록 설계한다.
    • 인터페이스나 추상 클래스를 통해 의존성을 느슨하게 만든다.
  2. 의존성 주입 (Dependency Injection)
    • 객체 간 의존 관계를 직접 설정하지 않고, 외부에서 주입받도록 설계해서 유연성과 테스트 용이성 높임
  3. SOLID 원칙 준수 (이 부분은 따로 정리하겠습니다. 아래의 링크를 확인해주세요)
  4.  모듈화
    • 의존성을 명확히 분리하여, 각 모듈이 독립적으로 동작할 수 있도록 한다.
  5.  테스트를 통한 의존성 관리
    • 모의(Mock) 객체를 사용하여 독립적으로 Unit Test를 수행
 

SOLID 원칙

급하신 분들은 마지막에 요약해놨으니, 그부분만 보셔도 무관합니다! 들어가기에 앞서...♤ 객체 지향 프로그래밍?정의절차적 프로그래밍 : 반복될 가능성이 있는 것들을 재사용이 가능한 함수

ghwjd5684.tistory.com

 

 

public class Singleton {
    // 1. private static instance 변수를 선언 (유일한 인스턴스)
    private static Singleton instance;

    // 2. private 생성자를 선언하여 외부에서 인스턴스 생성을 차단
    private Singleton() {
        System.out.println("Singleton Instance Created");
    }

    // 3. public static 메서드를 통해 유일한 인스턴스에 접근
    public static Singleton getInstance() {
        if (instance == null) { // 인스턴스가 생성되지 않았으면 새로 생성
            instance = new Singleton();
        }
        return instance; // 이미 생성된 인스턴스를 반환
    }

    // 추가 메서드: 싱글톤 클래스에서 제공할 기능
    public void showMessage() {
        System.out.println("Hello from Singleton!");
    }
}



// Main.java (다른 클래스임)

public class Main {
    public static void main(String[] args) {
        // 싱글톤 인스턴스 가져오기
        Singleton singleton1 = Singleton.getInstance();
        singleton1.showMessage(); // 출력: Hello from Singleton!

        // 두 번째 인스턴스 요청
        Singleton singleton2 = Singleton.getInstance();
        singleton2.showMessage(); // 출력: Hello from Singleton!

        // 동일한 인스턴스인지 확인
        System.out.println(singleton1 == singleton2); // 출력: true
    }
}

 

 

 

 

'CS > 디자인 패턴' 카테고리의 다른 글

(CS) 디자인 패턴 - 전략 패턴  (1) 2025.01.09
(CS) 디자인 패턴 - 팩토리 패턴  (0) 2025.01.09