과학

자바로 프로그래밍에 입문할래요: 3.3. 자료형 설계하기 (1)

 이제 내가 번역할 마지막 절이야. 4개의 파트로 나누어서 업로드 될거야.

이번 시간은 API에 대한 다양한 이야기와, 특히 캡슐화에 대해서 다뤄볼 거야.

 

3.2. 자료형 생성하기 (3)

3.2. 자료형 생성하기 (2)

3.2. 자료형 생성하기 (1)

3.1. 자료형 (4)

3.1. 자료형 (3)

3.1. 자료형 (2)

3.1. 자료형 (1)

2.3. 재귀 (2)

2.3. 재귀 (1)

2.2. 라이브러리와 클라이언트 (3)

2.2. 라이브러리와 클라이언트 (2)

2.2. 라이브러리와 클라이언트 (1)

2.1. 정적 메소드 (2)

2.1. 정적 메소드 (1)

1.5. 입출력 (2)

1.5. 입출력 (1)

1.4. 배열 (3)

1.4. 배열 (2)

1.4. 배열 (1)

1.3. 조건문과 반복문 (2)

1.3. 조건문과 반복문 (1)

1.2. 내장 자료형 (2)

1.2. 내장 자료형 (1)

0.0. 여는 글, 1.1. 첫 프로그램 만들기

 

================================

 

자료형 설계하기

  자료형을 생성할 수 있게 됨으로써, 모든 프로그래머들은 언어 디자이너가 될 수 있습니다. 여러분들은 내장 자료형의 굴레에서 벗어나 자신만의 자료형으로 원하는 클라이언트 프로그램을 작성할 수 있습니다. 

 

  자바는 복소수 자료형을 내장하지 않았으나, 여러분들은 <Complex>를 정의하고 <Mandelbrot> 프로그램을 작성하였죠. 자바는 터틀 그래픽을 내장하지 않았으나, 여러분들은 <Turtle>을 정의하고 다양한 클라이언트 프로그램들을 작성하였습니다. 

 

  또한 자바에서 특정 기능을 지원하더라도, 그것을 자신이 사용하기 편하게 조정하여 사용할 필요가 있습니다. 자바는 입출력에 대한 기능을 충분히 제공하지만, Std* 라이브러리를 이용해서 좀 더 편하게 사용한 것처럼 말이죠.

 

  이제부터 우리가 프로그램을 만들 때 고민해야 할 첫번째 사항은, 우리가 필요한 자료형에 대해서 이해하는 것입니다. 이러한 행위는 곧 설계 행위입니다. 이번 절에서 우리는 어떤 프로그램의 개발이든 간에 핵심적인 단계인, API를 개발하는 것에 집중해볼 것입니다.

 

  다양한 대안에 대해서 고민해보고, 그것이 클라이언트 프로그램과 구현에 끼치는 영향을 이해하며, 구현 전략과 클라이언트의 필요 사이에서 적절한 균형을 맞추기 위해 설계를 고쳐볼 것입니다. 

 

  이번 절에서 우리는 캡슐화, 불변성, 상속에 대해 논의할 것입니다. 특히, 모듈화 프로그래밍과 디버깅을 가능케 하고, 명료하고 정확한 코드를 작성할 수 있게 만드는 자료형 설계에 집중할 것입니다.

 

  이번 절의 마지막에는, 런타임에서 설계의 가정을 점검하기 위해 사용되는 자바의 메커니즘에 대해 이야기할 것입니다. 이러한 도구들은 신뢰성 높은 소프트웨어를 개발하는데 굉장히 큰 도움이 됩니다.

 

 

API 설계하기

  3.1절에서, 우리는 API를 사용하는 클라이언트 프로그램을 작성해봤습니다. 3.2절에서, 우리는 API를 구현했습니다. 이제는 API를 설계하는 방법에 대해 생각해볼 차례입니다. 프로그래밍의 대부분은 클라이언트 프로그램 작성이기 때문에, 일부러 이러한 순서로 주제들을 다뤘습니다.

 

  소프트웨어 구축에서 가장 중요하고 가장 어려운 문제는 바로 API 설계입니다. 이 작업은 많은 연습과 신중한 고민, 수많은 반복이 필요하죠. 하지만 이 작업을 위해 아무리 많은 시간을 소모한들, 디버깅과 코드 재사용 등으로부터 그 시간을 다시 보상받을 것입니다.

 

  아주 작은 프로그램을 작성할 때조차 API를 명세하는 것은 과한 작업인 것처럼 보입니다. 하지만 여러분들은 어떤 프로그램이든 언젠가 그 코드를 재사용 할 날이 올 수도 있다는 사실을 명심해야 합니다. 여러분의 미래는 여러분이 결정하는 것이 아닙니다.

 

 

표준(Standards)

  API를 작성하는 것이 왜 그렇게 중요한 것인지는, 다른 분야를 생각해보면 쉽게 이해할 수 있습니다. 기차 선로, 너츠와 볼트(나사), 팩스 기계, 라디오 주파수, DVD 표준 등. 공용 표준 인터페이스를 사용하면, 그 기술을 광범위하게 적용할 수 있습니다. 자바 역시 이러한 예시 중 하나입니다. 여러분들의 자바 프로그램은 자바 가상 머신의 클라이언트이며, 이는 다양한 하드웨어와 소프트웨어 플랫폼에 구현된 표준 인터페이스입니다. API를 통해 클라이언트를 분리로부터 분리시키고, 표준 인터페이스의 이점을 우리가 작성하는 모든 프로그램으로부터 얻을 수 있습니다.

 

 

명세 문제(Specification problem)

  우리의 API는 각 메소드가 무엇인지에 대해 간단하게 영어로 적은 목록입니다. 이상적으로, API는  가능한 모든 입력에 대한 행위들을 명확히 표현해야 합니다. 부작용(side effects)를 포함해서요! 그리고 구현이 명세를 잘 충족하는지 확인하는 소프트웨어를 갖게 됩니다. 

 

  불행히도, 명세 문제라고 알려진 컴퓨터 과학 이론의 법칙에 의해 이런 목표는 절대 달성할 수 없습니다. 이러한 명세는 프로그래밍 언어처럼 정형 언어로 작성돼야 하며, 두 프로그램(명세된 프로그램과 실제 구현)이 같은 계산을 수행할 것인지를 결정하는 문제는 수학적으로 해결 불가능합니다. 따라서 API 주변에 삽입된 텍스트들과 같이 비공식적인 설명들에 의지하기도 하죠.

 

 

와이드 인터페이스(Wide interfaces)

  와이드 인터페이스란 메소드의 수가 너무 많은 것입니다. API를 설계할 때 따라야 할 중요한 원칙 중 하나는 와이드 인터페이스를 피하는 것입니다. API의 크기는 시간이 가면 갈수록 커지는 경향이 있습니다. 이미 존재하는 API에 메소드를 추가하는 것은 아주 쉽지만, 이미 존재하는 클라이언트들을 깨지 않으면서 메소드를 삭제하는 것은 아주 어렵기 때문입니다. 간혹 와이드 인터페이스가 정당화되는 경우도 있습니다. <String>과 같이 널리 사용되는 시스템 라이브러리의 경우죠. 

 

  다양한 기술은 인터페이스의 크기를 실질적으로 줄이는데 도움이 됩니다. 예를 들어, 우리는 2장에서 모든 자료형에 대응하는 메소드의 구현을 오버로딩으로 제공하는 몇몇 라이브러리들을 살펴봤었습니다. 또 다른 방법은 기능적으로 수직적 관계에 있는 메소드만을 구현하는 것입니다. 예를 들어, 자바의 <Math> 라이브러리는 sin(), cos(), tan()을 구현하지만, sec()는 구현하지 않습니다. (기존의 메소드들과 수직적 관계에 있는 메소드가 아니기 때문입니다)

   

 

클라이언트 코드로부터 시작하기

  자료형 개발의 기본적 목적 중 하나는 클라이언트 코드를 단순화 하기 위함입니다. 따라서, API의 설계 처음부터 클라이언트 코드의 형식에 집중하는 것이 좋습니다. 대부분의 경우 이렇게 하는 것은 문제가 되지 않습니다. 자료형을 개발하는 전형적인 이유는 대부분 크고 복잡한 클라이언트 코드를 단순화하기 위함이기 때문입니다.

 

  여러분이 별로 좋지 않은 클라이언트 코드를 사용하는 자기 자신을 발견하였을 때, 이를 개선할 방법은, 코드의 세부사항과 상관 없이 먼저 자신이 생각하는 방법대로의 높은 수준에서 간단화된 코드를 작성해보는 것입니다. 그리고 간단화된 버전에 대해 주석을 달아보고, 이를 다시 코드로 작성해보는 것이 하나의 시작점이 될 수 있습니다.

 

  자료형에 대한 주문을 다시 생각해보죠: "자료 및 해당 자료와 관련된 연산들을 명확하게 분리할 수 있다면, 그렇게 해야 합니다." 어떤 소스던 간에, 이는 보통 구현 이전에 클라이언트 코드를 작성하는 것이 좋습니다. 두 개의 클라이언트를 작성해보면 더욱 좋습니다. 클라이언트 코드로부터 시작하는 것은 구현을 개발하는 것이 곧 확실한 효과가 있을 것임을 보장하기도 합니다.

 

 

표현에 의존하지 않기

  보통 API를 개발할 때, 우리 생각을 표현하려고 합니다. 자료형은 값의 집합과 그 값에 대한 연산의 집합이며, 값을 모르고 작업에 대해 이야기 하는 것은 당연히 의미가 없습니다. 하지만 값의 표현을 아는 것과는 조금 다릅니다. 자료형의 목적 중 하나는 특정 표현법에 대한 종속성과 세부사항을 피할 수 있게 함으로써 클라이언트 코드를 단순화 하는 것입니다. 예를 들어, <Picture>와 <StdAudio>의 클라이언트 프로그램은 그림과 소리에 대한 간단한 추상적 표현만으로 작동합니다. 이러한 추상화를 위한 API들의 주요 가치는, 클라이언트 코드들에게 이러한 추상화들의 표준 표현에서 발견되는 상당한 양의 세부 사항을 숨길 수 있다는 것입니다.

 

 

API 설계의 주의사항

  API는 아마 굉장히 구현이 어렵거나 개발하기 불가능할 수도 있으며, 사용하기 아주 어려울 수도, API를 사용하는 것이 클라이언트 코드를 더 복잡하게 만들 수도 있습니다. 어떤 API는 아주 초라해서, 클라이언트가 필요한 메소드들이 없을 수 있습니다. 또 어떤 API는 너무 거대해서, 클라이언트가 필요하지도 않은 메소드들도 포함할 수 있습니다. 또 어떤 API는 너무 일반적이어서 추상화가 유용하지 않을 수 있고, 어떤 API는 과하게 특수하고 세부적이어서 추상화가 유용하지 않을 수 있습니다. 따라서 우리는 다음의 신조를 따르도록 합시다: "클라이언트들이 필요한 메소드만을 제공한다."

 

  여러분이 프로그래밍을 처음 시작했을 때에는, <HelloWorld>가 어떤 영향을 끼치는지에 대한 이해 없이 코드를 작성했습니다. 그 시작점으로부터, 여러분은 코드를 따라써보고 가끔은 여러 문제를 풀기 위해 자신만의 프로그램을 작성하기도 했을 것입니다. 마찬가지로, 여러분은 현재 API 설계의 시작점에 놓여 있습니다. 

 

 

캡슐화(Encapsulation)

  정보를 은닉함으로써 클라이언트를 구현으로부터 분리하는 작업을 캡슐화라고 합니다. 구현의 세부사항은 클라이언트에게 숨기고, 구현은 향후 생성될 클라이언트 코드의 세부사항을 알 방법이 없습니다.

 

  우리는 우리의 자료형 구현에서 캡슐화를 연습하고 있었습니다. 3.1절에서 우리는 "특정 자료형을 사용하기 위해서 해당 자료형이 어떻게 구성되어있는지 전혀 알 필요가 없음"을 알았습니다. 이 문장은 캡슐화의 주된 이점을 표현합니다. 이를 빼놓고 자료형 설계의 다른 방법에 대해 이야기하는 것은 의미가 없을 정도로 중요합니다. 다음은 왜 우리가 캡슐화를 해야하는지 세 가지의 주된 이유입니다.

  • 모듈화 프로그래밍을 활성화 합니다.
  • 디버깅을 가능케 합니다.
  • 프로그램 코드를 명확하게 합니다.

  이들은 서로 연결된 개념입니다. 잘 설계된 모듈화 코드는 디버깅하기 쉬우며, 기본 자료형에만 의존하는 긴 프로그램보다 이해하기 쉽습니다.

 

 

모듈화 프로그래밍(Modular Programming)

  우리가 2장에서 함수를 배우면서 개발했던 프로그래밍 스타일은 거대한 프로그램을 작은 모듈로 쪼개어, 그것들이 각각 독립적으로 개발되고 디버깅할 수 있게 만드는 개념에 기반합니다. 이러한 방법은 변경의 영향을 국부화하고 한정시켜 소프트웨어를 탄력적으로 만들고, 자료형의 성능, 정확도, 메모리 추적 등을 향상시키기 위해 새로운 구현으로 대체할 수 있게 함으로써 코드 재사용을 가능하게 합니다. 

 

  우리는 시스템 라이브러리를 사용할 때 캡슐화의 이점을 종종 얻습니다. 새로운 버전의 자바 시스템은 다양한 자료형 혹은 정적 메소드 라이브러리의 새로운 구현을 포함하곤 합니다. 하지만 API는 바뀌지 않죠. 이는 자료형 구현을 끊임없이 향상시키고자 하는 강력한 의지로부터 기인되는데, 모든 클라이언트들은 향상된 구현으로부터 잠재적인 이점을 얻을 수 있기 때문입니다.

 

  모듈화 프로그래밍을 성공하는 핵심은 모듈 간의 독립성을 유지하는 것입니다. 우리는 클라이언트와 구현 사이의 유일한 의존점이 바로 API라고 생각함으로써, 이러한 독립성을 유지합니다.  "특정 자료형을 사용하기 위해서 해당 자료형이 어떻게 구성되어있는지 전혀 알 필요가 없습니다." 이는 다시 말하면, 자료형 구현 코드는 클라이언트 코드가 (구현의 세부사항이 아닌)API만을 알고있다고 가정해야 한다는 것입니다.

 

 

예제

  예를 들어, <Complex>를 봅시다. 이는 <프로그램 3.2.6>과 동일한 이름, 동일한 API를 지녔지만, 복소수의 표현 방법이 다릅니다. <프로그램 3.2.6>의 경우 직교(cartesian)좌표계 사용했습니다. 변수 x와 y는 복소수 x+iy를 표현하죠. <프로그램 3.3.1>의 경우 극(polar)좌표계를 사용합니다. 변수 r과 theta는 r(cos θ + i sin θ) 형식의 복소수를 표현합니다.

 

  극좌표계는 꽤 흥미로운데, 복소수의 특정 연산에 대해서는 극좌표계에서 연산하는 것이 훨씬 더 쉽기 때문입니다. 덧셈과 뺄셈은 직교좌표계에서 하는 것이 더 쉽고, 곱셈과 나눗셈은 극좌표계에서 하는 것이 더 쉽습니다. 각 경우에 성능의 차이가 극적으로 달라지죠.

 

 

프로그램 3.3.1: Complex numbers (alternate)

 
public final class Complex {
    private double r;        // distance
    private double theta;    // angle

    // constructor that takes in rectangular coordinates
    public Complex(double re, double im) {
        r     = Math.sqrt(re*re + im*im);
        theta = Math.atan2(im, re);
    }

    // accessor methods
    public double re() { return r * Math.cos(theta); }
    public double im() { return r * Math.sin(theta); }

    // return a string representation of this complex number
    public String toString()  {
        return re() + " + " + im() + "i";
    }

    // return this Complex number plus b
    public Complex plus(Complex b) {
        Complex a = this;
        double re = a.r * Math.cos(a.theta) + b.r * Math.cos(b.theta);
        double im = a.r * Math.sin(a.theta) + b.r * Math.sin(b.theta);
        return new Complex(re, im);
    }

    // return this Complex number times b
    public Complex times(Complex b) {
        Complex a = this;
        Complex c = new Complex(0, 0);
        c.r = a.r * b.r;                // can't make r and theta final
        c.theta = a.theta + b.theta;    // because of these two statements
        return c;
    }

    // return the magnitude / absolute value of this complex number
    public double abs() { return r; }

    // sample client for testing - calculates roots of unity
    public static void main(String[] args) {
        Complex a = new Complex(5.0, 6.0);
        StdOut.println("a = " + a);

        Complex b = new Complex(-2.0, 3.0);
        StdOut.println("b = " + b);

        Complex c = b.times(a);
        StdOut.println("c = " + c);
    }

}
% java Complex
-7.0 + 7.0i

 

  캡슐화의 개념은 클라이언트 코드의 변화 없이 이러한 프로그램 중 하나로 대체할 수 있다는 것입니다. 두 구현 중 무엇을 선택할지는 클라이언트에 따라 다르겠죠. 실제로, 원칙적으로 클라이언트와의 유일한 차이점은 성능 속성의 차이여야만 합니다. 

 

  이러한 능력은 여러가지 이유로 아주 중요합니다. 이유 중 하나는 이것이 우리에게끔 소프트웨어를 끊임없이 개선하도록 만든다는 것입니다: 더 좋은 방법으로 자료형을 구현할 수 있다면, 클라이언트들 역시 이득입니다. 새로운 소프트웨어 시스템을 설치할 때마다 사실 여러분들은 이러한 이점을 이용하고 있는 것입니다.

 

 

Private

  private 접근 지정자는 자바에서 캡슐화를 실현하기 위한 장치입니다. 여러분이 변수를 private으로 선언하면, 어떤 클라이언트도 해당 인스턴스 변수(혹은 메소드)에 접근할 수 없게 됩니다. 클라이언트는 오로지 API를 통한 public 메소드와 생성자에만 접근할 수 있죠. 따라서 클라이언트에 영향을 미치지 않아서 알 필요가 없는 구현의 메소드(혹은 변수)를 private으로 지정할 수 있습니다.

 

  자바는 모든 인스턴스 변수가 private이 되어야 할 필요는 없습니다만, 우리는 그렇게 하도록 합시다. 예를 들어, 만약 <Complex>의 인스턴스 변수 re와 im이 public이었다면, 클라이언트는 이들에게 직접 접근하는 코드를 작성할 수 있었을 것입니다. 만약 z가 <Complex> 객체를 참조하는 경우, z.re와 z.im을 통해서 접근할 수 있습니다. 

 

  하지만 어떤 클라이언트든 간에 이런 식으로 구현에 의존(re와 im, 세부적인 사항에 접근)하게 된다면, 이는 캡슐화의 원칙을 위반하는 것입니다. <프로그램 3.3.1>과 같이 다른 구현으로 치환된다면, 구현에 의존하는 코드들은 전부 쓸모 없게 되버리죠. 이러한 상황으로부터 우리를 보호하기 위해, 우리는 언제나 인스턴스 변수들을 private으로 선언할 것입니다. 인스턴스 변수를 public으로 선언할 마땅한 이유가 없습니다. 

 

 

미래 계획

  자료형을 캡슐화하지 않은 프로그래머 때문에 상당한 비용이 발생한 사례들은 수도 없이 많습니다.

  • 우편 번호. 1963년, 미 우편당국(USPS)은 편지의 배달과 정리를 쉽게 하기 위해 5자리 우편 번호의 사용을 시작했습니다. 프로그래머들은 5자리 우편 번호를 영원히 쓸 것이라고 생각하고 소프트웨어를 작성했습니다. 32-비트 정수로 표현했죠. 1983년, 미 우편당국은 확장된 우편 번호인 ZIP+4를 소개하였고, 기존의 5자리 우편 번호에서 4자리가 더 추가되었습니다.
  • IPv4 vs IPv6. 인터넷 프로토콜은 인터넷에서 전자 기기들이 데이터를 교환하기 위해 사용되는 표준입니다. 각 기기는 고유의 정수 혹은 주소를 지닙니다. IPv4는 32-비트 주소를 사용하여 최대 43억 개의 주소를 표현할 수 있습니다. 인터넷이 폭발적으로 성장하면서, 새로운 IPv6이 등장하였고, 128-비트 주소로 2^12 개의 주소를 표현할 수 있게 되었습니다.
  • 차량 식별 번호. 차량 식별 번호는 차량을 식별하기 위한 17자의 숫자입니다. 생산자, 모델, 생산연도, 그 외의 차, 트럭, 버스 등에 대한 속성들을 담고 있죠. 이는 1981년도에 만들어졌습니다. 하지만 자동차 회사들은 이 숫자들을 2010년에 전부 동내버렸죠. 결국 이미 존재하는 차량 식별 번호를 재사용하거나, 차량 식별 번호의 길이를 증가시켜야만 했습니다.

  이러한 경우들에서 어쩔 수 없이 생기는 내부적 표현의 변화는, 해당 표준을 따르고 있는 수많은 클라이언트 코드들은 대부분 제대로 작동하지 않게 될 것입니다. 앞서 보여드린 변경에 대한 비용은 각각 약 1000억 원에 달합니다! 겨우 단 하나의 숫자를 캡슐화하지 않은 실패로부터 비롯된 것이죠.

 

  이런 위기는 여러분들과 거리가 먼 이야기처럼 보일 수 있으나, 이러한 캡슐화의 이점을 사용하지 않는 모든 프로그래머들은 변경에 직면했을 때 수많은 시간과 노력을 허비해야 함을 확실히 알아야 합니다. 모든 인스턴스 변수를 private 접근 지정자로 정의하는 약속은 이러한 문제들로부터 우리를 지켜줍니다. 만약 우편 번호, IP 주소, 차량 식별 번호 등에서 이러한 약속을 지켰었다면, 클라이언트들에게 아무런 영향 없이 손쉽게 내부를 변경할 수 있었을 것입니다. 자료형의 구현은 자료 표현을 알고 있으며, 객체는 자료를 쥐고 있습니다. 클라이언트는 오로지 객체의 참조만을 쥐고 있으며, 세부사항은 알지 못합니다.

 

 

잠재적 오류를 제한하기

  캡슐화는 프로그래머에게 코드가 의도한 대로 잘 동작함을 보장해주기도 합니다. 한 가지 예로, 소름 돋는 일화 하나를 소개해드리죠: 2000년 미국 대선, 플로리다 볼루시아 카운티에 있는 전자 투표기는 앨 고어(Al Gore)가 -16,022 개의 표를 받았다는 결과를 출력했습니다. 표를 집계하는 변수는 투표기 소프트웨어에서 캡슐화가 잘 이루어지지 않았었죠. 이 문제가 왜 발생했는지 이해하기 위해, 다음 API를 구현하는 간단한 집계 프로그램 <Counter>를 소개합니다.

 

<Counter>의 API


  이 추상화는 다양한 분야에서 유용합니다. 물론 전자 투표도 포함이죠. 이는 정수 하나를 캡슐화하고, 이 정수에 행할 수 있는 유일한 연산은 1을 증가시키는 것뿐임을 보장합니다. 따라서, 절대로 음수가 될 수 없죠. 추상화의 목표는 자료형에 대한 연산을 제한하는 것입니다. 

 

  또한 자료형의 연산을 고립시키죠. 예를 들어, 우리는 집계의 일관성 확인을 위해, 기록과 관련한 새로운 구현을 추가할 수 있고, increment()는 각 투표에 대해 당시의 시각이나 기타 정보를 저장하게 할 수 있습니다. 하지만 private 지정자가 없다면, 투표기의 어딘가에 다음과 같은 코드가 작성되어 있을 수도 있다는 것입니다.

 

 
Counter counter = new Counter("Volusia", VOTERS_IN_VOLUSIA_COUNTRY);
counter.count = -16022;

 

  private가 있다면, 이 코드는 컴파일되지 않습니다. 고어의 득표량이 음수가 될 일도 없었겠죠. 캡슐화를 사용하는 것은 투표 보안 문제의 완벽한 해결책으로부터 조금 멀리 있긴 합니다만, 어쨌거나 좋은 출발점이 될 수 있습니다.

 

 

코드 명확성(Code clarity)

  신중하게 자료형을 정의하는 것이 좋은 설계인 것은, 클라이언트 코드들이 좀 더 계산의 의도를 명확하게 표현하는 데에도 도움이 되기 때문입니다. 3.1, 3.2절에서 수많은 클라이언트 코드의 예제들을 보셨을 것입니다. 우리는 <Histogram, 3.2.3>을 다루면서 이 이야기를 이미 했었죠. 

 

  <Histogram>의 클라이언트는 코드 명확성을 잘 지켜준 예입니다. 우리는 addDataPoint()를 호출함으로써 클라이언트에서 특정 점을 추가할 수 있게 했죠. 해당 코드를 보면, 누구든지 그 의도를 파악할 수 있을 것입니다. 자료형이 잘 설계되었기 때문이죠. 좋은 설계를 하는 핵심 중 하나는 적절한 추상화로 쓰인 코드가 자체 문서적(self-documenting)인지 보는 것입니다. 

 

 

프로그램 3.3.2: Counter

 
public class Counter {

    private final String name;     // counter name
    private final int maxCount;    // maximum value
    private int count;             // current value

    // create a new counter with the given parameters
    public Counter(String id, int max) {
        name = id;
        maxCount = max;
        count = 0;
    } 

    // increment the counter by 1
    public void increment() {
        if (count < maxCount) count++;
    } 

    // return the current count
    public int value() {
        return count;
    } 

    // return a string representation of this counter
    public String toString() {
        return name + ": " + count;
    } 

    // test client
    public static void main(String[] args) { 
        int n = Integer.parseInt(args[0]);
        int trials = Integer.parseInt(args[1]);

        // create n counters
        Counter[] hits = new Counter[n];
        for (int i = 0; i < n; i++) {
            hits[i] = new Counter(i + "", trials);
        }

        // increment trials counters at random
        for (int t = 0; t < trials; t++) {
            int index = StdRandom.uniform(n);
            hits[index].increment();
        }

        // print results
        for (int i = 0; i < n; i++) {
            StdOut.println(hits[i]);
        }
    } 
} 
% java Counter 6 600000
0: 100684
1: 99258
2: 100119
3: 100054
4: 99844
5: 100037

 

  캡슐화의 이점에 대해 배우면서 아마 머리가 아플 것입니다. 자료형 설계의 관점에서 다시 한 번 요약해봅시다. 캡슐화는 모듈화 프로그래밍을 가능케 하며, 다음을 할 수 있습니다:

  • 클라이언트와 구현 코드를 독립적으로 개발할 수 있습니다.
  • 클라이언트에게 영향을 끼치지 않고도 구현을 개선해 대체할 수 있습니다.
  • 앞으로 작성될 프로그램을 지원할 수 있습니다. (어떤 클라이언트든 API를 쓸 수 있습니다)

  또한 캡슐화는 자료형의 연산을 고립시키며,  다음의 가능성을 열어줍니다:

  • 구현 단에서 자료형의 일관성을 확인할 수 있게 하고 다른 디버깅 도구들을 추가할 수 있습니다.
  • 클라이언트 코드를 명확하게 합니다.

  적절히 구현된 자료형은 자바 언어를 확장하며, 어떤 클라이언트 프로그램이든 그것을 사용할 수 있게 만듭니다.

 

계속.

9개의 댓글

2021.08.12

모듈화랑 캡슐화가 어떻게 다른지 설명이 부족한거같음

0
2021.08.12
@ytrewq

대충 캡슐화한 클래스들이 독립적으로 개발/디버그할수있도록 만드는게 모듈화라는 뜻인가보네.. 이런거 봐도봐도 까먹더라.. 툭치면 해당 정의 술술 풀어내는 애들 보면 진짜 대단한거같어

0
2021.08.12
@ytrewq

아무래도 이전 글부터 연속되어서 나오는 내용들이라, 단편적으로만 보면 조금 이해하기 어려울 수도 있음.

 

모듈화라는 건 결국 기능을 독립적으로 구분해 재사용할 수 있게 만든다는 개념임. 어떤 프로그램을 만드는데 리스트라는 자료구조를 사용했고, 이것이 쓰다보니 유용해서 다른 프로그램에도 써야한다면, ‘리스트’ 자료형 모듈을 독립적으로 만들어서 수많은 프로그램에서 재사용할 수 있지. 유지보수의 이점은 덤.

 

이러한 모듈을 효과적으로 만들어주는 수단이 캡슐화야. 캡슐화는 값과 연산을 묶어주고, 필요한 부분만을 드러내지.(정보 은닉) 필요한 부분만을 드러내는 것이 왜 중요하냐면, 클라이언트 코드에게 끼치는 영향을 제어해 최소화할 수 있기 때문임.(클라이언트가 자료형을 사용하는 방법을 제한함으로써, 코드 개선 시 그 부분만 신경쓰면 됨)

0
2021.08.12

설명 괜찮네~

0
2021.08.13

r 이랑 theta는 private인데 Complex method라서 액세스 가능한건가? 안될껏같은데..?

 

Complex c = new Complex(0, 0);

c.r = a.r * b.r; // can't make r and theta final

c.theta = a.theta + b.theta; // because of these two statements

 

이 부분은 오타 난듯? 인스턴스 변수 re와 im -> r & theta

 

만약 의 인스턴스 변수 re와 im이 public이었다면, 클라이언트는 이들에게 직접 접근하는 코드를 작성할 수 있었을 것입니다. 만약 z가 객체를 참조하는 경우, z.re와 z.im을 통해서 접근할 수 있습니다.

 

잘읽엇어 담편도 기대할께

0
2021.08.13
@득근이어라

같은 클래스라면 서로 다른 인스턴스여도 접근할 수 있어. 물론 Complex를 사용하는 클라이언트에선 불가능.

 

re와 im은 오타는 아니고, 이전 절에서 다뤘던 또다른 버전의 Complex 클래스 이야기이긴 해. 충분히 혼동할만 하네.. 지적 고마워.

0
2021.08.14

정독하고 있는데 내용 정말 좋아용 ~~

0
2021.08.14

갠적으로 궁금한건데

삼항연산자나 람다식 현업에서 씀?

가독성이 안좋은거 같은뎅..

0
2021.08.14
@샤켓

람다를 자주 쓰지는 않는데

삼항은 짧고 간결하게 쓸수있을때는 자주써 짧으면 오히려 가독성 괜찮아서

0
무분별한 사용은 차단될 수 있습니다.
번호 제목 글쓴이 추천 수 날짜
534 [과학] 제논의 역설은 어떻게 풀렸는가? (5) - 진정한 뜻 4 존콘웨이 3 21 시간 전
533 [과학] 제논의 역설은 어떻게 풀렸을까? (4) - 보편적 논리란 없다 존콘웨이 1 22 시간 전
532 [과학] 제논의 역설은 어떻게 풀렸을까? (3) - 무한에의 늪 1 존콘웨이 2 1 일 전
531 [과학] 제논의 역설은 어떻게 풀렸을까? (2) - 측도론의 늪 존콘웨이 0 1 일 전
530 [과학] 제논의 역설은 어떻게 풀렸을까? (1) - 배신당한 상식 1 존콘웨이 3 1 일 전
529 [과학] [유튭]인터넷 케이블이 바다를 가로질러 이동하는 방법 6 수간호사 5 9 일 전
528 [과학] 그림으로 보는 해부학: 1. 다리의 뼈 13 Volksgemeinschaft 12 11 일 전
527 [과학] 어두운 것은 밤이요, 찬 것은 땅이니. 12 너구리구나 21 25 일 전
526 [과학] 위드 코로나와 코로나 백신 관련 응급의학과 전문의 글 (긴글) 43 바이코딘 58 2021.10.19
525 [과학] <강력의 탄생> - 추천합니다 7 미분가능하지않은... 6 2021.10.17
524 [과학] 오싹오싹 우주 근황의 조금 더 자세하고 정확한 설명.physics 21 샤킬오닐 17 2021.10.15
523 [과학] 코로나 백신과 혈전증에 대한 응급의학과 전문의 글 10 바이코딘 9 2021.10.13
522 [과학] 앗싸식 팩트 체크 : MBTI는 그냥 말장난, 조크, 혈액형 성격... 31 커뮤질은인생의낭비 6 2021.09.30
521 [과학] 한반도 형성 모델 8 白猫 4 2021.09.06
520 [과학] 인류 발전은 정체되었는가? 119 월급받으며개드립하기 24 2021.09.02
519 [과학] 자바로 프로그래밍에 입문할래요: 3.3. 자료형 설계하기 (4) 스비니 6 2021.08.21
518 [과학] 자바로 프로그래밍에 입문할래요: 3.3. 자료형 설계하기 (3) 3 스비니 3 2021.08.17
517 [과학] 자바로 프로그래밍에 입문할래요: 3.3. 자료형 설계하기 (2) 2 스비니 0 2021.08.15
516 [과학] 자바로 프로그래밍에 입문할래요: 3.3. 자료형 설계하기 (1) 9 스비니 1 2021.08.12
515 [과학] 자바로 프로그래밍에 입문할래요: 3.2. 자료형 생성하기 (3) 스비니 0 2021.08.11