월별 글 목록: 2022년 2월월

Effective Java – static factory method

클래스의 인스턴스를 얻는 전통 수단은 public 생성자이다.

하지만, 꼭 알아둬야 하는 기법 -> static factory method

public class item1 {

  private String name;

  // public 생성자
  public item1(String name) {
    this.name = name;
  }

  // static factory method
  public static item1 myName(String name) {
    return new item1(name);
  }

  public static void main(String[] args) {
    item1 my = myName("haessae0"); // static factory method
    item1 my2 = new item1("haessae0"); // public 생성자
  }
}

이렇게 클래스는 정적 팩토리 소드를 제공할 수 있다.

장점

– 이름을 가질 수 있다.

– 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.

– 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.

– 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.

– 정적 팩토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

단점

– 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩토리 메서드만 제공하면 하위 클래스 생성 불가

– 정적 팩토리 메소드는 프로그래머가 찾기 어렵다.


장점

1. 이름을 가질 수 있다.

정적 팩토리 메소드는 이름만 잘 지으면 반환되는 객체의 특성을 명시적으로 표현할 수 있다.

public class item1 {

  private String name;

  // public 생성자
  public item1(String name) {
    this.name = name;
  }

  // static factory method
  public static item1 myName(String name) {
    return new item1(name);
  }

  public static void main(String[] args) {
    item1 my = myName("haessae0"); // static factory method
    item1 my2 = new item1("haessae0"); // public 생성자
  }
}

앞서 작성한 코드를 보자면

1.

public 생성자로 만든 것 -> 호출하였을 때, ‘haessae0’라는 것이 어떤 인스턴스 변수인지 알기 어렵다. -> 클래스 이름인 ‘item1’에게 전달하여 생성하는 것이기 때문이다.

2. 

정적 팩토리 메소드로 만든 것 -> 호출하였을 때, ‘haessae0’라는 것이 myName이라는 것을 알 수 있다. -> 내 이름이 haessae0라는 것을 알 수 있다.

당연히 알기 쉬운 정적 팩토리 메서드를 사용할 것이다.

또한, 

public 생성자는 하나의 시그니처로 하나만 생성할 수 있다.

public class item1 {

  private String name;
  private String birth;

  // public 생성자
  public item1(String name) {
    this.name = name;
  }

  // public 생성자 -> 불가
  public item1(String birth) {
    this.birth = birth;
  }

}

이미 name 변수를 받는 생성자가 존재 하기 때문에 birth를 받는 생성자는 생성할 경우 오류를 범하고 만다.

왜? 이미 하나의 변수를 받는 생성자가 존재하기 때문에 사용을 하여도 엉뚱한 메소드를 호출할 수 있다.

하지만! 정적 팩토리 메서드는 가능하게 해준다.

public class item1 {

  private String name;
  private String birth;

  // static factory method
  public static item1 myName(String name) {
    item1 my = new item1();
    my.name = name;
    return my;
  }

  // static factory method
  public static item1 myBirth(String birth) {
    item1 my = new item1();
    my.birth = birth;
    return my;
  }

}

이렇듯이 하나의 시그니처로 여러 가지를 구현할 수 있다.


2. 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.

불변 클래스는 인스턴스를 미리 만들거나 캐싱하여 재활용하는 방식이기에 불필요한 객체 생성을 막을 수 있다.

대표적으로 Boolean.valueOf(boolean) 메서드가 있다.

public final class Boolean implements java.io.Serializable,Comparable<Boolean> {
    public static final Boolean TRUE = new Boolean(true);
    public static final Boolean FALSE = new Boolean(false);

    public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }
}

상수 객체로 선언해주기 때문에 새로운 객체를 매번 만들지 않는다.

-> 생성 비용이 큰 객체가 자주 불려지는 상황에 정적 팩토리 메서드를 사용하면 그 성능을 많이 이끌어 낼 수 있다.

또한, 반복되는 객체 요청을 언제 어느 순간에 인스턴스를 살고 죽게 할지 통제하는 인스턴스 통제 컨트롤이 있다.

왜? 사용할까?

– 인스턴스를 통제하면 싱글톤이나 인스턴스화 불가 상태로 만들 수 있다.

– 불변 값 클래스에서 동치인 인스턴스가 하나인 것을 보장 -> a == b일 때만, a.equals(b)가 성립된다.

– 플라이 웨이트 패턴의 근간이고, 열거 타입은 인스턴스가 하나만 만들어지는 것을 보장한다.


3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.

코드의 유연성을 제공해준다. -> 인터페이스를 정적 팩토리 메서드 반환 타입으로 사용하는 인터페이스 기반 프레임워크를 만드는 핵심 기술이기도 하다.

동반 클래스에서 주로 사용된다.

자바 1.8 이전 -> 인터페이스에 정적 메서드를 선언할 수 없었다. 따라서 인터페이스에 기능을 추가하기 위해서는 동반 클래스라는 것을 만들어 그 안에 정적 메서드를 추가했다.

-> 굳이 별도의 문서를 찾아가며 수현 클래스가 무엇인지 알아보지 않아도 된다.


4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.

반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관없다. 심지어 다른 클래스가 객체를 반환해도 된다.

예시로 EnumSet 클래스가 존재한다.

public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
        Enum<?>[] universe = getUniverse(elementType);
        if (universe == null)
            throw new ClassCastException(elementType + " not an enum");

        if (universe.length <= 64)
            return new RegularEnumSet<>(elementType, universe);
        else
            return new JumboEnumSet<>(elementType, universe);
    }

클래스에는 noneOf라는 메서드가 존재하는데 잘 보면 universe의 개수가 64개 이하면 RegularEnumset의 인스턴스를 반환하고, 65개 이상이면 JumboEnumset의 인스턴스를 반환한다.

두 타입 모두 알려지지 않기 때문에 클라이언트는 인지하지 않아도 되며 나중에 삭제하거나 새로운 타입을 만들어도 문제없이 사용할 수 있다. 그저 Enumset의 하위 클래스에만 존재하면 된다.


5. 정적 팩토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

이러한 유연함은 서비스 제공자 프레임워크를 만드는 기반이 된다. -> 대표 JDBC

서비스 제공자 프레임워크의 구성요소

  • Service Interface : 구현체의 동작 정의
    • Connection
  • Provider Registration API : 제공자가 구현체를 등록할 때 사용
    • DriverManager.registerDriver
  • Service Access API : 클라이언트가 서비스의 인스턴스를 얻을 때 사용
    • DriverManager.getConnection
  • Service Provider Interface : 서비스 인터페이스의 인스턴스를 생성하는 팩토리 객체
    • Driver

단점

– 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩토리 메서드만 제공하면 하위 클래스 생성 불가

– 정적 팩토리 메서드는 프로그래머가 찾기 어렵다.

1. 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩토리 메서드만 제공하면 하위 클래스 생성 불가

java.util.Collections로 만든 구현체는 상속할 수 없다.

2. 정적 팩토리 메서드는 프로그래머가 찾기 어렵다.

Javadoc 문서에서 따로 정리하지 않는다. API 문서를 잘 써놓고 메소드 이름도 알려진 규약을 따라 짓는 식으로 문제를 해결해줘야 한다.


명명 방식

from매개 변수를 하나 받아 해당 타입의 인스턴스를 반환하는
형변환 메소드
Date d = Date.from(instant);
of여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는
집계 메소드
Set<Rank> faceCards =
EnumSet.of(JACK, QUEEN, KING);
valueOfFrom과 of의 더 자세한 버전BigInteger prime =
BigInteger.valueOf(Integer.MAX_VALUE);
instance
getInstance
매개변수로 명시한 인스턴스를 반환하지만, 
같은 인스턴스임을 보장하지 않는다.
StackWalker luke =
StackWalker.getInstance(options);
create
newInstance
instance & getInstance와 같지만, 매번 새로운 인스턴스
생성해 반환을 보장
Object newArray = 
Array.newInstance(classObject, arrayLen);
getTypegetInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메소드를 정의할 때 쓴다.
“Type”은 팩토리 메소드가 반환할 객체의 타입
FileStroe fs = Files.getFileStore(path);
newTypenewInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메소드를 정의할 때 쓴다.
“Type”은 팩토리 메소드가 반환할 객체의 타입
BufferedReader br =
Files.newBufferedReader(path);
typegetType과 newType의 간결한 버전List<Complaint> litany =
Collections.list(legacyLitany);

핵심

정적 팩토리 메서드와 생성자는 각자의 쓰임새가 있어 상대적인 장단점 이해 후 사용
그래도 정적 팩토리 메서드가 훨씬 좋다.

밥약 (2)

에브리타임 시간표를 기반으로 여러 시간표를 비교해서 공강 시간을 알려주는 프로젝트입니다.

지난 게시글에서 밥약을 만들기 위해서 “시간표 비교 알고리즘(공강 시간 반환)”을 해결해야 한다고 하였습니다.

파이썬 Request 라이브러리를 이용하여 에브리타임 시간표를 파싱 하고 BeautifulSoup를 이용하여 원하는 시간표 값을 파싱 하였습니다.

시간표 비교를 위해 빈 배열의 시간을 만들고 파싱한 시간표를 대입하여 공강 시간을 추출하였습니다.

현재 기능적으로 공강 시간은 추출되나 효율적인 코드 제작을 위해 검토하고 개선할 계획입니다. 이후 카카오톡에 적용하여 시범 서비스할 예정입니다.

파이썬 라이브러리 소개: Celery

비동기 작업을 손쉽게 관리해주는 라이브러리 Celery 에 대해 간단히 소개 드리겠습니다.

파이썬에서는 비동기 모듈을 사용하지 않으면 기본적으로 동기 방식으로 작업을 처리합니다. asyncio를 사용하지 않고 비동기 작업을 원할 때 Celery 를 사용하면 됩니다.

Celery 는 라이브러리지만 broker 와 backend 가 필요합니다. broker는 큐이고 backend 는 샐러리가 처리한 결과물을 저장합니다. 아래 그림을 보시면 이해가 쉽습니다.

Celery 작동 흐름

python 에서 celery 라이브러리를 이용해 작업 요청을 하면 celery 라이브러리는 broker 에 메시지를 삽입합니다(큐에 추가). 다른 프로세스인 worker 는 대기하고 있다가 큐에 메시지가 들어오면 읽고 작업을 수행합니다. 작업이 완료되면 backend 에 작업 결과물을 저장합니다.

그림에서는 마치 동기적인 작업처럼 그려 놓았는데 작업 요청은 non-blocking 입니다. 따라서 시간이 오래 걸리는 여러 개의 작업을 먼저 요청해 두고 다른 작업을 하다가 나중에 결과 수신을 해도 되는 것이죠. 수신의 경우엔 blocking 입니다. 작업이 완료될때까지 계속 대기합니다. 이 경우 코드는 아래와 비슷해질 것입니다.

def main():
    # 시간이 오래 걸리는 작업 요청
    hw1 = heavy_work_1.delay()
    
    # 다른 작업 수행
    work2_result = work2()
    ...
    
    # 결과 수신
    hw_result = hw1.get()
    
    return hw_result, work2_result

이번에는 흔한 예제를 보겠습니다. 회원가입 예시인데요. 회원가입을 하게 되면 database 에 레코드를 추가하는 작업은 오래 걸리지 않지만 이메일 발송이 2초에서 3초 정도 소요되는 경우가 발생합니다. 이런 절차를 전부 기다리게 되면 고객이 가입 버튼을 누르고 2초에서 3초정도 기다리게 됩니다. 이 때 celery 에게 메일 발송 작업을 요청하고 고객에게 응답을 바로 전송하면 지연 시간 없이 다음 페이지로 넘어가게 됩니다. 확인 메일 발송에는 보통 반환값을 기다릴 필요가 없기 때문에 결과를 수신하는 코드는 제거했습니다.

def register_user(username, password, email):
    # 데이터베이스에 회원 레코드 삽입
    db_success = insert_to_database(username, password)
    
    if not db_success:
        return False
        
    # 확인 메일 발송 (non-blocking)
    send_verification_email.delay(username, email)
    
    # 메일 발송 기다리지 않고 응답 전송
    return True

두 가지 시나리오로 간단하게 celery 라이브러리를 알아봤는데요. 기회가 된다면 celery 의 지원 범위와 사용 예시를 포스팅하도록 하겠습니다.

밥약 (1)

에브리타임 시간표를 기반으로 여러 시간표를 비교해서 공강 시간을 알려주는 프로젝트 입니다.

대학생활에서는 사람들마다 다 다른 시간표로 대학생활을 하고 있습니다. 그래서 서로 점심을 먹거나 약속을 잡을때 시간잡기가 어렵습니다. 파이썬 공부를 위해 에브리타임 시간표를 분석하여 서로의 공강시간을 알려주는 프로젝트를 진행 하고자 합니다.

구상하고 있는 밥약의 기능은 다음과 같습니다..

  1. 로그인 기능으로 자신의 시간표를 저장해야함
  2. 비로그인 기능으로 단순 시간표 기능 제공
  3. 여러명의 시간표를 넣어 공강시간을 표시
  4. 비교 기록 저장

현재 에브리타임 시간표를 파싱하는 기능은 제작하였습니다. 하지만 밥약을 만들기 위해서 해결해야 할 가장 큰 문제가 있습니다.

  1. 시간표 비교 알고리즘(공강 시간 반환)

일단 시작하는 단계이니 조금더 정리하여 다뤄보도록 하겠습니다.

토이프로젝트 디지털액자 (1)

원룸에 기본 옵션으로 있는 TV를 다양한 정보가 담긴 디지털 액자?로 만드는 프로젝트입니다.

자취를 하면 원룸에 옵션으로 딸려오는 TV가 있는데요. 이건 화질도 좋지 못하고 공간만 잔뜩 차지합니다. 개인적으로 TV를 자주 보지 않기 때문에 무관심으로 1년을 지낼 수도 있지만 마침 눈에 잘 밟히는 자리에 있기에 어떻게든 활용해 보려 합니다.

일단 구상하고 있는 액자 기능은 다음과 같습니다.

  1. 오전 6시에 자동으로 켜져야 함
  2. 오후 9시에 자동으로 꺼져야 함
  3. 주간 날씨와 평균 기온 표시
  4. 시간별 경산시 기온 표시
  5. 회사 이메일 / 개인 이메일 안 읽은 개수 표시
  6. 주간 일정 표시

그리고 준비물은 아래와 같습니다

  1. TV
  2. 미니 PC(라즈베리파이 같은)
  3. 리모컨 전파 발신기(?)

티비를 켜고 끄기 위해서 3번이 필요할 것 같습니다. 아니면 IoT 콘센트 등으로 전원을 내리는 방법이 있는데 다시 켰을 때 TV가 잘 켜질지는 미지수입니다.

그리고 디지털 액자를 만들기 위해 해결해야 할 문제입니다.

  1. 디스플레이 되는 웹 서버
  2. 여러 데이터 소스에서 데이터 조회 및 가공
  3. 미니 PC를 부팅했을 때 자동으로 웹 페이지 띄우기
  4. 시간에 맞춰 TV ON/OFF 신호 송신

일단 계획은 이렇게 잡고 미니 PC부터 다뤄보도록 하겠습니다.