포스트

2020-11-24-TIL

소프트웨어도 부품을 쌓아 만든다

건축재료 -> 건축부품 -> 건축모듈 -> 건축시공

컴퓨터 -> 운영체제 -> 라이브러리 -> 프레임워크 -> 애플리케이션

건물이 재료를 부품으로, 부품을 모듈로 묶어 올라가듯이 소프트웨어도 아래 계층이 제공하는 것을 위 계층이 가져다 쓴다. 애플리케이션 개발자는 운영체제의 파일 처리나 네트워크 처리를 직접 구현하지 않고, 라이브러리와 프레임워크가 감싸 놓은 API를 호출한다. 각 계층은 아래 계층의 세부 구현을 몰라도 되도록 경계를 둔다.


컴포짓 패턴

디자인패턴 중에 컴포짓 패턴?

디렉터리도 하나의 파일이다. 컨테이너도 하나의 컴포넌트이다.

컴포짓 패턴은 “여러 개를 담는 것”과 “하나짜리”를 같은 타입으로 다루게 하는 구조다. 파일 시스템에서 디렉터리는 파일과 다른 디렉터리를 담지만, 바깥에서 보면 이름이 있고 옮기거나 지울 수 있는 하나의 항목이다. 그래서 “이 항목의 크기를 구하라”는 요청을 받으면 파일은 자기 크기를 돌려주고, 디렉터리는 담고 있는 항목들에게 같은 요청을 재귀적으로 보내 합친다.

Java AWT가 이 구조를 그대로 쓴다. java.awt.Container는 java.awt.Component를 상속하고, Javadoc은 이 클래스를 “a component that can contain other AWT components”라고 설명한다. 패널(Panel)도 컴포넌트이므로 다른 패널 안에 넣을 수 있고, 화면을 그릴 때는 바깥 컨테이너부터 안쪽 컴포넌트까지 같은 방식으로 내려간다.

1
2
3
4
5
Panel inner = new Panel();
inner.add(new Button("OK"));      // 컴포넌트를 담는다

Panel outer = new Panel();
outer.add(inner);                 // 컨테이너도 컴포넌트이므로 담을 수 있다

이벤트와 핸들러

이벤트가 발생하면 이벤트 클래스에 대한 객체가 생성된다. 운영체제나 JVM이 해당 이벤트를 일단 가져간 다음 누구한테 전달해줄 지를 결정해주어야 한다. 핸들러 함수를 호출해야하는데, 이벤트 핸들러가 없으면 처리할 수 없다.

이벤트 객체는 이벤트에 대한 정보와 그 이벤트가 어디서 발생했는지(이벤트 소스)를 담는다. Oracle의 Swing 튜토리얼도 “Each event is represented by an object that gives information about the event and identifies the event source.”라고 설명한다. 버튼 같은 이벤트 소스에 addActionListener()로 리스너를 등록해 두면, 클릭이 일어날 때 소스가 등록된 리스너의 actionPerformed()를 호출한다. 리스너를 등록하지 않았다면 이벤트는 발생해도 그것을 처리할 코드가 없다.


클로저

클로저 현상 - 내부 함수에서 클래스 변수를 사용가능한 것

Java에서는 내부 클래스(익명 클래스 포함)와 람다가 바깥 범위의 변수를 가져다 쓸 수 있다. 바깥 클래스의 필드는 자유롭게 읽고 쓸 수 있지만, 바깥 메서드의 지역 변수는 조건이 붙는다. JLS 8.1.3에 따르면 내부 클래스에서 쓰는 지역 변수는 final이거나 사실상 final(effectively final, 선언 후 다시 대입하지 않는 변수)이어야 한다. 이벤트 리스너를 익명 클래스로 만들면서 바깥 지역 변수를 쓸 때 자주 만나는 컴파일 오류가 이 규칙 때문이다.


toString()을 오버라이딩하지 않은 이유

  1. toString() 메서드의 목적은 정보를 문자화 시키는 것입니다. 특히 객체의 정보를 일정한 형식의 문자열로 표현하는 경우에 보다 의미있는 출력을 얻어낼 수 있을 것입니다. 예를 들어, 객체의 멤버를 , 로 구분하여 csv 포맷의 문자열로 얻어낼 수도 있고, json 포맷으로 얻어낼 수도 있을 것입니다. 이 프로젝트에서, 그리고 이 경우에는 굳이 toString()을 오버라이딩할 필요가 없다고 판단되어서 별도의 getPlayer()라는 메서드를 정의하였습니다.

Object.toString()의 기본 구현은 클래스이름@해시코드16진수 형태의 문자열을 돌려주고, Javadoc은 모든 하위 클래스가 이 메서드를 오버라이딩하기를 권한다. 다만 toString()은 디버깅과 로그에서 객체를 사람이 읽기 좋게 보여 주는 용도에 가깝다. 화면 출력 형식처럼 요구사항에 따라 바뀌는 문자열을 toString()에 맡기면, 로그 형식과 화면 형식이 서로 묶여 버린다. 출력 전용 메서드를 따로 둔 판단은 이 점에서 이해할 수 있다.


자바 인터페이스와 추상 클래스

Oracle 튜토리얼은 둘을 고르는 기준을 이렇게 정리한다. 밀접하게 관련된 클래스끼리 코드와 상태(필드)를 공유하고 싶으면 추상 클래스를 쓴다. 서로 관련 없는 클래스들이 같은 동작을 약속하게 하거나, 여러 타입을 동시에 구현하고 싶으면 인터페이스를 쓴다. 클래스는 하나만 상속할 수 있지만 인터페이스는 여러 개 구현할 수 있기 때문이다. HashMap이 AbstractMap을 상속하면서 Map, Cloneable, Serializable을 구현하는 것이 두 방식을 함께 쓰는 예다.


읽을 책

참고한 자료외부 출처 5

외부 출처

이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

댓글

아직 댓글이 없습니다