작성자: BJ Hargrave
Java API를 설계할 때 적용해야 하는 몇 가지 API 설계 방식을 이해합니다. 이러한 방법은 일반적으로 유용하며 OSGi 및 JPMS(Java Platform Module System)와 같은 모듈식 환경에서 API를 올바르게 사용할 수 있도록 보장합니다. 일부 관행은 규범적이며 일부는 규범적입니다. 물론 다른 좋은 API 설계 방식도 적용됩니다.
OSGi 환경은 Java 클래스 로더 개념을 사용하여 가시성 유형 캡슐화를 적용하는 모듈식 런타임을 제공합니다. 각 모듈에는 내보낸 패키지를 공유하고 가져온 패키지를 사용하기 위해 다른 모듈의 클래스 로더에 연결되는 자체 클래스 로더가 있습니다.
Java 9에 도입된 JPMS는 유형 접근성 캡슐화를 적용하기 위해 Java 언어 사양의 액세스 제어 개념을 사용하는 모듈식 플랫폼을 제공합니다. 각 모듈은 내보내어 다른 모듈에서 액세스할 수 있는 패키지를 정의합니다. 기본적으로 JMPS 계층의 모듈은 모두 동일한 클래스 로더에 있습니다.
패키지는 API를 포함할 수 있습니다. 이러한 API 패키지에는 API 소비자와 API 제공자라는 두 가지 역할이 있습니다. API 소비자는 API 공급자가 구현한 API를 사용합니다.
다음 디자인 사례에서는 패키지의 공개 부분에 대해 논의합니다. 공개 또는 보호되지 않은(즉, 비공개 또는 기본 액세스 가능) 패키지의 멤버 및 유형은 패키지 외부에서 액세스할 수 없으므로 패키지의 구현 세부 사항입니다.
Java 패키지는 결합력 및 안정 단위가 되도록 설계되어야 합니다. 모듈식 Java에서 패키지는 모듈 간의 공유 엔터티입니다. 한 모듈은 다른 모듈이 패키지를 사용할 수 있도록 패키지를 내보낼 수 있습니다. 패키지는 모듈 간 공유 단위이므로 패키지의 모든 유형이 패키지의 특정 목적과 관련되어야 한다는 점에서 패키지는 응집력이 있어야 합니다. java.util과 같은 패키지 패키지는 권장되지 않습니다. 이러한 패키지의 유형은 종종 서로 관련이 없기 때문입니다. 이러한 비 응집성 패키지는 패키지의 관련되지 않은 부분이 관련되지 않은 다른 패키지를 참조하고 패키지의 한 측면에 대한 변경 사항이 모듈이 실제로 해당 부분을 사용하지 않더라도 패키지에 의존하는 모든 모듈에 영향을 미치기 때문에 많은 종속성을 초래할 수 있습니다. 수정된 패키지입니다.
패키지는 공유 단위이므로 해당 내용이 잘 알려져 있어야 하며 포함된 API는 패키지가 향후 버전에서 발전함에 따라 호환 가능한 방식으로만 변경될 수 있습니다. 이는 패키지가 API 상위 집합이나 하위 집합을 지원해서는 안 된다는 의미입니다. 예를 들어 내용이 불안정한 패키지인 javax.transaction을 참조하세요. 패키지 사용자는 패키지에서 어떤 유형을 사용할 수 있는지 알 수 있어야 합니다. 이는 또한 패키지가 단일 엔터티(예: jar
)에 의해 전달되어야 함을 의미합니다.
파일) 및 패키지 사용자는 전체 패키지가 존재한다는 것을 알아야 하므로 여러 엔터티로 분할되지 않습니다.
또한 패키지는 향후 버전과 호환되는 방식으로 발전해야 합니다. 따라서 패키지의 버전이 관리되어야 하며 해당 버전 번호는 의미론적 버전 관리 규칙에 따라 발전해야 합니다. 의미론적 버전 관리에 대한 OSGi 백서도 있습니다.
그러나 패키지의 주요 버전 변경에 대한 의미론적 버전 관리 권장 사항에는 문제가 있습니다. 패키지 진화는 기능의 증가여야 합니다. 의미론적 버전 관리에서는 부 버전이 증가합니다. 기능을 제거하면 주요 기능을 늘리는 대신 패키지에 호환되지 않는 변경을 하게 됩니다
버전에서는 원래 패키지가 계속 호환되는 상태로 유지하면서 새 패키지 이름으로 이동해야 합니다. 이것이 왜 중요하고 필요한지 이해하려면 Semantic Import Versioning for Go에 대한 이 문서와 Clojure/conj 2016에서 Rich Hickey가 진행한 훌륭한 기조 프레젠테이션을 참조하세요. 두 가지 모두 주요 패키지 이름을 변경하는 대신 새 패키지 이름으로 이동하는 사례를 제시합니다. 버전이 호환되지 않는 패키지 변경을 수행할 때 발생합니다.
패키지의 유형은 다른 패키지의 유형을 참조할 수 있습니다. 예를 들어 매개변수 유형, 메서드의 반환 유형, 필드 유형 등이 있습니다. 이 패키지 간 결합은 패키지에 사용 제약 조건을 생성합니다. 이는 API 소비자가 참조 유형을 모두 이해하려면 API 공급자와 동일한 참조 패키지를 사용해야 함을 의미합니다.
일반적으로 우리는 패키지 사용 제약을 최소화하기 위해 이러한 패키지 결합을 최소화하려고 합니다. 이는 OSGi 환경에서 배선 확인을 단순화하고 종속성 팬아웃을 최소화하여 배포를 단순화합니다.
API의 경우 클래스보다 인터페이스가 선호됩니다. 이는 모듈식 Java에도 중요한 매우 일반적인 API 설계 방식입니다. 인터페이스를 사용하면 구현의 자유와 다중 구현이 가능해집니다. API 소비자를 API 공급자로부터 분리하려면 인터페이스가 중요합니다. 이를 통해 인터페이스를 구현하는 API 제공자와 인터페이스에서 메소드를 호출하는 API 소비자 모두가 API 인터페이스가 포함된 패키지를 사용할 수 있습니다. 이러한 방식으로 API 소비자는 API 공급자에 직접적으로 종속되지 않습니다. 둘 다 API 패키지에만 의존합니다.
추상 클래스는 때때로 인터페이스 대신 유효한 디자인 선택이지만, 특히 기본 메소드를 인터페이스에 추가할 수 있으므로 일반적으로 인터페이스가 첫 번째 선택입니다.
마지막으로 API에는 이벤트 유형 및 예외 유형과 같은 소규모의 구체적인 클래스가 필요한 경우가 많습니다. 괜찮지만 유형은 일반적으로 변경할 수 없어야 하며 API 소비자가 하위 클래스로 분류하기 위한 것이 아닙니다.
API에서는 정적을 피해야 합니다. 유형에는 정적 멤버가 있어서는 안 됩니다. 정적 팩토리는 피해야 합니다. 인스턴스 생성은 API에서 분리되어야 합니다. 예를 들어, API 소비자는 종속성 주입이나 OSGi 서비스 레지스트리 또는 JPMS의 java.util.ServiceLoader와 같은 객체 레지스트리를 통해 API 유형의 객체 인스턴스를 받아야 합니다.
정적은 쉽게 흉내낼 수 없으므로 테스트 가능한 API를 만드는 데 있어서 정적을 피하는 것도 좋은 습관입니다.
때때로 API 디자인에 싱글톤 개체가 있습니다. 그러나 싱글톤 개체에 대한 액세스는 정적 getInstance 메서드나 정적 필드와 같은 정적 메서드를 통해 이루어져서는 안 됩니다. 싱글톤 객체가 필요한 경우 API에서 해당 객체를 싱글톤으로 정의하고 위에서 언급한 것처럼 종속성 주입이나 객체 레지스트리를 통해 API 소비자에게 제공해야 합니다.
API에는 API 소비자가 API 공급자가 로드해야 하는 클래스 이름을 제공할 수 있는 확장성 메커니즘이 있는 경우가 많습니다. 그런 다음 API 공급자는 Class.forName(스레드 컨텍스트 클래스 로더 사용)을 사용하여 클래스를 로드해야 합니다. 이러한 종류의 메커니즘은 API 공급자(또는 스레드 컨텍스트 클래스 로더)에서 API 소비자에 대한 클래스 가시성을 가정합니다. API 디자인은 클래스 로더 가정을 피해야 합니다. 모듈화의 주요 포인트 중 하나는 유형 캡슐화입니다. 한 모듈(예: API 제공자)은 다른 모듈(예: API 소비자)의 구현 세부정보에 대한 가시성/접근성이 없어야 합니다.
API 디자인은 API 소비자와 API 제공자 간에 클래스 이름을 전달하는 것을 피해야 하며 클래스 로더 계층 구조 및 유형 가시성/접근성에 관한 가정을 피해야 합니다. 확장성 모델을 제공하려면 API 디자인에는 API 소비자가 클래스 개체를 전달하거나 인스턴스 개체를 API 공급자에게 전달해야 합니다. 이는 API의 메소드나 OSGi 서비스 레지스트리와 같은 객체 레지스트리를 통해 수행될 수 있습니다. 화이트보드 패턴을 참고하세요.
JPMS 모듈에서 사용되지 않는 java.util.ServiceLoader 클래스는 모든 공급자가 스레드 컨텍스트 클래스 로더 또는 제공된 클래스 로더에서 표시된다고 가정한다는 점에서 클래스 로더 가정으로 인해 어려움을 겪습니다. JPMS에서는 모듈 선언을 통해 모듈이
ServiceLoader 관리형 서비스입니다.
많은 API 설계에서는 객체가 인스턴스화되어 API에 추가되는 구성 단계만 가정하고 동적 시스템에서 발생할 수 있는 소멸 단계는 무시합니다. API 디자인은 객체가 오고 갈 수 있다는 점을 고려해야 합니다. 예를 들어 대부분의 리스너 API에서는 리스너를 추가하고 제거할 수 있습니다. 그러나 많은 API 디자인에서는 객체가 추가되고 제거되지 않는다고 가정합니다. 예를 들어, 많은 종속성 주입 시스템에는 주입된 개체를 철회할 수 있는 수단이 없습니다.
OSGi 환경에서는 모듈을 추가하고 제거할 수 있으므로 이러한 역학을 수용할 수 있는 API 설계가 중요합니다. OSGi 선언적 서비스 사양
주입된 개체의 철회를 포함하여 이러한 역학을 지원하는 OSGi에 대한 종속성 주입 모델을 정의합니다.
서문에서 언급했듯이 API 패키지 클라이언트에는 API 소비자와 API 제공자라는 두 가지 역할이 있습니다. API 소비자는 API를 사용하고 API 공급자는 API를 구현합니다. API의 인터페이스(및 추상 클래스) 유형의 경우 API 설계에서 API 제공자에 의해서만 구현되는 유형과 API 소비자가 구현할 수 있는 유형을 명확하게 문서화하는 것이 중요합니다. 예를 들어, 리스너 인터페이스는 일반적으로 API 소비자에 의해 구현됩니다
API 제공업체에 인스턴스가 전달됩니다.
API 제공자는 API 소비자와 API 제공자가 구현한 유형의 변경에 민감합니다. 공급자는 API 공급자 유형의 새로운 변경 사항을 구현해야 하며 API 소비자 유형의 새로운 변경 사항을 이해하고 호출할 가능성이 높아야 합니다. API 소비자는 일반적으로 API 소비자가 새 함수를 호출하기 위해 변경을 원하지 않는 한 API 공급자 유형의 (호환 가능한) 변경 사항을 무시할 수 있습니다. 그러나 API 소비자는 API 소비자 유형의 변경에 민감하므로 새 기능을 구현하려면 수정이 필요할 수 있습니다. 예를 들어, javax.servlet 패키지에서 ServletContext 유형은 서블릿 컨테이너와 같은 API 제공자에 의해 구현됩니다. ServletContext에 새 메소드를 추가하려면 새 메소드를 구현하기 위해 모든 API 제공자를 업데이트해야 하지만 API 소비자는 새 메소드를 호출하지 않는 한 변경할 필요가 없습니다. 그러나 Servlet 유형은 API 소비자에 의해 구현되며 Servlet에 새 메소드를 추가하려면 모든 API 소비자를 수정하여 새 메소드를 구현해야 하며 모든 API 공급자도 새 메소드를 활용하도록 수정해야 합니다. 따라서 ServletContext 유형에는 API 제공자 역할이 있고 Servlet 유형에는 API 소비자 역할이 있습니다.
일반적으로 API 소비자는 많고 API 제공자는 적기 때문에 API 진화는 API 소비자 유형 변경을 고려할 때 매우 신중해야 하며 API 제공자 유형 변경에 대해서는 좀 더 여유를 가져야 합니다. 업데이트된 API를 지원하려면 소수의 API 공급자를 변경해야 하지만 API가 업데이트될 때 많은 기존 API 소비자를 변경하도록 요구하고 싶지 않기 때문입니다. API 소비자는 API 소비자가 새 API를 활용하려는 경우에만 변경하면 됩니다.
OSGi Alliance는 API 패키지에서 유형의 역할을 표시하기 위해 문서 주석, ProviderType 및 ConsumerType을 정의합니다. 이러한 주석은 API에서 사용할 수 있도록 osgi.annotation jar에서 사용할 수 있습니다.
다음번에 API를 설계할 때 이러한 API 설계 방식을 고려하십시오. 그러면 API를 모듈식 Java 및 비모듈식 Java 환경 모두에서 사용할 수 있습니다.
위 내용은 Java용 API 설계 사례의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!