본문 바로가기

프로그래머/Java Managed Programming

[개체지향 프로그래밍] static | 싱글턴 | 내포 클래스

static, 싱글턴, 내포 클래스


static

모든 것이 개체 속에 있는 불편함

  1. 이런 단순한 계산도 개체를 만들어서 해야 하나?
  2. 개체 단위기 아니라 클래스 단위에서 뭔가를 하고 싶을 때는?

정적 멤버 함수 예

// Math.java
public class Math{
    public static int abs(int n){
        return n < 0 ? -n : n;
    }

    public static int min(int a, int b){
        return a < b ? a : b;
    }

    public static int max(int a, int b){
        return a > b ? a : b;
    }
}

// 메인 함수
int absValue = Math.abs(-2);
int minValue = Math.min(100, -200);

정적 멤버 함수

  • 멤버 함수 시스내처에 static만 붙여주면 됨
  • 이 멤버 함수의 소유주는 인스턴스가 아니라 클래스
  • 정적 멤버 함수를 호출할 때는?
    • <개체명>.<함수명>() - 세모
    • <클래스면>.<함수명>() - O
  • new를 이용해서 개체를 만들지 않아도 됨!

클래스 다이어그램과 정적 멤버 함수

  • 정적 멤버 함수 아래에 밑줄을 그음

Math 개체 생성 후, 함수 호출하기 예

// 메인 함수
Math math = new Math();
int absValue = math.abs(-2);
int minValue = math.min(100, -200);
  • 개체.메서드()는 클래스에 속한 메서드를 호출
  • 생성된 개체 수에 상관 없이 클래스는 단 하나만 존재
    • 즉, 어떤 개체라도 호출해야 하는 메서드를 특정할 수 있음

Math 개체 생성 금지 예

// Math.java
public class Math{
    private Math(){}

    public static int abs(int n){
        return n < 0 ? -n : n;
    }

    public static int min(int a, int b){
        return a < b ? a : b;
    }

    public static int max(int a, int b){
        return a > b ? a : b;
    }
}

// 메인 함수
int absValue1 = Math.abs(-2);   // OK

Math math = new Math();         // compile error
int absValue2 = math.abs(-2);
  • private 생성자는 꼼수에 가깝다
  • C#에는 static class가 있음

정적 멤버 변수

public class ColaCan {
    private int remainingMl;
    private static int numCreated;

    public ColaCan(int initialMl){
        this.remainingMl = initialMl;
        ++numCreated;
        // ++this.numCreated;       // OK
        // ++ColaCan.numCreated;    // OK
    }

    public void pour(int ml){
        // ml만큼 비우는 코ㅗ드
    }
}
  • static 메서드와 마찬가지로 밑줄을 그음

정적 멤버 변수에 접근하는 정적 메서드

public class ColaCan {
    private static int numCreated;
    //...
    public static void printStats() {
        System.out.println("# Cola Produced: " + numCreated);
    }
}

// 메인 함수
ColaCan.printStats();
ColaCan cola = new ColaCan(355);
ColaCan.printStats();
cola new ColaCan(500);
cola.printStats();

정적 메서드에서 비정적 메서드 접근하기

  • 불가능
  • 클래스에 속한 메서드가 개체에 속한 멤버(함수/변수)에 접근 불가
  • 정적 메서드에서는 정적 멤버 변수/함수만 접근 가능

static 정리

  1. static 멤버 변수 및 멤버 함수는 클래스에 속함(딱 하나만 존재)
  2. static 아닌 것은 개체에 속함(따라서 개체 수만큼 존재)
  3. 비정적 -> 정적: 접근 가능
  4. 정적 -> 비정적: 접근 불가능

static이 C의 전역 변수/함수보다 좋은 점

  • 접근 범위 제어 가능
  • 클래스 내부에 위치하기 때문에 이름 충돌이 적음

static을 비판하는 사람들

  • '모든 것은 개체여야 한다'라고 주장하는 소수설 지지자
  • 자칭 순수 OO 주창자들
  • static은 '순수한 OO가 아니다'
  • 다른 언어 진영의 사람들
  • OOP가 절차적 프로그래밍을 완전히 대체할 것이라는 도발 때문
  • 이 도발의 역효과로 오히려 다른 언어 진영에서 핀잔을 주곤 했음

static 비판에 대한 결론

  • static을 쓰는 게 OO의 개념과 먼 것은 사실
  • 그러나 OO의 개념과 멀다고 그게 잘못된 방법은 아님
  • 훌륭한 프로그래머의 자세
    • OO와 절차적 개념을 언제, 어디서 각각 써야 하는지 안다!
  • Java가 static을 넣은 건 잘한 일!

디자인 패턴과 싱글턴

  • 프로그래밍에서 주로 베스트 프랙티스라고 불려 온 것을 좀 더 범용적, 추상적 OO 설계로 발전시킨 게 디자인 패턴

디자인 패턴

  • 소프트웨어 설계에서 흔히 겪는 문제에 대한 해결책
  • 범용적, 반복적
  • 완성된 설계가 아님
    • 곧바로 코드로 바뀌지 않음
    • 어떤 문제를 다양한 환경에서 해결하는 법을 설명한 가이드로 생각하길
  • 흔히 GoF라고 불리는 저자들의 1994년 책에서 등장

디자인 패턴의 장점(이라 주장한 것)

  1. 이미 테스트를 마친 검증된 개발 방법을 사용해 개발 속도를 향상
  2. 공통 용어 정립을 통한 개발자들 간의 빠른 의사소통 촉진

디자인 패턴의 단점(으로 비판 받은 것)

  1. 고치려는 대상이 잘못됨
    • GoF 책의 패턴 중 과반수는 C++ 언어의 미지원 기능에 대한 미봉책
  2. 곧바로 적용할 수 없는 참고 가이드를 '패턴'이라 부를 수 없음
  3. 잘못 적용하는 경우가 빈번
    • 오히려 프로그램을 더 복잡하게 만듦
  4. 비효율적인 해법이 되는 경우가 많음
    • 디자인 패턴은 범용적, 추상적
    • 코드 중복이 많아지고 성능이 떨어질 수 있음
  5. 다른 추상화 기법과 크게 다르지 않음
    • 이미 프로그래밍 분야에 존재하던 현상에 왜 굳이 건축 용어를 사용?

세상이 바뀌었다

  • 자체 개발팀의 증가
  • 제품에 대한 사람들의 인식 혹은 비즈니스 모델 변화
    • 버전 별 배포, 서포트 기간
  • 배포 방식의 변화
    • 웹 서비스(간단한 함수 단위의 약속으로 회귀)
    • 인터넷을 통한 패키지 관리 시스템
  • Git 등의 버전 관리 시스템
    • 언제든 예전 코드로 돌아갈 수 있음
  • 인터넷에서 더 훌륭한 해답을 찾을 수 있음

싱글턴 패턴

  • 어떤 클래스에서 만들 수 있는 인스턴스 수를 하날 제한하는 패턴
  • 다음과 같은 조건을 충족하는 개체에 적합
    1. 프로그램 실행 중에 최대 하나만 있어야 함
      • 예: 프로그램 설정, 파일 시스템 등
    2. 이 개체에 전역적으로 접근이 가능해야 함
  • 딱 하나만 존재해야 하니 이름도 싱글턴

싱글턴 패턴의 비공식적 정의

  • 일부 사람들이 static을 싫어하는 이유
    1. 전역 변수 같아 보임
    2. 개체가 아님
    • 이러한 이유로 static 사용이 OO가 아니라고 주장
  • 싱글턴은 이러한 비판을 해결하는 패턴
  • 그러면서도 OO에서 전역 변수 및 전역 함수를 만드는 법

싱글턴 패턴의 클래스 다이어그램

  • private 생성자
  • static 메서드를 통해서만 개체를 얻어올 수 잇음
  • 아직 개체가 없는 경우
    • 개체를 생성 후 static 변수에 저장
    • static 변수에 저장된 개체를 반환
  • 이미 개체가 있는 경우
    • static 변수에 저장되어 있는 개체를 반환

static으로는 못하는 일

  1. 다형성을 사용할 수 없다
  2. 시그내처를 그대로 둔 채 멀티턴 패턴으로 바꿀 수 없다
  3. 개체의 생성 시점을 제어할 수 없다
    • Java의 static은 프로그램 실행 시에 초기화 됨
    • 단, 싱글턴을 사용해도 제어에 어려움이 있음

싱글턴 개체의 생성 시기

  • 처음으로 getInstance() 메서드가 호출될 때
  • 하지만 보통 다양한 개체에서 getInstance()를 호출함

초기화 순서를 보장하는 방법

  • 프로그램 시작 시 여러 싱글턴의 getInstance()를 순서대로 호출

싱글턴의 변형

  • 현재의 구현으로는 표현이 어려움
  • 따라서 실무에서는 다르 변형을 사용하기도 함
  • 디자인 패턴은 그저 가이드라인일 뿐

싱글턴의 변형 예

  • 프로그램 실행 시
    • getInstance()가 아니라 createInstance()를 호출
GraphicResourceManager.createInstance(loader, gfxDevice);
  • 인스턴스가 필요할 때
    • 매개변수가 없는 getInstance()를 호출
GraphicResourceManager gfxManager = GraphicResourceManager.getInstance();
  • 프로그램 종료 시
    • 더 이상 사용하지 않는 싱글턴 인스턴스 삭제 (메모리 해제)
GraphicResourceManager.deleteInstance();

안티패턴이란?

  • 안티패턴이란 올바르지 않은 방법을 의미
    • 즉, 배드 프랙티스를 의미
    • 베스트 프랙티스의 반대말
  • 안티패턴의 다른 예 : 매직 넘버, 매직 스트링

싱글턴은 안티패턴?

  • 실질적으로 static과 똑같다는 이유로 욕하는 사람들이 잇음
  • 싱글턴을 안 쓰고 같은 일을 할 수도 있음
    • 하지만 쓸데없이 복잡해짐
    • 모든 것이 개체여야만 하는 실용적 이유가 없으니 패스
  • 훌륭한 프로그래머는 이런 무의미한 주장에 신경 쓰지 않음

--

내포 클래스

  • 클래스 안에 다른 클래스

Java의 내포 클래스

  • 내포 클래스
    • 비정적 내포 클래스(=내부 클래스)
    • 정적 내포 클래스
  • C#, C++ 등의 언어에서는 정적 내포 클래스만 존재
  • 그래서 보통 inner와 nested라는 용어를 혼용해서 사용

내포 클래스의 용도

  1. 서로 연관된 클래스들을 그룹 지을 수 있음
    • 패키지로 그룹 짓는 것도 가능
    • 하지만 클래스 속에 넣는 것이 더 긴밀한 그룹
  2. 내포 클래스는 바깥 클래스의 private 멤버에 접근 가능
    • 하지만 그 반대의 경우는 불가능

별도 클래스 vs 비정적 내포 클래스

별도 클래스

public class Record {
    byte[] rawData;

    // ...
}

public class RecordReader {
    private final Record record;
    private int position;

    public RecordReader(Record record) {
        this.record = record;
    }

    // ...
}

비정적 내포 클래스

public class Record {
    private final byte[] rawData;

    public Record(byte[] rawData) {
        this.rawData = rawData;
    }

    public class Reader {
        private int position;
        // ...
    }
}