클린코드 9장 (클래스), 10장 (시스템), 11장(창발성), 12장(동시성)

JAVA/클린코드|2020. 7. 10. 09:14

클래스


  • 내부에서 동작하는 변수나 유틸리티는 protected로 선언하여 테스트에 사용하기도 한다.
  • 클래스에 기본 규칙은 작게 만드는 것. 많을수록 클래스에 대한 책임이 너무 커진다.
    SPR을 지키기 위해 클래스가 너무 많아지면 사용에 더 어렵다고 우려할 수 있으나 하나의 여러 책임을 가지고 있는 클래스를 사용하는 것 보다 더 직관적 이므로 사용하기 더 편하다.
  • 클래스 내부에 인스턴스 변수가 많아 진다는건 결국 클래스 내부에 함수와 인스턴스 변수들 사이에 응집도가 높아진다는 뜻이다. 이럴 수록 클래스를 분리해야 한다는 걸 의미한다.
  • 긴 함수를 쪼갠다 → 작은 함수 여러 개로 만든다. → 몇몇 변수와 몇몇 함수만 사용 되는 경우가 보이면 클래스로 쪼갠다.
  • 특정 기능이 변경 될 때 마다 코드가 변경되어야 하는 함수가 있다고 한다면 이는 SRP를 위반하는 행위이다. 그럴 경우 인터페이스를 만들어 기능 마다 필요한 코드를 구현하도록 하면 SRP와 OCP를 모두 위반하지 않을 수 있다.
  • 인터페이스를 선언하고 구현 클래스를 사용하는 것이 깔끔한 경우도 있으나 특정 기능이 매번 바뀌어야 하는 경우에는 변경의 위험이 있을 수 있다. 예를 들어 상황에 따라서 호출하는 api가 변경될 수 있는 경우에는 구현 클래스 레벨에서 모두 할 것이 아니라 api를 호출하는 인터페이스를 전달 받아서 처리하는 것이 더 좋다.
public interface StockExchange {
	Money currentPrice(String symbol);
}

public Portfolio {
	private StockExchange stockExchange;
	public Portfolio (StockExchange stockExchange) {
		this.stockExchange = stockExchange;
	}

	//...
}

 

 

 

 

시스템


  • 모든 애플리케이션에서 가장 중요한 부분은 관심사 분리이다.
    아래와 같은 코드의 경우 Lazy Initialization 또는 Lazy Evaluation을 사용하여 실제 필요한 순간에 할당하여 사용할 수 있다는 장점이 있으나 테스트 할 때 런타임 시에 객체 생성 로직이 합쳐져 있어서 Mock 작업도 진행해 줘야 하고 정상적으로 생성 되는지도 테스트 해야하는 두 가지의 책임이 생기기에 이는 SRP를 위반하는 행위이다. 이처럼 시스템 설정 부분과 런타임 시 필요한 부분이 연동되어 있으면 안된다. 모듈화와 관심사 분리가 잘 고려되어야 한다.
public Service getService() {
	if (this.service == null) 
		this.service = new WedulServiceImpl();
	return this.service;
}
  • Main과 application은 서로 관심사가 분리되어 있어 main은 적절하게 객체를 생성하고 application은 그 것이 잘 생성되었다는 걸 가정하에 실행하도록 하여 시스템 설정과 실행을 분리하도록 한다.
  • 객체는 인스턴스로 만드는 책임을 지지 않고 이에 대한 책임은 main이나 특수 컨테이너 제공하는 것을 DI (의존성 주입)이라고 한다.
  • 시스템은 깨끗해야하고 깨끗하지 못한 아키텍처는 도메인 논리를 흐리며 기민성을 떨어뜨린다. 도메인 논리가 흐려지면 제품 품질이 떨이지고 버그가 숨어들기 쉬워지고 생산성이 떨어진다.
  • 시스템이든 모듈이든 실제로 돌아가는 가장 단순한 수단을 사용해야 한다는 사실을 명심하자.

 

 

 

창발성


설계 규칙 4가지

 

 

모든 테스트를 실행하라.

  • 테스트를 통해 의도대로 돌아가는지 확인하는 건 당연한 역할
  • 테스트를 진행하다 보면 설계 품질은 더불어서 상승한다.
  • 결합도가 높으면 테스트가 어렵기 때문에 테스트를 작성하면서 DIP, DI, 인터페이스, 추상화 등과 같은 도구를 사용해 결합도를 낮출 수 있다.
  • 완벽한 테스트 코드를 만들고 나면 코드를 수정하는 리팩토링 과정을 거친다 하더라도 검증이 잘 되기 때문에 기존 코드가 잘 동작하지 않을거라는 불안에서 벗어 날 수 있다. 그래서 장기적으로 보나 단기적으로 보나 테스트 코드는 중요하다.
  • 핵심은 응집도를 높이고, 결합도를 낮추고, 관심사를 분리하고, 시스템 관심사를 모듈로 나누고, 함수와 클래스 크기를 줄이고, 더 나은 이름을 선택하는 것.

 

중복을 없애라

  • 같은 기능을 여러곳에서 생성하는 건 수정이 있을 때 여러곳에 작업이 필요로 하다.
  • 중복을 해결 하기 위해서는 TEMPLATE METHOD 패턴을 참고하라.
// 중복으로 들어가는 코드는 추상 클래스 내부에서 정의하고 
// 개별적으로 사용되는 부분만 abstract 메소드 선언한 뒤, 그 코드들을 공용으로 쓰는 메소드를 만들어라.



// 직원들 임금을 정하는 템플릿 클래스
public abstract class Pay {

  public int salary() {
	return commonSalary() + ratingBouns();
  }

  public int commonSalary() {
	return 100000;
  }

  abstract protected int ratingBonus();
}

// 정규직 임금
public class regularPay extends Pay {

  @Override
  protected int ratingBonus() {
	return 12321;
  }
}

// 비정규직 임금
public class temporaryPay extends Pay {
  
  @Override
  protected int ratingBonus() {
	return 21251;
  }
}

 

의도를 표현하라.

  • 표준 명칭과 가식성 좋은 이름을 사용하고 함수와 클래스 크기등을 잘 조절하여 같은 동료가 이해하기 쉽게 작성하라.

 

클래스와 메서드의 수를 최소로 줄여라

  • 클래스와 메서드 수를 줄이기 위해 무조건 인터페이스먼저 선언하고 진행을 하거나 하는 습관도 좋다.
  • 중복을 제거하고 테스트케이스를 잘 만들어 나가서 클래스와 메서드의 수를 최대한으로 줄여보자.

 

 

 

동시성


  • 대량의 데이터를 동시에 실행시키면 단일로 실행시킬 때 보다 더욱 효율적이다.
  • 원칙적으로 각 서블릿 스레드는 다른 서블릿 스레드와 무관하게 자신만의 세상에서 돌아간다.
  • 동시성은 부하를 유발할 수 있고 복잡하며 버그가 발생할 시 재현이 어렵다.
  • 동시성을 방어하기 위해서는 우선 동작 단위를 작게 하여 SRP를 꼭 지켜야 한다.
  • 그리고 임계영역을 제한하고 그 임계영역을 최대한 작게 만들어서 다른 동작에 영향 없게 설계 해야 한다.
  • ConcurrentHashMap과 같은 다중 스레드 환경에서 안전한 라이브러리를 활용하라.
  • 동기화 코드를 실행할 시 알수 없는 에러가 발생할수도 있는데 이를 단순 일회성 오류라고 생각하지 말고 실패할 수 있는 모든 경우의 테스트를 실행하라.

 

 

 

그 이후 내용은 특정 라이브러리를 개선하는 작업등이 남아있는데 이 부분은 따로 정리하지는 않는다.

결론은 기본에 충실하고 코드의 나쁜 품질을 지키기 위해서 컨벤션 정의가 필요하다고 생각된다. 결국 일은 혼자하는 것이 아니기 때문에 컨벤션 정의가 없으면 새로들어오는 사람이나 기존의 사람도 계속 코드를 유지하기는 어렵다고 판단된다. 이 기회에 이 규칙과 기존에 공부했던 내용들을 토대로 컨벤션을 한번 만들어 봐야겠다.

댓글()

객체지향의 사실과 오해 1 ~ 2장

Book Review|2019. 10. 27. 17:45
객체지향의 사실과 오해
국내도서
저자 : 조영호
출판 : 위키북스 2015.06.17
상세보기

객체지향의 사실과 오해를 읽고 핵심적으로 생각되는 부분만 정리해봤다.

 

1. 협력하는 객체들의 공동체

현실세계의 객체

객체지향을 실세계와 대입하는 경우가 많다. (완벽하게 동일 시 할 수는 없지만 이해하기에는 편리함)

 

그럼 객체 지향을 현실세계에 대입했을 때 커피집을 생각해보면 손님, 캐리어, 바리스타는 개개인의 객체를 의미하고 각 객체는 서로간의 협력관계가 있고 그 속에서 자신의 책임을 다한다.

 

예를 들어보면 손님은 주문을 하고 캐리어는 계산을 받고 바리스타는 커피를 만드는 역할을 한다. 그리고 서로간의 협력 관계를 통해 주문을 하고 받고 커피를 만드는 작업을 진행한다.

 

이렇듯 객체지향에서 가장 중요한 개념은 각자의 역할, 책임 그리고 서로간의 협력이다.

그 중에서 협력은 객체지향에서 중요한 개념으로 서로간의 충분히 협력적이어야하고 다른 객체에 적극적으로 도움을 요청할 정도로 열린 마음을 가져야한다. 여기서의 협력적인 의미는 다른 객체에 수동적이라는 뜻이 아니라 요청에 응답하는 것을 의미하고 어떻게 응답하는 지는 객체 스스로 결정한다. 또한 객체는 충분히 자율적인 존재로 구성된 협력 공동체이다. 협력적일 수 있지만 결국 스스로 행동할 줄 알아야한다.

 

 

객체의 특징

- 객체는 상태와 행동을 가지고 있으며 스스로 자기 자신을 책임진다.

- 객체지향이 절차지향과 다른 가장 큰점은 실행시간에 어떤 행위를 할지 결정하는 것이다. 절차지향의 컴파일 시 결정되는 부분과 가장 큰 다른 점이다.

- 객체의 자율성으로 객체가 외부의 요청을 받는 메소드와 객체가 작업을 하는 구체적인 방법을 나눔으로써 매커니즘이 나눔으로써 매커니즘이 정해지는데 이게 바로 캡슐화이다.

- 객체는 다른 객체와 협력하기 위해 메시지를 전송하고 메시지를 수신한 객체는 메시지를 처리하는 데 적합한 메서드를 자율적으로 선택한다.

- 객체지향은 클래스 기반이 아니라 객체를 중심으로 바라봐야한다. 지나치게 클래스를 강조하는 프로그래밍 언어적인 관점은 객체의 캡슐화를 저해하고 클래스를 서로 강하게 결합시킨다. 어떤 클래스가 필요한가가 아니라 어떤 객체들이 어떤 메시지를 주고받으며 협력하는가를 중요시 여기자.

- 클래스의 구조와 메서드가 아니라 객체의 역할, 책임, 협력에 집중하라.

 

 

2. 이상한 나라의 객체

객체는 인간이 분명하게 구별할 수 있는 물리적인 또는 개념적인 경계를 지닌 어떤 것을 의미한다. 객체는 현실세계와 정확하게 같지 않고 모방하는 것이다. 그리고 그 현실세계를 기반으로 새로운 세계를 창조하는 것이 객체지향이다.

 

객체에 있는 상태를 특정시점에 객체가 가지고 있는 정보의 집합이다. 객체의 상태는 객체에 존재하는 정적인 프로퍼티와 동적인 프로퍼티로 구성된다. 프로퍼티는 단순한 값을 나타내는 속성과 다른 객체를 참조하는 Link로 구성된다. 

 

객체는 자율적인 존재로써 다른 객체가 값을 바꿀수 없다. 하지만 객체의 행동은 상태에 영향을 받고 변경시키는데 이러한 행동은 외부의 요청 또는 수신된 메시지에 응답하기 위해 동작하고 반응하는 활동이다. 행동의 결과로 객체는 자신의 상태를 변경하거나 다른 객체에 메시지를 전달할 수 있다.

 

객체의 상태는 캡슐속에 감춰두고 행동만 외부로 노출시켜서 그 행동으로 상태가 변경될 수 있도록 하는 것, 객체가 주체가 되어 행동하는 것을 캡슐화라고 한다.

 

 

 

1장과 2장에서 객체지향속에서 객체의 정확한 정의를 다시 정리할 수 있었던 것 같다. 

단순하게 코드로 보여주는게 아니라 이야기로 객체지향에 대해 이해하면서 볼 수 있어서 좋았다.

좋은 책이다.

 

출처 : 객체지향의 사실과 오해 (조영호)

댓글()

클래스와 인터페이스 - 규칙22 멤버 클래스는 가능하면 static으로 선언하라.

JAVA/Effective Java|2018. 5. 29. 22:43

중첩 클래스 (nested class)는 다른 클래스안에 선언된 클래스이다. 

이런 중첩 클래스는 4가지 종류로 구성되어있다.
1. static member class
2. non-static member class
3. anonymous class
4. local class.

2 ~ 4번 클래스는 내부 클래스(inner class)이다.


상황별 중첩 클래스 사용

그럼 위에 출력한 4가지의 중첩 클래스의 적절한 사용 시기를 알아보자.

1. 정적 멤버 클래스 (static member class)
- 정적 멤버 클래스는 바깥 클래스의 모든 멤버에 접근할 수 있다. (private 멤버 변수도 접근 가능)
- 다른 정적 멤버와 동일하게 접근 권한 규칙을 따른다.
- private static member class로 선언할 시 해당 클래스에 접근 가능한 클래스는 바깥 클래스이다.

2. 비 정적 멤버 클래스 (nonstatic member class)
- 비 정적 클래스 객체는 바깥 클래스 객체와 바로 연동이된다.
=> 바깥 클래스의 메소드도 호출 할 수 있으며, this 키워드를 통해 바깥 객체에 대한 참조도 얻을 수 있다. (this  키워드를 사용하여 바깥 클래스를 접근 하기 위해서는 바깥 클래스 이름을 기재하여 사용해야 한다. ClassName.this )
- 정적 클래스와 달리 단독으로 존재 할 수 없다.


위와 같은 이유로
비정적 클래스를 사용할 경우에는 바깥 클래스의 멤버 요소들을 사용하지 않아도 
접근이 가능하기 때문에 시간과 공간 요구량이 늘어난다. ( 이는 garbage collection이 쓰레기 처리하는데 어렵게한다.)

그러므로
바깥 클래스의 내부요소에 접근하지 않을 경우에는 
무조건 정적 클래스로 선언해야 한다.


3. 익명 클래스 (anonymous class)
- 익명 클래스는 사용하는 순간에 선언하고 객체를 만든다.
- 표현식 문법을 준수



댓글()

클래스와 인터페이스 - 규칙 21 전략을 표현하고 싶을 때는 함수 객체를 사용하라.

JAVA/Effective Java|2018. 5. 29. 22:41

호출 대상에 대해 어떠한 작업을 수행하는 것을

전략 패턴이라고 한다.



1
2
3
4
5
class StringCompare {
 public int compare (String s1, String s2) {
   return s1.length() - s2.length();
 }
}
cs



 두 개의 문자열을 받아서 비교하는 클래스를 사용할 수 있는 전략 패턴이다.
비교가 필요할 경우 매번 StringCompare 클래스를 생성하지 말고, 싱글톤 패턴을 정의하여 가져다가 사용하면 더욱 편리하다.

하지만 이 StringCompare 클래스의 경우 객체를 메서드에 전달하기 위해서는 인자의 자료형이 String이어야 한다. 

따라서 Compareator 인터페이스를 정의 하여 이를 StringComapre 클래스가 구현하도록 해야 한다.



1
2
3
4
5
6
7
8
9
10
11
// 인터페이스
public interface Comparator<T> {
  public int compare(T t1, T t2);
}
 
class StringCompare implements Comparator<String> {
  @Override 
  public int compare (String s1, String s2) {
   return s1.length() - s2.length();
  }
}
cs




정리하면,
 함수 객체의 주된 용도는 전략 패턴이다. 전략 패턴을 사용하기 위해서는 인터페이스 (위에서는 Comparator<String>)를 선언하고, 실행가능 전략 클래스 생성 시 저 위에 인터페이스를 구현하도록 해야 한다.

단, 전략 패턴이 한번만 사용할 경우 익명 클래스로 사용할 수 있다.



1
2
3
4
5
6
Arrays.sort(StringArray, new Comparator<String>() {
  @Overide
  public int compare (String s1, String s2) {
    return s1.length() - s2.length();
  }
});
cs



만약 자주 사용 되는 경우에는,
public static final 지시자를 사용하여 외부에 공개 할 수 있다.



1
2
3
4
5
6
7
8
9
10
11
class Host {
  private static Class StringCompare implements Comparator<String>, Serializable {
   public int comapare(String s1, String s2) {
    return s1.length() - s2.length();
   }
  }
 
  // Compare<String>를 구현한 클래스로, 다형성을 이용하여 StringCompare를 받을 수 있다.
  // 외부에서는 Host.STRING_LENGTH_COMPARATOR.compare("dd", "ff"); 방식으로 사용할 수 있다.
  public static final Comparator<String> STRING_LENGTH_COMPARATOR = new StringCompare();
}
cs



출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙21 인용.



댓글()

클래스와 인터페이스 - 규칙 20 태그 달린 클래스 대신 클래스 계층을 사용하라.

JAVA/Effective Java|2018. 5. 29. 22:39

클래스에 상황에 따라서 역할을 변경하는 

태그 달린 클래스를 볼 수 있다.

예를 들어, 아래 유형과 같이 탈것을 나타내는 클래스는
어떤 형태로 사용하느냐에 따라서 자동차가 될수도, 자전거가 될 수도 있다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Vehicle {
  enum Type { CAR, BICYCLE };
  final Type type;
  
  public Vehicle (double pedalCount) {
   this.type = Type.BICYCLE;
  }
 
  public Vehicle (double license, int sheetCount) {
    this.type = Type.CAR;
  }
 
  // 바퀴수를 반환하는 메서드
  public int getWheelCount() {
   switch (type) {
    case CAR:
       return 4;
       break;
    case BICYCLE:
       return 2;
       break;
   }
  }
 
}
cs




하지만 이럴경우, 종류에 따라 Vehicle 클래스에서
다루어주어야 할 부분이 너무 많다. 

예를 들면,
위에 보면 Vehicle클래스 타입에 따라 getWheelCount 메서드를 사용할 때, switch문을 사용해야 하고 굉장히 번잡스럽다.

이러한 이유들로
관리도 힘들고 버그가 발생할 위험도 있다.

또한 이런 경우 단일 책임 원칙 (Single Responsibility Principle) SRP를 위해하는 행위이다.

이런 경우 태그 기반 클래스(tagged class)를 사용하지 말고 계층기반으로 변경한다.



1
2
3
public abstract class Vehicle {
 abstract int getWheelCount();
}
cs




Vehicle을 추상클래스로 생성하고, 이를 Car, Bicycle클래스에서 상속받아 구현하여 

getWheelCount를 자신의 클래스에 맞게 재정의 하여 사용하도록 처리한다.

그럴 경우, 처리도 단순해지고 유지보수에도 수월해진다.


결론을 말하자면,

태그 기반 클래스를 피하고 클래스 계층을 사용한다.



출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙20인용.



댓글()

클래스와 인터페이스 - 규칙 19 인터페이스는 자료형을 정의할 때만 사용하라.

JAVA/Effective Java|2018. 5. 29. 22:38

인터페이스를 구현하는 클래스를 만들경우,

그 인터페이스는 해당 클래스의 객체를 참조할 수 있는 자료형 역할을 하게 된다. 

상수 인터페이스
=> 메서드가 없고 static final 필드만 있어, 상수 이름 앞에 클래스 이름을 붙이는 번거로움을 피하기 위해서이다.



1
2
3
public interface PhysicalConstants {
  static final double AVOGADROS_NUMBER = 6.02312312323;
}
cs




이런 상수 인터페이스 패턴은 인터페이스를 잘못 사용한 것이다.
=> 인터페이스는 사용자에게 이러한 기능을 구현한 클래스라는 명세를 알려주는거와 같은데 상수 인터페이스는 기능 명세를 제공하는 것이 아니라 상수값을 사용하기 위한 것으로 좋은 방식이 아니다.

또한 

추후에 이런 상수 인터페이스에 정의된 상수를 사용하지 않게 되더라도 하위호완성 때문에 제거를 하지 못하는 문제가 발생한다.

이런 상수들이 필요한 경우에는 상속이 불가능하고, 객체 생성이 불가능 하도록 private 생성자를 가지고 있는 유틸 클래스에 정의해서 사용하는 것이다.

만약 유틸리티 클래스에 정의된 상수 값을 자주 사용하게 된다면, 
JDK 1.5부터 도입된 정적 임포트(static import) 기능을 사용하여 클래스 이름을 제거하여 사용할 수 있다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Constant {
  private Constant() {}
  
  public static BABO = 2412.424;
}
 
 
 
import static com.wedul.wedulpos.Constant;
 
public Class WedulPosDto {
 double getData() {
  return BABO;
 }
}
cs



정리하면 인터페이스는 클래스의 명세로서 자료형을 정의할 때만 사용하고, 그 특정 목적을 벗어난 특정 상수와 같은 상수 인터페이스는 사용을 자제해야 한다.

출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙19 인용.

댓글()

클래스와 인터페이스 - 규칙 18 추상 클래스 대신 인터페이스를 사용하라.

JAVA/Effective Java|2018. 5. 29. 22:36

자바는 다중 상속이 되지 않기 때문에, 추상 클래스 보다 인터페이스를 사용하는 것이 좋다.

믹스인
인터페이스는 믹스인을 정의하는 데 이상적이다.
=> 믹스인은 클래스가 주 자료형 이외에 추가로 구현할 수 있는 자료형으로 어떤 선택적 기능을 제공한다는 사실을 선언하기 위해 쓰인다.
=> 예를 들면, Comparable은 어떤 클래스가 자기 객체를 다른 객체와의 비교 결과에 따른 순서를 갖는다고 선언할 때 쓰는 인터페이스이다.

이런 믹스인 기능을 추상클래스에 할 수 없다. 
=> 클래스가 가질 수 있는 상위 클래스는 하나 이기 때문에 좋은 방법이 아니다.

인터페이스는 여러 속성을 합쳐서 새로운 속성을 만들 수 있다.
=> singer와 SongWriter 속성을 합쳐서 새로운 인터페이스를 만들 수 있다.



1
2
3
4
5
6
7
8
9
10
public interface singer {
}
 
public interface SongWriter {
 
}
 
public interface SingerSongWriter extends Singer, SongWriter {
 
}
cs




하지만 이런 조합을 인터페이스가 아닌 클래스를 이용한다면 별도의 클래스를

계속 만들어 주어야 한다.

필요한 속성이 n개가 있을 때 조합의 가짓수는 2의 n승으로 이것을 조합 폭증이라고 한다.

추상 골격 구현 (Abstract skeletal implemetation)
해당 추상 골격 구현 클래스를 중요 인터페이스마다 두면, 인터페이스의 장점과 추상 클래스의 장점을 결합 할 수 있다.

방법
=> 인터페이스로는 자료형을 정의하고, 구현하는 일은 골격 구현 클래스에 맡기면 된다.

Ex) List



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 골격 구현 위에서 만들어진 완전한 List 구현
static List<Integer> intArrayAsList(final int[] a) {
  if (a == null
     throw new NullPointerExeception();
 
  return new AbstractList<Integer>() {
     public Integer get(int i) {
        return null;
     }
 
     @Oveerride
     public Integer set(int i, Integer val) {
         int oldVal = a[i];
         a[i] = val;
         return oldVal;
      }
 
      public int size() {
         return a.length;
      }
  };
}
cs



인터페이스 보다 추상 클래스가 좋은 경우는
새로운 기능이 추가되었을 때, 인터페이스는 새로운 메서드를 추가하더라도 새로 모두 구현해 주어야 하지만 추상클래스의 경우에는 그대로 하위클래스에서 사용할 수 있다는 것이다.
=> 하지만 Java 8에서 default 메소드를 통해 이 단점 또한 해결이 되었다.

또한 
public 인터페이스의 경우 공개되고 난 다음에는, 인터페이스 수정이 거의 불가능 하다. 그러므로 처음 설계부터 잘 구현해야 한다는 단점이 있다.

출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙18 인용.



댓글()

클래스와 인터페이스 - 규칙 17 계승을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 계승을 금지하라.

JAVA/Effective Java|2018. 5. 29. 22:34


16번에서 정의한 것처럼 계승 (이하 상속)을 받을 경우 문제를 야기할 수 있다.

그렇기에 이를 예방하기 위해서 문서를 잘 갖추고 있어야 하는데 
문서를 갖춘다는 것은 어떤 의미일까?

이는 재정의 가능 메서드를 내부적으로 어떻게 사용하는지(self-use)반드시 남기라는 것이다.
-> 어떤 재정의 가능 메서드를 어떤 순서로 호출하는지, 그리고 호출결과가 추후 어떤 영향을 미치는지 문서로 남기라는 의미이다.

여러 문제를 야기할 수 있기에, 계승에 맞도록 설계하고 문서화하지 않은 클래스에 대한 하위 클래스는 만들지 않아야 한다.


출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙17 인용.

댓글()