작성자: 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 提供者(例如 Servlet 容器)实现。向 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联盟定义了文档注释、ProviderType和ConsumerType来标记API包中类型的角色。这些注释可在 osgi.annotation jar 中找到,以便在您的 API 中使用。
下次设计 API 时,请考虑这些 API 设计实践。然后,您的 API 将可在模块化 Java 和非模块化 Java 环境中使用。
위 내용은 Java용 API 설계 사례의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!