객체지향 프로그래밍과 설계(5)

업데이트:

POCU의 개체지향 프로그래밍 및 설계 강의를 듣고 정리한 내용입니다.

static

System.out.println("hello");

  • out객체를 생성한 적이 없었는데, 메서드 호출이 가능?

static을 사용하면 전역 변수나 함수처럼 사용 가능하다.

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

  1. 단순한 계산도 객체를 생성해야 하나?
    • 절댓값 구하기, 공백 문자 개수 구하기
  2. 객체 단위가 아닌 클래스 단위에서 연산하고 싶을 때?
    • ex) 클래스에서 객체를 몇 개 만들었나?
public static int abs(int n) {
  return n < 0 ? -n : n;
}
  • 멤버 함수 시그니처에 static만 붙여주면 됨
  • 함수의 소유주는 인스턴스가 아닌 클래스
  • <개체명>.<함수명>(); 과 같이 호출 가능하나 권장 X

정적 클래스와 생성자

클래스로부터 생성한 객체에서도 정적 메서드가 호출 가능하다.

  • 객체 생성을 하지 못하게 하면 안되나?
  • private 생성자를 이용해 객체 생성 금지
    public class Math {
      private Math() {
      }
    
      public static int abs(int n) {
        return n < 0 ? -n : n;
      }
      ...
    }
    
    • new 키워드로 생성 시 컴파일 에러
    • static 메서드는 public이므로 호출 가능

정적 멤버 변수

클래스에서 객체를 몇 개 만들었나?

개체보다 상위 개념인 클래스의 static 멤버 변수 사용

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

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

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

public class ColaCan {
  private int remainingMl;
  private static int numCreated;
  ...
  public static void printStats() {
    System.out.println("# Cola Produced : " + numCreated);
  }
}
  • remainingMl 멤버변수에는 접근 가능할까?
    • 클래스 레벨에 속한 메서드가 객체 레벨에 속한 멤버(함수/변수)에 접근 불가
    • static 메서드는 어떤 객체의 상태를 특정할 수 없다.

static 정리

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

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

  • 클래스 단위로 접근 범위를 제어할 수 있음
  • 클래스 내부에 위치하므로 이름 충돌이 적다

디자인 패턴

  • 소프트웨어 설계에서 흔히 겼는 문제에 대한 해결책
  • 범용적, 반복적
  • 완성된 설계가 아님

장점

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

단점

  1. 고치려는 대상이 잘못됨
  2. 곧바로 적용할 수 없는 참고 가이드를 ‘패턴’이라 할 수 없음
  3. 잘못 적용하는 경우가 빈번함
  4. 비효율적인 해법이 되는 경우가 많음
  5. 다른 추상화 기법과 크게 다르지 않음

공부법

패턴을 적용해볼 생각은 최소 2년 경력 이후에! 내 코드가 정확히 어떻게 도는지 이해될 때까지 기본에 집중해라. 프로그래밍 처음 하는 사람에게 쉽게 설명할 수 있을 정도로.

싱글턴

  • 클래스에서 만들 수 있는 인스턴스 수를 하나로 제한하는 패턴
  • 프로그램 실행 중 최대 하나만 있어야 함
    • 프로그램 설정, 파일 시스템 등
  • 이 객체에 전역적으로 접근이 가능해야 함
public class Singleton {
    private static Singleton instance;

    private Singleton(){
    }

    public static Singleton getInstance(){
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • private 생성자
  • static 메서드를 통해서만 객체를 얻어올 수 있음
  • 객체가 없는 경우
    • 객체 생성 후 static 변수에 저장
    • static 변수에 저장된 객체를 반환
  • 생성된 객체가 있는 경우
    • static 변수에 저장되 있는 객체를 반환

예제

Math

public class Math {
    private static Math instance;
    
    private  Math(){
    }
    
    public static Math getInstance(){
        if (instance == null) {
            instance = new Math();
        }
        return instance;
    }

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

int absValue = Math.abs(-2); // 컴파일 에러

Math math = Math.getInstance();
int minValue = math.min(-2, 1);
int maxValue = Math.getInstance().max(3, 100);
  • 싱글턴을 만드는 데 필요한 것만 static
    • 나머지는 다 일반 메서드
  • 멤버 변수들도 static 아님

싱글턴 vs static

static으로 못 하는 일

  1. 다형성을 사용할 수 없다
  2. 시그니처를 그대로 둔 채 멀티턴(multiton) 패턴으로 바꿀 수 없다.
    • 멀티턴 : 객체를 하나가 아닌 여러개
  3. 객체의 생성 시점을 제어할 수 없다
    • static은 프로그램 실행 시 초기화됨
    • 단, 싱글턴 사용해도 제어에 어려움이 있다.

싱글턴 객체의 생성 시기

  • 처음으로 getInstance() 호출될 때
  • 하지만 보통 다양한 객체에서 getInstance()를 호출함
    • 싱글턴의 생성 순서를 알아내기 힘듬

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

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

싱글턴 패턴의 응용

싱글턴 생성 시 인자가 필요한 경우

public class GraphicsResourceManager {
  ...
  public static GraphicsResourceManager getInstance(FileLoader loader, GraphicsDevice gfxDevice) {
    if (instance == null) {
      instance = new GraphicsResourceManager(loader, gfxDevice);
    }

    return instance;
  }
}
  • 최초 생성 시에는 매개변수를 넣어주지만, 다른 클래스에서 싱글턴 객체를 받아올 떄 매개변수를 모른다면??
    • 객체 생성과 반환을 동시에 구현하기 힘듬
public static void createInstance(FileLoader loader, GraphicsDevice gfxDevice) {
  assert (instance == null) : "do not create instance twice";

  instance = new GraphicsResourceManager(loader, gfcDevice);
}

public static void deleteInstance() {
  assert (instance != null) : "no instance to delete";

  instance = null;
}

public static GraphicsResourceManager getInstance() {
  assert (instance != null) : "no instance was created before get()";

  return instance;
}

내포 클래스 nested class

  • 클래스 안에 다른 클래스가 들어가 있는 구조
  • java에서는 크게 둘로 나뉨
    • 정적 내포 클래스
    • 비정적 내포 클래스 = 내부 클래스

용도

  • 서로 연관된 클래스들을 그룹지을 수 있음
  • 내포 클래스는 바깥 클래스의 private 멤버에 접근 가능

내포 클래스를 사용 안 할 경우

public class Record {
    final byte[] rawData;

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

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

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

    public boolean canRead() {
        return this.position < this.record.rawData.length;
    }

    public byte readByte() {
        return this.record.rawData[this.position++];
    }

    ...
}

//main.java
Record record = new Record(fileData);
RecordReader reader0 = new RecordReader(record);
RecordReader reader1 = new RecordReader(record);

비정적 내포 클래스를 사용할 경우

public class Record {
    private final byte[] rawData;

    public Record(byte[] rawData) {
        this.rawData = rawData;
    }
    
    public class Reader {
        private int position;
        
        public boolean canRead() {
            return this.position < rawData.length;
        }
        
        public byte readByte() {
            return rawData[this.position++];
        }
        ...
    }
}

// main.java
Record record = new Record(fileData);
Record.Reader reader0 = record.new Reader();
Record.Reader reader1 = record.new Reader();

정적 내포 클래스를 사용할 경우

public class Record {
    private final byte[] rawData;

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

    public static class Reader {
        private final Record record;
        private int position;

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

        public boolean canRead(){
            return this.position < this.record.rawData.length;
        }
    }
}
// main.java
Record record = new Record(fileData);

Record.Reader reader0 = new Record.Reader(record);
Record.Reader reader1 = new Record.Reader(record);
  • Reader 클래스에 static을 붙임
    • static 클래스라는 의미 아님
    • 바깥 클래스의 레퍼런스가 없다는 의미
  • 비정적 내포 클래스는 암시적으로 컴파일러에서 정적 내포 클래스로 바꾸는 것

Reference

POCU 강의

태그: ,

카테고리:

업데이트:

댓글남기기