네로개발일기

개발자 네로의 개발 일기, 자바를 좋아합니다 !

'2021/12'에 해당되는 글 16건


반응형

자바스크립트는 프로포타입 기반(prototype-based) 객체지향 언어이다. 비록 다른 객체 지향 언어들과의 차이점에 대한 논쟁이 있긴 하지만, 자바스크립트는 강력한 객체지향 프로그래밍 능력을 지니고 있다.

 

프로토타입 기반 프로그래밍은 클래스가 필요없는(class-free) 객체지향 프로그래밍 스타일로 프로토타입 체인과 클로저 등으로 객체 지향 언어의 상속, 캡슐화(정보 은닉) 등의 개념을 구현할 수 있다.

 

ES5에서는 생성자 함수와 프로토타입, 클로저를 사용하여 객체 지향 프로그래밍을 구현하였다.

// ES5
var person = (function() {
    // Constructor
    function Person(name) {
    	this._name = name;
    }
    
    // public method
    Person.prototype.sayHi = function() {
    	console.log('Hi! ' + this._name);
    };
    
    // return constructor
    return Person;
}());

var me = new Person('Jeon');
me.sayHi(); // Hi Jeon.

console.log(me instanceof Person); // true

 

클래스 기반 언어에 익숙한 프로그래머들은 프로토타입 기반 프로그래밍 방식이 혼란스러울 수 있다.

 

ES6의 클래스는 기존 프로토타입 기반 객체지향 프로그래밍보다 클래스 기반 언어에 익숙한 프로그래머보다 빠르게 학습할 수 있는 단순명료한 새로운 문법을 제시하고 있다. 사실 클래스도 함수이며 기존 프로토타입 기반 패턴의 문법적 설탕(Syntactic sugar)이라고 볼 수 있다. 다만, 클래스와 생성자 함수가 정확히 동일하게 동작하지는 않는다. 클래스가 보다 엄격하다. 따라서 클래스를 프로토타입 기반 패턴의 문법적 설탕이라고 인정하지 않는 견해도 일리가 있다.

 

1. 클래스 정의 (Class Definition)

ES6 클래스는 class 키워드를 사용하여 정의한다. 앞에서 살펴본 Person 생성자 함수를 클래스로 정의해보자.

클래스 이름은 생성자 함수와 마찬가지로 파스칼 케이스를 사용하는 것이 일반적이다. 파스칼 케이스를 사용하지 않아도 에러가 발생하지는 않는다.

// 클래스 선언문
class Person {
    // constructor(생성자)
    constructor(name) {
    	this._name = name;
    }
    
    sayHi() {
    	console.log(`Hi! ${this._name}`);
    }
}

// 인스턴스 생성
const me = new Person('Jeon');
me.sayHi(); // Hi! Jeon

console.log(me instanceof Person); // true

클래스는 클래스 선언문 이전에 참조할 수 없다.

console.log(Foo); 
// ReferenceError: Cannot access 'Foo' before initialization

class Foo {}

하지만 호이스팅이 발생하지 않은 것은 아니다. 클래스는 var 키워드로 선언한 변수처럼 호이스팅이 되지 않고 let, const로 선언한 변수처럼 호이스팅된다. 따라서 클래스 선언문 이전에 일시적 사각지대(Temporal Dead Zone; TDZ)에 빠지기 때문에 호이스팅이 발생하지 않는 것처럼 동작한다.

const Foo = '';

{
    // 호이스팅이 발생하지 않는다면 ''가 출력되어야 한다.
    console.log(Foo);
    // ReferenceError: Cannot access 'Foo' before initialization
    class Foo {}
}

클래스 선언문도 변수 선언, 함수 정의와 마찬가지로 호이스팅이 발생한다. 호이스팅은 var, let, const, function, function*, class 키워드를 사용한 모든 선언문에 적용된다. 다시 말해, 선언문을 통해 모든 식별자(변수, 함수, 클래스 등)는 호이스팅된다. 모든 선언문은 런타임 이전에 먼저 실행되기 때문이다. 

일반적이지는 않지만, 표현식으로도 클래스를 정의할 수 있다. 함수와 마찬가지로 클래스는 이름을 가질 수도 갖지 않을 수도 있다. 이때 클래스가 할당된 변수를 사용해 클래스를 생성하지 않고 기명 클래스의 클래스 이름을 사용해 클래스를 생성하면 에러가 발생한다. 이는 함수와 마찬가지로 클래스 표현식에서 사용한 클래스 이름은 외부 코드에서 접근 불가능하기 때문이다. 

// 클래스명 MyClass는 함수 표현식과 동일하게 클래스 몸체 내부에서만 유효한 식별자이다.
const Foo = class MyClass {};

const foo = neew Foo();
console.log(foo); // MyClass {}

new MyClass(); // ReferenceError: MyClass is not defined

2. 인스턴스 생성

마치 생성자 함수와 같이 new 연산자와 함께 클래스 이름을 호출하면 클래스의 인스턴스가 생성된다.

class Foo {}

const foo = new Foo();

위 코드에서 new 연산자와 함께 호출한 Foo는 클래스의 이름이 아니라 constructor(생성자)이다. 표현식이 아닌 선언식으로 정의한 클래스의 이름은 constructor와 동일하다.

console.log(Object.getPrototypeOf(foo).constructor === Foo); // true

new 연산자를 사용하지 않고 constructor를 호출하면 타입 에러(Type Error)가 발생한다.

constructor는 new 연산자 없이 호출할 수 없다.

class Foo {}

const foo = Foo(); // TypeError: Class constructor Foo cannot be invoked without 'new'

3. constructor

constructor는 인스턴스를 생서아고 클래스 필드를 초기화하기 위한 특수한 메소드이다.

클래스 필드(class field)

클래스 내부의 캡슐화된 변수를 말한다. 데이터 멤버 또는 멤버 변수라고도 부른다. 클래스 필드는 인스턴스의 프로퍼티 또는 정적 프로퍼티가 될 수 있다. 쉽게 말해 자바스크립트의 생성자 함수에서 this에 추가한 프로퍼티를 클래스 기반 객체지향언어에서는 클래스 필드라고 부른다.

// 클래스 선언문
class Person {
    // constructor(생성자)
    constructor(name) {
        // this는 클래스가 생성할 인스턴스를 가리킨다.
        // _name은 클래스 필드이다.
        this._name = name;
    }
}

// 인스턴스 생성
const me = new Person('Jeon');
console.log(me); // Person { _name: 'Jeon' }

constructor는 클래스 내에 한 개만 존재할 수 있으며 만약 클래스 2개 이상의 constructor를 포함하면 문법 에러(SyntaxError)가 발생한다. 인스턴스를 생성할 때 new 연산자와 함께 호출한 것이 바로 constructor이며 constructor의 파라미터에 전달한 값은 클래스 필드에 할당한다.

constructor는 생략할 수 있다. constructor를 생략하면 클래스에 constructor() {}를 포함한 것과 동일하게 동작한다. 즉, 빈 객체를 생성한다. 따라서 인스턴스에 프로퍼티를 추가하려면 인스턴스를 생성한 이후, 프로퍼티를 동적으로 추가해야 한다.

class Foo { }

const foo = new Foo();
console.log(foo); // Foo {}

// 프로퍼티 동적 할당 및 초기화
foo.num = 1;
console.log(foo); // Foo { num: 1 }

constructor는 인스턴스의 생성과 동시에 클래스 필드의 생성과 초기화를 실행한다. 따라서 클래스 필드를 초기화해야 한다면 constructor를 생략해서는 안된다.

class Foo {
    // constructor는 인스턴스의 생성과 동시에 클래스 필드의 생성과 초기화를 실행한다.
    constructor(num) {
    	this.num = num;
    }
}

const foo = new Foo(1);

console.log(foo); // Foo { num: 1 }

4. 클래스 필드

클래스 몸체(class body)에는 메서드만 선언할 수 있다. 클래스 바디에 클래스 필드(멤버 변수)를 선언하면 문법 에러(SyntaxError)가 발생한다.

class Foo {
    name = ''; // SyntaxError
    
    constructor() {}
}

클래스 필드의 선언과 초기화는 반드시 constructor 내부에서 실시한다.

class Foo {
    constructor(name = '') {
    	this.name = name; // 클래스 필드의 선언과 초기화
    }
}

const foo = new Foo('Jeon');
console.log(foo); // Foo { name: 'Jeon' }

constructor 내부에서 선언한 클래스 필드는 클래스가 생성할 인스턴스를 가리키는 this에 바인딩한다. 이로써 클래스 필드는 클래스가 생성할 인스턴스의 프로퍼티가 되며, 클래스의 인스턴스를 통해 클래스 외부에서 언제나 참조할 수 있다. 즉, 언제나 public 이다.

ES6의 클래스는 다른 객체지향 언어처럼 private, public, protected 키워드와 같은 접근 제한자(access modifier)를 지원하지 않는다.

class Foo {
    constructor(name = '') {
    	this.name = name; // public 클래스 필드
    }
}

const foo = new Foo('Jeon');
console.log(foo.name); // 클래스 외부에서 참조할 수 있다.
728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

디스트럭처링(Destructuring)은 구조화된 배열 또는 객체를 Destructuring(비구조화, 파괴)하여 개별적인 변수에 할당하는 것이다. 배열 또는 객체 리터럴에서 필요한 값만을 추출하여 변수에 할당하거나 반환할 때 유용하다.

 

1. 배열 디스트럭처링(Array destructuring)

ES5에서 배열의 각 요소를 배열로부터 디스트럭처링하여 변수에 할당하기 위한 방법은 아래와 같다.

// ES5
var arr = [1, 2, 3];

var one = arr[0];
var two = arr[1];
var three = arr[2];

console.log(one, two, three); // 1 2 3

ES6의 배열 디스트럭처링은 배열의 각 요소를 배열로부터 추출하여 변수 리스트에 할당한다. 이때 추출/할당 기준은 배열의 인덱스이다.

// ES6 Destructuring
const arr = [1, 2, 3];

// 배열의 인덱스를 기준으로 배열로부터 요소를 추출하여 변수에 저장
// 변수 one, two, three가 선언되고 arr(initializer(초기화자))가 Destructuring(비구조화, 파괴)되어 할당된다.
const [one, two, three] = arr;
// 디스트럭처링을 사용할 때는 반드시 initializer(초기화자)를 할당해야 한다. 
// const[one, two, three]; // SyntaxError: Missing initializer in destructuring declaration

console.log(one, two, three); // 1 2 3

배열 디스트럭처링을 위해서는 할당 연산자 왼쪽에 배열 형태의 변수 리스트가 필요하다.

let x, y, z;
[x, y, z] = [1, 2, 3];

// 위의 구문과 동치이다.
let [x, y, z] = [1, 2, 3];

왼쪽의 변수 리스트와 오른쪽의 배열은 배열의 인덱스 기준으로 할당된다.

let x, y, z;

[x, y] = [1, 2];
console.log(x, y); // 1 2

[x, y] = [1];
console.log(x, y); // 1 undefined

[x, y] = [1, 2, 3];
console.log(x, y); // 1 2

[x, , z] = [1, 2, 3];
console.log(x, z); // 1 3

[x, y, z = 3] = [1, 2];
console.log(x, y, z); // 1 2 3

[x, y = 10, z = 3] = [1, 2];
console.log(x, y, z); // 1 2 3

[x, ...y] = [1, 2, 3];
console.log(x, y); // 1 [ 2, 3 ]

ES6의 배열 디스트럭처링은 배열에서 필요한 요소만 추출하여 변수에 할당하고 싶은 경우에 유용하다. 아래의 코드는 Date 객체에서도 년도, 월, 일을 추출하는 예제이다.

const today = new Date();
const formattedDate = today.toISOString().substring(0, 10); // "2022-01-02"
const [year, month, day] = formattedDate.split('-');
console.log([year, month, day]); // [ '2022', '01', '02' ]

 

2. 객체 디스트럭처링 (Object destructuring)

ES5에서 객체의 각 프로퍼티를 객체로부터 디스트럭처링하여 변수에 할당하기 위해서는 프로퍼티 이름(키)을 사용해야 한다. 

// ES5
var obj = { firstName: 'Jiyoon', lastName: 'Jeon' };

var firstName = obj.firstName;
var lastName = obj.lastName;

console.log(firstName, lastName); // Jiyoon Jeon

ES6의 객체 디스트럭처링은 객체의 각 프로퍼티를 객체로부터 추출하여 변수 리스트에 할당한다. 이때 할당 기준은 프로퍼티 이름(키)이다.

// ES6 Destructuring
const obj = { firstName: 'Jiyoon', lastName: 'Jeon' };

// 프로퍼티 키를 기준으로 디스트럭처링 할당이 이루어진다. 순서는 의미가 없다. 
// 변수 lastName, firstName가 선언되고 obj(initializer(초기화자))가 Destructuring(비구조화, 파괴)되어 할당된다.
const { lastName, firstName } = obj;

console.log(firstName, lastName); // Jiyoon Jeon

객체 디스트럭처링을 위해서는 할당 연산자 왼쪽에 객체 형태의 변수 리스트가 필요하다.

// 프로퍼티 키가 prop1인 프로퍼티의 값을 변수 p1에 할당 
// 프로퍼티 키가 prop2인 프로퍼티의 값을 변수 p2에 할당
const { prop1: p1, prop2: p2 } = { prop1: 'a', prop2: 'b' }; 
console.log(p1, p2); // 'a' 'b'
console.log({ prop1: p1, prop2: p2 }); // { prop1: 'a', prop2: 'b' }

// 아래는 위의 축약형이다.
const { prop1, prop2 } = { prop1: 'a', prop2: 'b' };
console.log({ prop1, prop2 }); // { prop1: 'a', prop2: 'b' }

// default value
const { prop1, prop2, prop3 = 'c' } = { prop1: 'a', prop2: 'b' };
console.log({ prop1, prop2, prop3 }); // { prop1: 'a', prop2: 'b', prop3: 'c' }

객체 디스트럭처링은 객체에서 프로퍼티 이름(키)으로 필요한 프로퍼티 값만 추출할 수 있다. 

const todos = [
    { id: 1, content: 'HTML', completed: true },
    { id: 2, content: 'CSS', completed: false },
    { id: 3, content: 'JS', completed: false }
];

// todos 배열의 요소인 객체로부터 completed 프로퍼티만을 추출한다.
const completedTodos = todos.filter(({ completed }) => completed);
console.log(completedTodos); // [ { id: 1, content: 'HTML', completed: true } ]

Array.proptotype.filter 메서드의 콜백 함수는 대상 배열(todos)을 순회하며 첫 번째 인자로 대상 배열의 요소를 받는다. 이때 필요한 프로퍼티(completed 프로퍼티)만을 추출할 수 있다.

 

중첩 객체의 경우는 아래와 같이 사용한다.

const person = {
    name: 'Jeon',
    address: {
    	zipCode: '05407',
        city: 'Seoul'
    }
};

const { address: {city} } = person;
console.log(city); // Seoul
728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

ES6에서는 객체 리터럴 프로퍼티 기능을 확장하여 더욱 간편하고 동적인 객체 생성 기능을 제공한다.

 

1. 프로퍼티 축약 표현

ES6에서 객체 리터럴의 프로퍼티는 프로퍼티 이름과 프로퍼티 값으로 구성된다. 프로퍼티의 값은 변수에 할당된 값일 수도 있다.

// ES5
var x = 1, y = 2;

var obj = {
    x: x,
    y: y
};

console.log(obj); // { x: 1, y: 2 }

ES6에서는 프로퍼티 값으로 변수를 사용하는 경우, 프로퍼티 이름을 생략(Property shorthand)할 수 있다. 이때 프로퍼티 이름은 변수의 이름으로 자동 생성된다.

// ES6
let x = 1, y = 2;

const obj = { x, y };

console.log(obj); // { x: 1, y: 2 }

 

2. 프로퍼티 키 동적 생성

문자열 또는 문자열로 변환 가능한 값을 반환하는 표현식을 사용해 프로퍼티 키를 동적으로 생성할 수 있다. 단, 프로퍼티 키로 사용할 표현식을 대괄호 [...]로 묶어야 한다. 이를 계산된 프로퍼티 이름(Computed property name)이라 한다.

// ES5
var prefix = 'prop';
var i = 0;

var obj = {};

obj[prefix + '-' + ++i] = i;
obj[prefix + '-' + ++i] = i;
obj[prefix + '-' + ++i] = i;

console.log(obj); // [prop-1: 1, prop-2: 2, prop-3: 3]

ES6에서는 객체 리터럴 내부에서도 프로퍼티 키를 동적으로 생성할 수 있다.

// ES6
const prefix = 'prop';
let i = 0;

const obj = {
	[`${prefix}-${++i}`]: i,
	[`${prefix}-${++i}`]: i,
	[`${prefix}-${++i}`]: i
};

console.log(obj); // {prop-1: 1, prop-2: 2, prop-3: 3}

3. 메서드 축약 표현

ES5에서 메서드를 선언하려면 프로퍼티 값으로 함수 선언식을 할당한다. 

// ES5
var obj = {
    name: 'Lee',
    sayHi: function() {
    	console.log('Hi!' + this.name);
    }
};

obj.sayHi(); // Hi! Lee

ES6에서는 메서드를 선언할 때, function 키워드를 생략한 축약 표현을 사용할 수 있다. 

// ES6
const obj = {
    name: 'Lee',
    // 메소드 축약 표현
    sayHi() {
    	console.log('Hi!' + this.name);
    }
};
obj.sayHi(); // Hi! Lee

4. __proto__ 프로퍼티에 의한 상속

ES5에서 객체 리터럴을 상속하기 위해서는 Object.create() 함수를 사용한다. 이를 프로토타입 패턴 상속이라 한다.

// ES5
var parent = {
    name: 'parent',
    sayHi: function() {
    	console.log('Hi!' + this.name);
    }
};

var child = Object.create(parent);
child.name = 'child';

parent.sayHi(); // Hi! parent
child.sayHi(); // Hi! child

ES6에서는 객체 리터럴 내부에서 __proto__ 프로퍼티를 직접 설정할 수 있다. 이것은 객체 리터럴에 의해 생성된 객체의 __proto__ 프로퍼티에 다른 객체를 바인딩해서 상속을 표현할 수 있음을 의미한다.

// ES6
const parent = {
    name: 'parent', 
    sayHi() {
    	console.log('Hi!' + this.name);
    }
};

const child = {
	__proto__: parent,
    name: 'child'
};

parent.sayHi(); // Hi! parent
child.sayHi(); // Hi! child
728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

# 태그 달린 클래스보다는 클래스 계층 구조를 활용하라.

 

## 태그 달린 클래스
- 두 가지 이상의 기능을 가지고 있으며, 그 중에서 어떠한 능력을 갖고 있는지 나타내는 태그(tag) 필드가 있는 클래스를 태그 달린 클래스라고 말한다. 이 클래스는 아래와 같은 형태를 가지고 있다.
class Figure {
  enum Shape {
    RECTANGLE, CIRCLE
  };

  final Shape shape; // 태그 필드 

  // 사각형(RECTANGLE)일 때
  double length;
  double width;

  // 원(CIRCLE)일 때
  double radius;

  // 사각형용 생성자
  Figure(double length, double width) {
    shape = Shape.RECTANGLE;
    this.length = length;
    this.width = width;
  }

  // 원용 생성자
  Figure(double radius) {
    shape = Shape.CIRCLE;
    this.radius = radius;
  }

  double area() {
    switch(shape) {
      case RECTANGLE:
        return length * width;
      case CIRCLE:
        return Math.PI * (radius * radius);
      default:
        throw new AssertionError(shape);
    }
  }
}
 
### 태그 달린 클래스의 단점
1. 열거(enum) 타입 선언, 태그 필드, switch 문장 등 쓸데 없는 코드가 많다.
2. 여러 구현이 하나의 클래스에 혼합되어 있어서 가독성이 좋지 않다.
3. 다른 의미를 위한 코드가 함께 있으니 상대적으로 메모리도 더 차지한다.
4. 필드를 final로 선어하려면 해당 의미에 사용되지 않는 필드까지 생성자에서 초기화해야 한다.
5. 또 다른 의미를 추가하려면 코드를 수정해야 한다. 특히 switch 문장에도
6. 인스턴스 타입만으로는 현재 나타내는 의미를 파악하기 어렵다.

 

## 개선 => 클래스 계층구조
태그 달린 클래스 형태를 클래스 계층 구조로 바꿔보자
### 클래스 계층 구조로 만드는 방법
1. 계층 구조의 루트(root)가 될 추상 클래스를 정의한다.
2. 태그 값에 따라 동작이 달라지는 메서드들을 루트 클래스에 추상 메서드로 선언한다.
3. 태그 값에 상관없이 동작이 일정한 메서드들을 루트 클래스에 일반 메서드로 추가한다.
4. 모든 하위 클래스에서 공통으로 사용하는 데이터 필드들도 전부 루트 클래스로 올린다.
5. 루트 클래스를 확장한 구체 클래스를 의미별로 하나씩 정의한다.
6. 각 하위 클래스에는 각자의 의미에 해당하는 데이터 필드를 넣는다.
7. 루트 클래스가 정의한 추상 메서드를 각자의 의미에 맞게 구현한다.
abstract class Figure {
  abstract double area();
}

class Circle extends Figure {
  final dobule radius;
  Circle(double radius) {
    this.radius = radius;
  }
  @Override
  double area() {
    return Math.PI * (radius * radius);
  }
}

class Rectangle extends Figure {
  final double length;
  final double width;
  Rectangle(double length, double width) {
    this.length = length;
    this.width = width;
  }
  @Override
  double area() {
    return length * width;
  }
}

 

- 간결하고 명확해졌으며, 쓸데없는 코드들이 모두 사라졌다. 각 의미를 독립된 클래스에 담았기 때문에 관련없는 데이터 필드는 모두 제거되었다.
- 타입 사이의 자연스러운 계층 관계를 반영할 수 있어서 유연성은 물론 컴파일 타임에서의 타입 검사 능력도 높여준다. 또한 클래스 계층 구조라면, 아래와 같이 정사각형(Square)가 추가될 때도 간단하게 반영할 수 있다.
class Square extends Rectangle {
  Square(double side) {
    super(side, side);
  }
}​



## 정리
- 태그 달린 클래스를 써야 하는 상황은 거의 없다.
- 새로운 클래스를 작성하는 데 태그 필드가 등장한다면 태그를 없애고 계층 구조로 대체하는 방법을 생각해보자.
- 기존 클래스가 태그 필드를 사용하고 있다면 계층 구조로 리팩터링 하는 것을 고민하자.
728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

# 인터페이스는 타입을 정의하는 용도로만 사용하라.


## 인터페이스
- 인터페이스는 자신을 구현한 클래스의 인스턴스를 참조할 수 있는 타입 역할을 한다.
- 클래스가 어떤 인터페이스를 구현한다는 것은 자신의 인스턴스로 무엇을 할 수 있는지를 클라이언트가 이야기해주는 것.

### 상수 인터페이스 안티패턴 - 사용 금지
- 상수 인터페이스란 메서드없이 상수를 뜻하는 static final 필드로만 가득 찬 인터페이스를 말한다.
- 이 상수들을 사용하려는 클래스에서는 정규화된 이름을 쓰는 것을 피하고자 인터페이스를 구현한다.
public interface PhysicalConstants {
  // 아보가드로 수 (1/몰)
  static final double AVOGADROS_NUMBER   = 6.022_140_857e23;

  // 볼츠만 상수 (J/K)
  static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23;

  // 전자 질량 (kg)
  static final double ELECTRON_MASS      = 9.109_383_56e-31;
}
 
- 상수 인터페이스 안티 패턴은 인터페이스를 잘못 사용한 예다.
- 클래스 내부에서 사용하는 상수는 인터페이스가 아니라 내부 구현에 해당한다. 따라서 상수 인터페이스를 구현하는 것은 이 내부 구현을 클래스의 API로 노출하는 행위이다.
- 클래스가 어떤 상수 인터페이스를 사용하든 사용자에게는 아무런 의미가 없다. 반면, 이는 사용자에게 혼란을 주며 클라이언트 코드가 이 상수들에 종속되게 한다.
- final이 아닌 클래스가 상수 인터페이스를 구현한다면 모든 하위 클래스의 이름 공간이 그 인터페이스가 정의한 상수들로 오염되어 버린다.

### 상수 유틸리티 클래스
- 특정 클래스나 인터페이스와 강하게 연관된 상수라면 그 클래스나 인터페이스 자체에 추가해야 한다.
- 열거 타입으로 나타내기 적합한 상수라면 열거 타입으로 만들어 공개하면 된다.
- 또는, 인스턴스화할 수 없는 유틸리티 클래스에 담아 공개하는 방법도 있다.
public class PhysicalConstants {
  private PhysicalConstants() { // 인스턴스화 방지
  }

  // 아보가드로 수 (1/몰)
  public static final double AVOGADROS_NUMBER   = 6.022_140_857e23;

  // 볼츠만 상수 (J/K)
  public static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23;

   // 전자 질량 (kg)
  publicstatic final double ELECTRON_MASS      = 9.109_383_56e-31;
}
- 숫자 리터럴에 밑줄을 사용하였다. 자바 7부터 허용된다.
- 이 밑줄은 숫자 리터럴 값에는 영향을 주지 않으며, 가독성은 향상시킨다.
- 고정소수점 수 또는 부동 소수점 수가 5자리 이상이라면 밑줄을 사용하는 것을 고려해보자.
- 십진수 리터럴도 밑줄을 사용해서 세 자리씩 묶어주는 것이 좋다.

### 정적 임포트를 사용해 상수 이름만으로 사용하기
import static PhysicalConstants.AVOGADROS_NUMBER;

public class Test {
    double atoms(double mols){
        return AVOGADROS_NUMBER * mols;
    }
    // PhysicalConstants 를 자주사용하면 정적임포트가 좋다.
}
- 유틸리티 클래스에 정의된 상수를 클라이언트에서 사용하려면 클래스 이름까지 함께 명시해야 한다.
- 유틸리티 클래스의 상수를 빈번히 사용한다면 정적 임포트(static import)하여 클래스 이름은 생략할 수 있다.

## 정리
- 인터페이스는 타입을 정의하는 용도로만 사용해야 한다. 상수 공개용 수단으로 사용하지 말 것.
728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !