네로개발일기

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

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


반응형

의존성 주입 애노테이션 - @Autowired @Resource @Inject

의존 객체 자동 주입은 스프링 설정 파일에서 <constructor-arg> 또는 <property> 태그로 의존 객체 대상을 명시하지 않아도 스프링 컨테이너가 자동적으로 의존 대상 객체를 찾아 해당 객체에 필요한 의존성을 주입하는 것을 말한다.

이를 구현하는 방법은 @Autowired, @Resource, @Inject 애노테이션 등을 사용하여 구현한다. 

1. @Autowired

@Autowired는 주입하려고 하는 객체의 타입이 일치하는 객체를 자동으로 주입한다.

@Autowired는 필드, 생성자, Setter에 붙일 수 있다.

Type => Qualifier => Name

 

1) @Autowired 필드주입

public class UserService {

    @Autowired
    private UserRepository userRepository;
    
    public UserService() {
    }
}

2) @Autowired 생성자 주입

public class UserService {

    private UserRepository userRepository;
    
    @Autowired
    public UserService (UserRepository userRepository) {
    	this.userRepository = userRepository;
    }
}

3) @Autowired Setter 주입

public class UserService {

    private UserRepository userRepository;
    
    public UserRepository() {
    }
    
    @Autowired
    public void setUserRepository(UserRepository userRepository) {
    	this.userRepository = userRepository;
    }
}

4) @Qualifier 애노테이션

XML 설정 파일

<bean id="userRepository1" class="com.user.repository.UserRepository">
	<qualifier value="userRepository"/>
</bean>
<bean id="userRepository2" class="com.user.repository.UserRepository"/>
<bean id="userRepository3" class="com.user.repository.UserRepository"/>

위와 같이 동일한 타입의 빈 객체가 여러개 정의되어 있을 경우 우선적으로 사용할 빈 객체의 <bean> 태그 하위에 <qualifier> 태그를 설정한다.

 

자바코드

public class UserService {

    @Autowired
    @Qualifier("userRepository")
    private UserRepository userRepository;
    
    public UserService() {}
}

자바 코드에서는 @Autowired와 함께 @Qualifier를 사용해서 @Qualifier에 XML 설정파일에서 설정한 <qualifier> 태그의 value 값을 지정해준다.

이렇게 하면 동일한 타입의 빈이 여러 개일 경우 우선적으로 특정 빈이 주입된다.

 

2. @Resource

@Resource는 주입하려고 하는 객체의 이름(id)이 일치하는 객체를 자동으로 주입한다. 

@Resource는 Java가 제공하는 애노테이션이며 필드, Setter에 붙일 수 있지만 생성자에는 붙일 수 없다.

Name => Type => Qualifier

 

1) 의존성 설정

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

프로젝트에서 사용하기 위해 javax.annotation-api 의존성을 추가한다.

 

2) @Resource 필드 주입

import javax.annotation.Resource;

public class UserService {

    @Resource
    private UserRepository userRepository;
    
    public UserService() {}
}

3) @Resource Setter 주입

import javax.annotation.Resource;

public class UserService {

    private UserRepository userRepository;
    
    public UserService() {}
    
    @Resource
    public setUserRepository(UserRepository userRepository) {
    	this.userRepository = userRepository;
    }
}
728x90

3. @Inject

@Inject는 @Autowired와 유사하게 주입하려고 하는 객체의 타입이 일치하는 객체를 자동으로 주입한다. 

Type => Qualifier => Name

 

1) 의존성 설정

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

프로젝트에서 사용하기 위해 javax.inject 의존성을 추가한다.

 

2) @Inject 필드 주입

import javax.inject.Inject;

public class UserService {

    @Inject
    private UserRepository userRepository;
    
    public UserService() {}
}

3) @Inject 생성자 주입

import javax.inject.Inject;

public class UserService {

    private UserRepository userRepository;
    
    @Inject
    public UserService(UserRepository userRepository) {
    	this.userRepository = userRepository;
    }
}

4) @Inject Setter 주입

import javax.inject.Inject;

public class UserService {

    private UserRepository userRepository;
    
    public UserService() {}
    
    @Inject
    public void setUserRepository(UserRepository userRepository) {
    	this.userRepository = userRepository;
    }
}

5) @Named 애노테이션

@Autowired의 @Qualifier과 같이 사용할 수 있는 것이 @Inject에서는 @Named이다.

@Qualifier과 달리 @Named에는 빈 이름(id)를 지정하므로 @Autowired, @Qualifier를 사용할 때에 비해 XML 설정 파일이 다소 짧아진다는 특징이 있다.

 

XML 설정 파일

<bean id="userRepository1" class="com.user.repository.UserRepository">
<bean id="userRepository2" class="com.user.repository.UserRepository">
<bean id="userRepository3" class="com.user.repository.UserRepository">

<qualifier> 태그가 필요한 @Qualifier과 달리 @Named는 XML 설정 파일에 추가적으로 설정할 것이 없다.

 

자바코드

import javax.inject.Inject;
import javax.inject.Named;

public class UserService {

	@Inject
	@Named("userRepository1")
	private UserRepository userRepository;
	
	public UserService() {
		
	}
}

 

참고

https://www.baeldung.com/spring-annotations-resource-inject-autowire

728x90
반응형
blog image

Written by ner.o

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

반응형

# 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라.

 

## 1. 상속을 고려한 설계와 문서화

- 메서드를 재정의하려면 어떤 일이 일어나는지 정확히 정리하여 문서로 남겨야 한다. 즉, 상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지 (자기사용) 문서로 남겨야 한다.

- 클래스의 API로 공개된 메서드에서 클래스 자신의 또 다른 메서드를 호출할 수도 있다. 그런데 재정의가 가능한 메서드라면 그 사실을 API 명세에 적시해야 한다.

 

## 2. 좋은 API 문서란 어떻게가 아닌 무엇을 하는지를 설명해야 한다.

- 상속이 캡슐화를 해치기 때문에 안전하게 상속할 수 있도록 하려면 내부 구현 방식을 설명해야 한다. 그래서 특정 클래스의 메서드를 재정의하면 내부의 다른 메서드의 동작에 주는 영향도 정확하게 설명하고 있는 API들이 있다.

 

### @implSpec

- 자기사용 패턴(self-use pattern)에 대해서도 문서에 남겨 다른 프로그래머에게 그 메서드를 올바르게 재정의하는 방법을 알려야 한다.

- 일반적인 문서화 주석은 해당 메서드와 클라이언트 사이의 관계를 설명한다.

- @implSpec 주석은 해당 메서드와 하위 클래스 사이의 관계를 설명하며, 하위 클래스들이 그 메서드를 상속하거나 super 키워드를 이용해 호출할 때 그 메서드가 어떻게 동작하는지를 명확히 인지하고 사용하게 해야 한다.

- tag "implSpec:a:Implementation Requirement" 스위치를 키지 않으면 @implSpec 태그를 무시한다.

 

- 효율적인 하위 클래스를 큰 어려움없이 만들 수 있게 하려면 클래스의 내부 동작 과정 중간에 끼어들 수 있는 훅(hook)을 잘 선별하여 protected 메서드 형태로 공개해야 할 수도 있다. 또는 드물게는 protected 필드로 공개해야 할 수도 있다.

 

## 3. 상속용 클래스를 설계할 때 어떤 메서드를 protected로 노출

- protected 메서드 하나하나가 내부 구현에 해당하므로 그 수는 가능한 적어야 한다. 다만, 너무 적게 노출해서 상속으로 얻는 이점을 없애지 않도록 주의해야 한다.

- 상속용 클래스를 시험하는 방법은 직접 하위 클래스를 만들어보는 것이 유일하다.

- 꼭 필요한 protected 멤버를 놓쳤다면 하위 클래스를 작성할 때 그 빈자리가 확연히 드러난다.

- 거꾸로 하위 클래스를 여러개 만들 때까지 전혀 쓰이지 않는 protected 멤버는 사실 private 였어야 할 가능성이 크다.

 

## 4. 상속용 클래스는 영원히 책임져야 한다.

- 널리 쓰일 클래스를 상속용으로 설계한다면 문서화한 내부 사용 패턴과 protected 메서드와 필드를 구현하면서 선택한 결정을 영원히 책임져야 한다.

- 이 결정들이 그 클래스의 성능과 기능에 영원한 족쇄가 될 수 있다.

- 상속용으로 설계한 클래스는 배포 전에 반드시 하위 클래스를 만들어 검증해야 한다.

 

## 5. 상속용 클래스의 생성자는 재정의 메서드를 호출해서는 안된다.

- 상속용 클래스의 생성자는 직접적으로든 간접적으로든 재정의 기능 메서드를 호출해서는 안된다.

- 상위 클래스의 생성자는 하위클래스의 생성자 보다 먼저 실행되고, 하위클래스에서 재정의한 메서드가 하위 클래스의 생성자에서 초기화하는 값에 의존한다면, 의도대로 동작하지 않을 것이다.

- private, final, static 메서드는 재정의가 불가능하니 생성자에서 안심하고 호출해도 된다.

 

### clone과 readObject 메서드

- clone과 readObject 메서드는 생성자와 비슷한 효과를 낸다. (새로운 객체를 만든다.)

- 따라서, 상속용 클래스에서 Cloneable 이나 Serializable을 구현할지 정해야 한다면, 이들을 구현할 때 따라는 제약도 생성자와 비슷하다.

- 즉, clone과 readObject 모두 직접적으로든 간접적으로든 재정의 가능 메서드를 호출해서는 안 된다.

- readObject의 경우 하위 클래스의 상태가 미처 다 역직렬화되기 전에 재정의한 메서드부터 호출하게 된다.

- clone의 경우 하위 클래스의 clone 메서드가 복제본의 상태를 수정하기 전에 재정의한 메서드를 호출한다.

- Serializable을 구현한 상속용 클래스가 readResolve나 withReplace 메서드를 갖는다면 이 메서드들은 private가 아닌 protected로 선언해야 한다.

 

## 6. 클래스를 상속용으로 설계할 때는 주의해야 한다.

- 클래스를 상속용으로 설계하려면 엄청난 노력이 들고 그 클래스에 안기는 제약도 상당하다.

- 추상 클래스나 인터페이스의 골격 구현처럼 상속을 허용하는 게 명백히 정당한 상황이 있고, 불변 클래스처럼 명백히 잘못된 상황이 있다.

- 일반적인 구체 클래스는 final도 아니고 상속용으로 설계되지도 않았고 문서화되지도 않았다. 따라서 그대로 두면 위험하다. 클래스에 변화가 생길 때마다 하위 클래스를 오동작하게 만들 수 있다.

* 이 문제를 해결하는 가장 좋은 방법은 상속용으로 설계하지 않은 클래스는 상속을 금지하는 것이다.

 

### 7. 상속을 금지하는 두 가지 방법

1. 클래스를 final로 선언

2. 모든 생성자를 private나 package-private로 선언하고 public 정적 팩터리를 만들어 주는 것

 

- 핵심 기능을 정의한 인터페이스가 있고, 클래스가 그 인터페이스를 구현했다면 상속을 금지해도 개발하는 데 아무런 어려움이 없을 것이다. Set, List, Map이 그 예다. 또는 래퍼 클래스를 이용하는 것도 적절한 대안이 될 수 있다.

 

### 상속을 반드시 허용해야 한다면?

- 클래스 내부에서는 재정의 가능 메서드를 사용하지 않게 만들고 문서로 남겨야 한다.

- 즉, 재정의 가능 메서드를 호출하는 자기사용 코드를 완벽히 제거해야 한다.

- 이렇게 하면 상속해도 그리 위험하지 않다. 메서드를 재정의해도 다른 메서드의 동작에 아무런 영향을 주지 않기 때문이다.

 

## 정리

- 상속용 클래스를 설계한다면, 클래스 내부에서 스스로를 어떻게 사용하는지(자기사용 패턴) 모두 문서로 남겨야 한다.

- 일단 문서화한 것은 그 클래스가 쓰이는 한 반드시 지켜야 한다. 그러지 않으면 그 내부 구현 방식을 믿고 활용하던 하위 클래스를 오동작하게 만들 수 있다.

- 다른 이가 효율 좋은 하위 클래스를 만들 수 있도록 일부 메서드를 protected로 제공해야 할 수도 있다.

- 클래스를 확장해야 할 명확한 이유가 떠오르지 않으면 상속을 금지하는 편이 낫다.

- 상속을 금지하려면 클래스를 final로 선언하거나 생성자 모두를 외부에서 접근할 수 없도록 만들면 된다.

728x90
반응형
blog image

Written by ner.o

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

반응형

# 상속보다는 컴포지션을 사용하라.

- 상속이 안전할 때
* 상위 클래스와 하위 클래스를 모두 같은 프로그래머가 통제하는 패키지 안에서 사용한다.
* 확장할 목적으로 설계되었고 문서화도 잘 되어있다.
- 상속이 안전하지 않을 때
* 일반적인 구체 클래스를 패키지 경계를 넘어 다른 패키지의 구체 클래스를 상속하는 일은 위험하다. 이때 '상속'은 다른 패키지의 구체 클래스를 확장하는 '구현 상속'을 의미하며, 인터페이스의 상속과는 무관하다.

# 하위 클래스가 깨지기 쉬운 이유

## 1. 메서드 호출과는 달리 상속은 캡슐화를 깨뜨린다.

* 상위 클래스가 어떻게 구현되느냐에 따라 하위 클래스의 동작에 이상이 생길 수 있다.
* 상위 클래스는 릴리즈마다 내부 구현이 달라질 수 있으며, 그 여파로 코드 한 줄 건드리지 않은 하위 클래스가 오동작할 수 있다.

## 2. 상속의 문제를 해결하는 방법: 컴포지션

- 기존 클래스를 확장하는 대신, 새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조하게 하면 된다.
- 기존 클래스가 새로운 클래스의 구성요소로 쓰인다는 뜻에서 이러한 설계를 컴포지션(composition; 구성)이라 한다.
- 새 클래스의 인스턴스 메서드들은 (private 필드를 참조하는) 기존 클래스의 대응하는 메서드를 호출해 그 결과를 반환한다.
- 이 방식을 전달(forwarding)이라 하며, 새 클래스의 메서드들을 전달 메서드(forwarding method)라 부른다.
- 그 결과 새로운 클래스는 기존 클래스의 내부 구현 방식의 영향에서 벗어나며, 심지어 기존 클래스에 새로운 메서드가 추가되더라도 전혀 영향받지 않는다.

- 상속 방식은 구체 클래스를 각각 따로 확장해야 하며, 지원하고 싶은 상위 클래스의 생성자 각각에 대응하는 생성자를 별도로 정의해줘야 한다.
- 컴포지션 방식은 한 번만 구현해두면 어떠한 Set 구현체라도 계측할 수 있으며, 기존 생성자들과도 함께 사용할 수 있다.

- 래퍼 클래스와 Self 문제
- 콜백 프레임워크에서는 자기 자신의 참조를 다른 객체에 넘겨서 다음 호출(콜백)때 사용하도록 한다.
- 내부 객체는 자신을 감싸고 있는 래퍼의 존재를 모르니 대신 자신(this)의 참조를 넘기고, 콜백 때는 래퍼가 아닌 내부 객체를 호출하게 되는데, 이를 SELF 문제라고 한다.

## 3. 상속과 is-a

- 상속은 반드시 하위 클래스가 상위 클래스의 '진짜' 하위 타입인 상황에서만 쓰여야 한다.
- 즉, 클래스 B가 클래스 A와 is-a 관계일 때만 클래스 A를 상속해야 한다.
- 만약, is-a 관계가 아니라면 A는 B의 필수 구성 요소가 아니라 구현하는 방법 중 하나일 뿐이다.

## 4. 상속을 사용할 때 주의할 점

- 컴포지션을 써야할 상황에서 상속을 사용하는 건 내부 구현을 불필요하게 노출하는 것과 같다.
- 클라이언트에서 노출된 내부에 직접 접근할 수 있게 된다.
- 최악의 경우, 클라이언트에서 상위 클래스를 직접 수정하여 하위 클래스의 불변식을 해칠 수도 있다.
- 상속은 상위 클래스의 API를 결함까지도 승계하므로 주의해야 한다.

## 정리

- 상속은 강력하지만 캡슐화를 해친다는 문제가 있다.
- 상속은 상위 클래스와 하위 클래스가 순수한 is-a 관계일 때만 써야 한다.
- is-a 관계일 때도 하위 클래스의 패키지가 상위 클래스와 다르고, 상위 클래스가 확장을 고려해 설계되지 않았을 수도 있기 때문에 안심할 수 없다.
- 상속의 취약점을 피하려면 상속 대신 컴포지션과 전달을 사용한다. 특히 래퍼 클래스로 구현할 적당한 인터페이스가 있다면 더욱 그렇다. 래퍼 클래스는 하위 클래스보다 견고하고 강력하다.
728x90
반응형
blog image

Written by ner.o

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

반응형

1. 매개변수 기본값 (Default Parameter value)

함수를 호출할 때 매개변수의 개수만큼 인수를 전달하는 것이 일반적이지만 그렇지 않은 경우에도 에러가 발생하지 않는다. 함수는 매개변수의 개수와 인수의 개수를 체크하지 않는다. 인수가 부족한 경우, 매개변수의 값은 undefined이다.

function sum(x, y) {
	return x + y;
}

console.log(sum(1)); // NaN

따라서 매개변수에 적절한 인수가 전달되었는지 함수 내부에서 확인할 필요가 있다.

function sum(x, y) {
    // 매개변수의 값이 falsy value인 경우, 기본값을 할당한다.
    x = x || 0;
    y = y || 0;
    
    return x + y;
}

ES6에서는 매개변수 기본값을 사용하여 함수 내에서 수행하던 인수 체크 및 초기화를 간소화할 수 있다. 매개변수 기본값은 매개변수에 인수를 전달하지 않았을 경우에만 유효하다.

function sum(x = 0, y = 0) {
	return x + y;
}

console.log(sum(1)); // 1
console.log(sum(1, 2)); // 3

매개변수 기본값은 함수 정의시 선언한 매개변수 개수를 나타내는 함수 객체의 length 프로퍼티와 arguments 객체에 영향을 주지 않는다.

function foo(x, y = 0) {
	console.log(arguments);
}

console.log(foo.length); // 1

sum(1); // Arguments { '0': 1 }
sum(1, 2); // Arguments { '0': 1, '1': 2 }

 

2. Rest 파라미터

1) 기본 문법

Rest 파라미터(Rest Parameter, 나머지 매개변수)는 매개변수 이름 앞에 세개의 점 ...을 붙여서 정의한 매개변수를 의미한다. Rest 파라미터는 함수에 전달된 인수들의 목록을 배열로 전달받는다.

function foo(...rest) {
    console.log(Array.isArray(rest)); // true
    console.log(rest); // [1, 2, 3, 4, 5]
}

foo(1, 2, 3, 4, 5);

함수에 전달된 인수들은 순차적으로 파라미터와 Rest 파라미터에 할당된다.

function foo(param, ...rest) {
  console.log(param); // 1
  console.log(rest);  // [ 2, 3, 4, 5 ]
}

foo(1, 2, 3, 4, 5);

function bar(param1, param2, ...rest) {
  console.log(param1); // 1
  console.log(param2); // 2
  console.log(rest);   // [ 3, 4, 5 ]
}

bar(1, 2, 3, 4, 5);

Rest 파라미터는 이름 그대로 먼저 선언된 파라미터에 할당된 인수를 제외한 나머지 인수들이 모두 배열에 담겨 할당된다. 따라서 Rest 파라미터는 반드시 마지막 파라미터이어야 한다.

function foo( ...rest, param1, param2) { }

foo(1, 2, 3, 4, 5);
// SyntaxError: Rest parameter must be last formal parameter

Rest 파라미터는 함수 정의시 선언한 매개변수 개수를 나타내는 함수 객체의 length 프로퍼티에 영향을 주지 않는다.

function foo(...rest) {}
console.log(foo.length); // 0

function bar(x, ...rest) {}
console.log(bar.length); // 1

function baz(x, y, ...rest) {}
console.log(baz.length); // 2

2) arguments와 rest 파라미터

ES5에서는 인자의 개수를 사전에 알 수 없는 가변 인자 함수의 경우, arguments 객체를 통해 인수를 확인한다. arguments 객체는 함수 호출 시 전달된 인수(argument)들의 정보를 담고 있는 순회가능한 (iterable) 유사 배열 객체(array-like object)이며 함수 내부에서 지역 변수처럼 사용할 수 있다.

// ES5
var foo = function () {
  console.log(arguments);
};

foo(1, 2); // { '0': 1, '1': 2 }

가변 인자 함수는 파라미터를 통해 인수를 전달받는 것이 불가능하므로 arguments 객체를 활용하여 인수를 전달받는다. 하지만 arguments 객체는 유사 배열 객체이므로 배열 메소드를 사용하려면 Function.prototype.call을 사용해야 하는 번거로움이 있다.

// ES5
function sum() {
  // 가변 인자 함수는 arguments 객체를 통해 인수를 전달받는다.
  // 유사 배열 객체인 arguments 객체를 배열로 변환한다.
  var array = Array.prototype.slice.call(arguments);
  return array.reduce(function (pre, cur) {
    return pre + cur;
  });
}

console.log(sum(1, 2, 3, 4, 5)); // 15

ES6에서는 rest 파라미터를 사용하여 가변 인자의 목록을 배열로 전달받을 수 있다. 이를 통해 유사 배열인 arguments 객체를 배열로 변환하는 번거로움을 피할 수 있다.

// ES6
function sum(...args) {
  console.log(arguments); // Arguments(5) [1, 2, 3, 4, 5, callee: (...), Symbol(Symbol.iterator); f]
  console.log(Array.isArray(args)); // true
  return args.reduce((pre, cur) => pre + cur);
}

console.log(sum(1, 2, 3, 4, 5)); // 15

하지만 ES6의 화살표 함수에는 함수 객체의 arguments 프로퍼티가 없다. 따라서 화살표 함수로 가변 인자 함수를 구현해야 할 대는 반드시 rest 파라미터를 사용해야 한다.

var normalFunc = function () {};
console.log(noramlFunc.hasOwnProperty('arguments')); // true

const arrowFunc = () => {};
console.log(arrowFunc.hasOwnProperty('arguments')); // false

 

3. Spread 문법

Spread 문법(Spread Syntax, ...)는 대상을 개별 요소로 분리한다. Spread 문법의 대상은 이터러블이어야 한다.

// ...[1, 2, 3]는 [1, 2, 3]을 개별 요소로 분리한다(-> 1, 2, 3)
console.log(...[1, 2, 3]) // 1, 2, 3

// 문자열은 이터러블이다.
console.log(... 'Hello'); // H e l l o

// Map과 Set은 이터러블이다.
console.log(... new Map([['a', '1'], ['b', '2']])); // [ 'a', '1' ] [ 'b', '2' ]
console.log(... new Set([1, 2, 3])); // 1 2 3

// 이터러블이 아닌 일반 객체는 Spread 문법의 대상이 될 수 없다.
console.log(... { a: 1, b: 2});
// TypeError: Found non-callable @@iterator

1) 함수의 인수로 사용하는 경우

배열을 분해하여 배열의 각 요소를 파라미터에 전달하고 싶은 경우, Function.prototype.apply를 사용하는 것이 일반적이다.

// ES5
function foo(x, y, z) {
  console.log(x); // 1
  console.log(y); // 2
  console.log(z); // 3
}

// 배열을 분해하여 배열의 각 요소를 파라미터에 전달하려고 한다.
const arr = [1, 2, 3];

// apply 함수의 2번째 인수(배열)는 분해되어 함수 foo의 파라미터에 전달된다.
foo.apply(null, arr);
// foo.call(null, 1, 2, 3);

ES6의 Spread 문법(...)을 사용한 배열을 인수로 함수에 전달하면 배열의 요소를 분해하여 순차적으로 파라미터에 할당한다.

// ES6
function foo(x, y, z) {
  console.log(x); // 1
  console.log(y); // 2
  console.log(z); // 3
}

// 배열을 foo 함수의 인자로 전달하려고 한다.
const arr = [1, 2, 3];

// ...[1, 2, 3]는 [1, 2, 3]을 개별 요소로 분리한다. (-> 1, 2, 3)
// spread 문법에 의해 분리된 배열의 요소는 개별적인 인자로서 각각의 매개변수에 전달된다.
foo(... arr);

앞에서 살펴본 Rest 파라미터는 Spread 문법을 사용하여 파라미터를 정의한 것을 의미한다. 형태가 동일하여 혼동할 수 있으므로 주의가 필요하다.

// Spread 문법을 사용한 매개변수 정의 (= Rest 파라미터)
// ...rest는 분리된 요소들을 함수 내부에 배열로 전달한다.
function foo(param, ...rest) {
  console.log(param); // 1
  console.log(rest); // [2, 3]
}
foo(1, 2, 3);

// Spread 문법을 사용한 인수
// 배열 인수는 분리되어 순차적으로 매개변수에 할당
function bar(x, y, z) {
  console.log(x); // 1
  console.log(y); // 2
  console.log(z); // 3
}

// ...[1, 2, 3]는 [1, 2, 3]을 개별 요소로 분리한다. (-> 1, 2, 3)
// spread 문법에 의해 분리된 배열의 요소는 개별적인 인자로서 각각의 매개변수에 전달된다.
bar(...[1, 2, 3]);

Rest 파라미터는 반드시 마지막 파라미터이어야 하지만 Spread 문법을 사용한 인수는 자유롭게 사용할 수 있다.

// ES6
function foo(v, w, x, y, z) {
  console.log(v); // 1
  console.log(w); // 2
  console.log(x); // 3
  console.log(y); // 4
  console.log(z); // 5
}

// ...[2, 3]는 [2, 3]을 개별 요소로 분리한다. (-> 2, 3)
// spread 문법에 의해 분리된 배열의 요소는 개별적인 인자로서 각각의 매개변수에 전달된다.
foo(1, ...[2, 3], 4, ...[5]);

2) 배열에서 사용하는 경우

Spread 문법을 배열에서 사용하면 보다 간결하고 가독성 좋게 표현할 수 있다. ES5에서 사용하던 방식과 비교하여 살펴보도록 하자.

 

2-1) concat

ES5에서 기존 배열의 요소를 새로운 배열 요소의 일부로 만들고 싶은 경우, 배열 리터럴만으로 해결할 수 없고 concat 메소드를 사용해야 한다.

// ES5
var arr = [1, 2, 3];
console.log(arr.concat([4, 5, 6]); // [1, 2, 3, 4, 5, 6]

Spread 문법을 사용하면 배열 리터럴만으로 기존 배열의 요소를 새로운 배열 요소의 일부로 만들 수 있다.

// ES6
const arr = [1, 2, 3]
// ...arr은 [1, 2, 3]을 개별 요소로 분리한다.
console.log([...arr, 4, 5, 6]); // [1, 2, 3, 4, 5, 6]

2-2) push 

ES5에서 기존 배열에 다른 배열의 개별 요소를 각각 push 하려면 아래와 같은 방법은 사용한다.

// ES5
var arr1 = [1, 2, 3];
var arr2 = [4, 5, 6];

// apply 메소드의 2번째 인자는 배열. 이것은 개별 인자로 push 메서드에 전달된다.
Array.prototype.push.apply(arr1, arr2);

console.log(arr1); // [1, 2, 3, 4, 5, 6]

Spread 문법을 사용하면 아래와 같이 보다 간편하게 표현할 수 있다.

// ES6
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// ...arr2는 [4, 5, 6]을 개별 요소로 분리한다
arr1.push(...arr2); // == arr1.push(4, 5, 6);

console.log(arr1); // [1, 2, 3, 4, 5, 6]

2-3) splice

ES5에서 기존 배열에 다른 배열의 개별 요소를 삽입하려면 아래와 같은 방법을 사용한다.

// ES5
var arr1 = [1, 2, 3, 6];
var arr2 = [4, 5];

/*
apply 메소드의 2번째 인자는 배열. 이것은 개별 인자로 splice 메소드에 전달된다.
[3, 0].concat(arr2) → [3, 0, 4, 5]
arr1.splice(3, 0, 4, 5) → arr1[3]부터 0개의 요소를 제거하고 그자리(arr1[3])에 새로운 요소(4, 5)를 추가한다.
*/
Array.prototype.splice.apply(arr1, [3, 0].concat(arr2));

console.log(arr1); // [ 1, 2, 3, 4, 5, 6 ]

Spread 문법을 사용하면 아래와 같이 보다 간편하게 표현할 수 있다.

// ES6
const arr1 = [1, 2, 3, 6];
const arr2 = [4, 5];

// ...arr2는 [4, 5]을 개별 요소로 분리한다
arr1.splice(3, 0, ...arr2); // == arr1.splice(3, 0, 4, 5);

console.log(arr1); // [ 1, 2, 3, 4, 5, 6 ]

2-4) copy

ES5에서 기존 배열을 복사하기 위해 slice 메서드를 사용한다.

// ES5
var arr  = [1, 2, 3];
var copy = arr.slice();

console.log(copy); // [ 1, 2, 3 ]

// copy를 변경한다.
copy.push(4);

console.log(copy); // [ 1, 2, 3, 4 ]
// arr은 변경되지 않는다.
console.log(arr);  // [ 1, 2, 3 ]

Spread 문법을 사용하면 보다 간편하게 배열을 복사할 수 있다.

// ES6
const arr = [1, 2, 3];
// ...arr은 [1, 2, 3]을 개별 요소로 분리한다
const copy = [...arr];

console.log(copy); // [ 1, 2, 3 ]

// copy를 변경한다.
copy.push(4);

console.log(copy); // [ 1, 2, 3, 4 ]
// arr은 변경되지 않는다.
console.log(arr);  // [ 1, 2, 3 ]

이때 원본 배열의 각 요소를 얕은 복사(shallow copy)하여 새로운 복사본을 생성한다. 이는 Array#slice 메서드도 마찬가지다.

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

// shallow copy
// const _todos = todos.slice();
const _todos = [...todos];
console.log(_todos === todos); // false

// 배열의 요소는 같다. 즉, 얕은 복사되었다.
console.log(_todos[0] === todos[0]); // true

* Spread 문법과 Object.assign는 원본을 shallow copy한다. Deep copy를 위해서는 lodash의 deepClone을 사용하는 것을 추천한다.

Spread 문법을 사용하면 유사 배열 객체(Array-like Object)를 배열로 손쉽게 변환할 수 있다.

const htmlCollection = document.getElementsByTagName('li');

// 유사 배열인 HTMLCollection을 배열로 변환한다.
const newArray = [...htmlCollection]; // Spread 문법

// ES6의 Array.from 메소드를 사용할 수도 있다.
// const newArray = Array.from(htmlCollection);

 

4. Rest/Spread 프로퍼티

ES 언어 표준에 제안(proposal)된 Rest/Spread 프로퍼티(Object Rest/Spread Properties)는 객체 리터럴을 분해하고 병합하는 편리한 기능을 제공한다.

- 2019년 5월 현재 Rest/Spread 프로퍼티는 TC39 프로세스의 stage 4(Finished) 단계이다.

- stage 4에 도달한 제안은 finished-proposals를 참고하기 바란다.

- 지원 현황

- 2019년 1월 현재 객체 리터럴 Rest/Spread 프로퍼티를 Babel로 트랜스파일링하려면 @babel/plugin-proposal-object-rest -spread 플러그인을 사용해야 한다.

// 객체 리터럴 Rest/Spread 프로퍼티
// Spread 프로퍼티
const n = { x: 1, y: 2, ... { a: 3, b: 4 }};
console.log(n); // { x: 1, y: 2, a: 3, b: 4}

// Rest 프로퍼티
const { x, y, ...z } = n;
console.log(x, y, z); // 1 2 { a: 3, b: 4 }

Spread 문법의 대상은 이터러블이어야 한다. Rest/Spread 프로퍼티는 일반 객체에 Spread 문법의 사용을 허용한다.

Rest/Spread 프로퍼티를 사용하면 객체를 손쉽게 병합 또는 변경할 수 있다. 이는 Object.assign을 대체할 수 있는 간편한 문법이다.

// 객체의 병합
const merged = { ...{ x: 1, y: 2 }, ...{ y: 10, z: 3 }};
console.log(merged); // { x: 1, y: 10, z: 3 }

// 특정 프로퍼티 변경
const changed = { ...{ x: 1, y: 2 }, y: 100 };
// changed = { ...{ x: 1, y: 2 }, ...{ y: 100 } }
console.log(changed); // { x: 1, y: 100 }

// 프로퍼티 추가
const added = { ...{ x: 1, y: 2 }, z: 0 };
// added = { ...{ x: 1, y: 2 }, ...{ z: 0 } }
console.log(added); // { x: 1, y: 2, z: 0 }

Object.assign 메서드를 사용해도 동일한 작업을 할 수 있다.

// 객체의 병합
const merged = Object.assign({}, { x: 1, y: 2 }, { y: 10, z: 3 });
console.log(merged); // { x: 1, y: 10, z: 3 }

// 특정 프로퍼티 변경
const changed = Object.assign({}, { x: 1, y: 2 }, { y: 100 });
console.log(changed); // { x: 1, y: 100 }

// 프로퍼티 추가
const added = Object.assign({}, { x: 1, y: 2 }, { z: 0 });
console.log(added); // { x: 1, y: 2, z: 0 }
728x90
반응형
blog image

Written by ner.o

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

반응형

❓ Yarn이란?

yarn은 프로젝트의 의존성을 관리하는 javascript의 package manager이다. *Java의 gradle이나 Python의 pip와 같은 것.
yarn은 npm과 마찬가지로 package.json을 통해 의존 패키지를 구분하고 프로젝트에서 어떤 일을 할지 결정한다.

 

❗️ npm vs yarn

npm의 한계
npm 저장소의 취약한 보안 이슈를 시작으로, 의존 패키지의 버저닝 이슈, 무엇보다 패키지가 많아짐에 따라 빌드 성능이 좋지 않다는 점이 가장 큰 문제이다.
yarn의 장점
- 다운로드한 패키지 캐시: yarn은 모든 패키지를 유저 디렉토리에 저장해 캐싱한다.
캐싱하기 위한 디렉토리 경로는 아래와 같다.

$ yarn cache dir $HOME/Library/Caches/Yarn/v1

- 운영 병렬화 리소스 활용 극대화로 설치 시간 단축
- 코드 실행전 설치된 패키지의 무결성 확인
- 이전에 설치한 패키지를 설치한 경우 오프라인으로 재설치

 

🍓 yarn.lock

같은 package.json에 의존하는 서로 다른 환경이 서로 다른 버전의 패키지 의존성을 가지는 것을 방지하기 위해 yarn.lock 파일을 사용하여 정확한 버전을 명시한다.

 

🍭 CLI 옵션

Default Command
yarn 뒤에 다른 명령어 없이 실행하면 yarn install 이 실행된다.

yarn add
- package를 다운받을 때 사용하는 명령어이다.

$ yarn add <package...> # dependencies에 추가한다. $ yarn add <package...> [--dev/-D] # devDependencies에 추가한다.

yarn global

$ yarn global <add/bin/list/remove/upgrade> [--prefix] # 패키지를 시스템 전역에 설치, 업데이트, 삭제한다.

yarn create

$ yarn create <starter-kit-package> [<args>] # starter kit package를 이용해 프로젝트를 설치한다.

yarn init

$ yarn init # package.json을 생성한다. $ yarn init --yes/-y # package.json을 설정하는 질문을 모두 default로 넘어간다.

yarn install

$ yarn install # package.json 의존성 모듈을 설치한다. yarn.lock도 같이 생성



728x90
반응형
blog image

Written by ner.o

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