static 사용을 최대한 자제해라. / 필드가 없는 클래스는 잘못 만든 클래스일 가능성이 높다.

static 사용을 최대한 자제해라. / 필드가 없는 클래스는 잘못 만든 클래스일 가능성이 높다.

예시 코드

static을 사용하지 않은 경우

public class CardFiltering {
		private final List<Card> cards;
		
		public CardFiltering(List<Card> cards) {
				this.cards = new ArrayList<>(cards);
		}
		
		public List<Card> filterUnder5() {
				return cards.stream()
						.findAny(card -> card.getNumber() < 5)
						.orElseThrow(IllegalArgumentException::new);
		}

		public List<Card> filterOver10() {
				return cards.stream()
						.findAny(card -> card.getNumber() > 10)
						.orElseThrow(IllegalArgumentException::new);
		}
}

CardFiltering 클래스로부터 인스턴스를 생성할 때, List<Card> cards라는 데이터를 받는다.

static을 사용한 경우

public class CardFiltering {
		public static List<Card> filterUnder5(List<Card> cards) {
				return cards.stream()
						.findAny(card -> card.getNumber() < 5)
						.orElseThrow(IllegalArgumentException::new);
		}

		public static List<Card> filterOver10(List<Card> cards) {
				return cards.stream()
						.findAny(card -> card.getNumber() > 10)
						.orElseThrow(IllegalArgumentException::new);
		}
}

CardFiltering 클래스는 인스턴스 필드 값을 따로 가지지 않고, 필요한 값에 대해서는 메서드의 파라미터로 받았다. 이로 인해 클래스가 별다른 인스턴스 변수를 가지지 않아서 메서드들을 static으로 쓸 수 있게 되었다.

static 메서드가 왜 더 편하다고 느끼는 지 ?

  • static 메서드로 만들게 되면, 인스턴스화를 할 필요 없이 쉽게 사용할 수 있기 때문이다.
  • 클래스가 필드(상태값)를 가지지 않을 때에는, 굳이 인스턴스 생성을 해야 하는 지에 대해 의문이 들기 때문이다.

위 2가지에 대한 근거는 잘못된 근거이다. 그 이유를 밑에서 살펴보자.

static 메서드를 사용하지 말라고 하는 이유

1. 다형성을 활용하지 못하기 때문이다.

static 메서드는 interface를 구현하는 데 사용할 수 없다. 즉, static 메서드는 객체지향의 핵심인 다형성을 활용하지 못하게 되는 것이다.

2. 데이터를 캡슐화하지 못하기 때문이다.

절차지향은 데이터를 캡슐화하지 않았기 때문에 발생한 단점들이 많았다. 이러한 절차지향의 단점을 극복하기 위해 나온 패러다임이 객체 지향이다. 객체 지향은 데이터와 동작을 캡슐화해야 한다. 생성자의 매개변수로 전달하는 데이터들이 캡슐화할 데이터임을 나타낸다. 하지만 static 메서드를 사용하게 되면 매번 메서드의 파라미터로 필요한 데이터를 전달해야 한다. 이로 인해 동작에 대해서는 여전히 캡슐화를 할 수 있지만, 데이터에 대해서는 캡슐화를 하지 못하게 된다.

3.

Clean Code 책에서는 매개 변수가 없는 형태의 객체를 만드는 것이 좋은 설계라고 한다. 그래서 지침으로 매개 변수의 수를 제한하도록 한다. 즉, 생성자 매개 변수로 필요한 데이터를 미리 전달함으로써 메서드를 만들 때에는 캡슐화를 지킬 수 있게 된다.

  • 함수에서 이상적인 인수 개수는 0개이다. 최대한 인수 개수는 적을수록 좋다는 뜼이다. 왜냐하면 인수는 코드를 읽는 사람 입장에서 어떤 인수를 넣어야 되는 지에 대한 세부사항을 추가적으로 알아야 하기 때문이다.
  • 각 클래스 메서드는 클래스 인스턴스 변수를 하나 이상 사용해야 한다. 일반적으로 메서드가 변수를 더 많이 사용할수록 메서드와 클래스는 응집도가 더 높다. 모든 인스턴스 변수를 메서드마다 사용하는 클래스는 응집도가 가장 높다.

    일반적으로 이처럼 응집도가 가장 높은 클래스는 가능하지도 바랍직하지도 않다. 그렇지만 우리는 응집도가 높은 클래스를 선호한다. 응집도가 높다는 말은 클래스에 속한 메서드와 변수가 서로 의존하며 논리적인 단위로 묶인다는 의미기 때문이다.

    ‘함수를 작게, 매개변수 목록을 짧게’라는 전략을 따르다보면 때때로 몇몇 메서드만이 사용하는 인스턴스 변수가 아주 많아진다. 이는 십중팔구 새로운 클래스로 쪼개야 한다는 신호이다. 응집도가 높아질도록 변수와 메서드를 적절히 분리해 새로운 클래스 2~3개로 쪼개준다.

4. 필드(속성)를 가지지 않는 클래스는 쓸모 없는 클래스이기 때문이다.

(By. 엘레강트 오브젝트)

OOP의 ‘객체’는 고수준의 행동을 낳기 위해 함께 동작하는 객체들의 집합체이다. 객체들의 집합체에 대한 예시를 보자.

  • 책 = 페이지 + 표지 + ISBN
  • 책장 = 책 + 제목
  • 자동차 = 바퀴 + 엔진 + 앞 유리
  • 차고 = 자동차 + 주소
  • 직원 = 이름 + 나이 + 봉급
  • 회사 = 직원 + 이름 + 관리자

위의 예시를 통해서도 알 수 있다시피, 특정 객체가 다른 객체를 캡슐화 하지 않는 객체란 존재하지도 않으며 존재할 수도 없다는 사실을 잘 보여준다. 부품이 없는 객체는 의미가 없기 때문이다. 상태 없는 객체는 존재해서는 안 되고, 상태는 객체의 식별자여야 한다. 내부에 캡슐화된 모든 객체 또는 필드들이 객체의 식별자를 구성하는 요소라는 사실을 받아들여야 한다. 객체의 식별자는 기본저긍로 세계 안에서 객체가 위치하는 좌표이다.

public class Year {
		int read() {
				return System.currentTimeMills() / (1000 * 60 * 60 * 24 * 30 * 12) - 1970;
		}
}

위 Year 클래스의 인스턴스는 어떠한 데이터(필드, 속성)도 캡슐화하지 않았다. 즉, 별다른 식별자로 사용할만한 프로퍼티가 없으므로, 이 클래스의 모든 객체들은 동일하다는 사실을 알 수 있다. 하지만 객체 지향의 관점에서 본다면 이 설계는 잘못된 것이다. 객체가 무(無, nothing)와 아주 비슷한 어떤 것이 아니라면 무언가를 캡슐화해야 한다. 여기에서 ‘무’란 세계 안에서 좌표가 없는 존재를 의미한다. 이런 특징을 가진 엔티티만이 아무런 데이터에 대해서 캡슐화하지 않을 수 있는데, 오직 하나만 존재하고 생존이나 자신의 좌표를 표현하기 위해 다른 엔티티를 필요로 하지 않기 때문이다.

반대로 어떤 일을 수행하는 객체는 다른 객체들과 공존하면서 이들을 사용한다. 이 객체는 자기 자신을 식별할 수 있도록 다른 객체들을 캡슐화해야 한다. 캡슐화된 상태는 세계 안에서 객체의 위치를 지정하는 고유한 식별자이다. 만약 객체가 어떤 것도 캡슐화하지 않는다면 객체의 좌표는, 바로 객체 자신이 세계 전체가 되어야 한다. 오직 하나의 세계만 존재할 수 있기 때문에 이 클래스는 오직 하나만 존재해야 한다.

References

정리 완료

왜 자바에서 static의 사용을 지양해야 하는가?

Constructor Parameters vs Method Parameters?

[JAVA] static키워드 바로알기