1. 소개
이 사용방법(예제)에서는 유형 및 각 유형에 대한 설명을 포함하여 Java의 커플링에 대해 배웁니다. 마지막으로 의존성 반전 원리와 제어 역전 원리, 그리고 이들이 커플링과 어떤 관련이 있는지 간략하게 설명합니다.
2. 자바에서의 결합
커플링에 대해 이야기할 때 시스템 내의 클래스가 서로 의존하는 정도를 설명합니다. 개발 프로세스 중 우리의 목표는 결합을 줄이는 것입니다.
다음 시나리오를 고려하십시오. 메타데이터 수집기 응용 프로그램을 설계하고 있습니다. 이 애플리케이션은 우리를 위해 메타데이터를 수집합니다. XML 형식의 메타데이터를 가져온 다음 가져온 메타데이터를 CSV 파일로 내보내면 됩니다. 초기 접근 방식은 설계에서 알 수 있듯이 다음과 같을 수 있습니다.
우리 모듈은 데이터 가져오기, 처리 및 내보내기를 처리합니다. 그러나, 그것은 형편없는 디자인입니다. 이 디자인은 단일 책임 원칙 을 위반합니다 . 따라서 첫 번째 디자인을 개선하려면 관심사를 분리해야 합니다. 이 변경 후의 디자인은 다음과 같습니다.
이제 우리의 디자인은 XML Fetch 및 Export CSV 의 두 가지 새로운 모듈로 분리되었습니다 . 한편, 메타데이터 수집기 모듈 은 둘 다에 의존합니다. 이 디자인은 초기 접근 방식보다 낫지만 아직 진행 중인 작업입니다. 다음 섹션에서는 좋은 결합 방식을 기반으로 설계를 개선할 수 있는 방법을 알아보겠습니다.
3. 타이트 커플링
클래스 그룹이 서로에 대한 의존도가 높거나 많은 책임을 지는 클래스가 있는 경우를 긴밀한 결합이라고 합니다. 또 다른 시나리오는 개체가 사용을 위해 다른 개체를 만드는 경우입니다. 긴밀한 결합 코드는 유지 관리하기 어렵습니다.
따라서 기본 애플리케이션을 사용하여 이 동작을 살펴보겠습니다. 몇 가지 코드 정의를 살펴보겠습니다. 먼저 XMLFetch 클래스는 다음과 같습니다.
public class XMLFetch {
public List<Object> fetchMetadata() {
List<Object> metadata = new ArrayList<>();
// Do some stuff
return metadata;
}
}
다음으로 CSVExport 클래스:
public class CSVExport {
public File export(List<Object> metadata) {
System.out.println("Exporting data...");
// Export Metadata
File outputCSV = null;
return outputCSV;
}
}
마지막으로 MetadataCollector 클래스는 다음과 같습니다.
public class MetadataCollector {
private XMLFetch xmlFetch = new XMLFetch();
private CSVExport csvExport = new CSVExport();
public void collectMetadata() {
List<Object> metadata = xmlFetch.fetchMetadata();
csvExport.export(metadata);
}
}
알 수 있듯이 MetadataCollector 클래스는 XMLFecth 및 CSVExport 클래스 에 의존합니다 . 또한 그것들을 만드는 일을 담당합니다.
수집기를 개선해야 하는 경우 새 데이터 JSON 가져오기를 추가하고 데이터를 PDF 형식으로 내보내려면 이러한 새 요소를 클래스에 포함해야 합니다. 새로운 "개선된" 클래스를 코딩해 보겠습니다.
public class MetadataCollector {
...
private CSVExport csvExport = new CSVExport();
private PDFExport pdfExport = new PDFExport();
public void collectMetadata(int inputType, int outputType) {
if (outputType == 1) {
List<Object> metadata = null;
if (inputType == 1) {
metadata = xmlFetch.fetchMetadata();
} else {
metadata = jsonFetch.fetchMetadata();
}
csvExport.export(metadata);
} else {
List<Object> metadata = null;
if (inputType == 1) {
metadata = xmlFetch.fetchMetadata();
} else {
metadata = jsonFetch.fetchMetadata();
}
pdfExport.export(metadata);
}
}
}
Metadata Collector Module 은 새로운 기능을 처리하기 위해 몇 가지 플래그가 필요합니다. 플래그 값에 따라 각 자식 모듈이 인스턴스화됩니다. 그러나 모든 새로운 기능은 코드를 더 복잡하게 만들 뿐만 아니라 유지 관리를 더 어렵게 만듭니다. 이것은 긴밀한 결합의 신호이므로 피해야 합니다.
4. 느슨한 연결
개발 과정에서 모든 클래스 간의 관계 수는 가능한 한 최소화해야 합니다. 이것을 느슨한 결합이라고 합니다. 느슨한 결합은 개체가 외부 소스에서 사용할 개체를 가져오는 경우 입니다. 우리의 객체는 서로 독립적입니다. 느슨하게 결합된 코드는 유지 관리 노력을 줄입니다. 또한 시스템에 훨씬 더 많은 유연성을 제공합니다.
느슨한 결합은 의존성 반전 원리를 통해 표현됩니다. 다음 섹션에서 설명하겠습니다.
5. 의존성 반전 원리
DIP ( Dependency Inversion Principle )는 높은 수준의 모듈이 책임을 위해 낮은 수준의 모듈에 의존하지 않아야 하는 방법을 나타냅니다 . 둘 다 추상화에 의존해야 합니다 .
우리 디자인에서 이것이 근본적인 문제입니다. 메타데이터 수집기(상위 수준) 모듈은 XML 가져오기 및 CSV 데이터 내보내기(하위 수준) 모듈에 따라 다릅니다.
그러나 디자인을 개선하기 위해 무엇을 할 수 있습니까? DIP는 솔루션을 보여주지만 구현 방법에 대해서는 말하지 않습니다. 이 경우 IoC(Inversion of Control)가 실행됩니다. IoC는 모듈 간의 추상화를 정의하는 방법을 나타냅니다. 요약하자면 DIP를 구현하는 방식이다.
따라서 현재 예제에 DIP 및 IoC를 적용해 보겠습니다. 먼저 데이터를 가져오기 위한 인터페이스와 내보내기를 위한 인터페이스를 정의해야 합니다. 코드로 이동하여 수행 방법을 살펴보겠습니다.
public interface FetchMetadata {
List<Object> fetchMetadata();
}
간단하지 않습니까? 이제 내보내기 인터페이스를 정의합니다.
public interface ExportMetadata {
File export(List<Object> metadata);
}
또한 해당 클래스에서 이러한 인터페이스를 구현해야 합니다. 간단히 말해서 현재 클래스를 업데이트해야 합니다.
public class XMLFetch implements FetchMetadata {
@Override
public List<Object> fetchMetadata() {
List<Object> metadata = new ArrayList<>();
// Do some stuff
return metadata;
}
}
다음으로 CSVExport 클래스를 업데이트해야 합니다.
public class CSVExport implements ExportMetadata {
@Override
public File export(List<Object> metadata) {
System.out.println("Exporting data...");
// Export Metadata
File outputCSV = null;
return outputCSV;
}
}
또한 새로운 디자인 변경을 지원하기 위해 메인 모듈의 코드를 업데이트합니다. 어떻게 보이는지 봅시다:
public class MetadataCollector {
private FetchMetadata fetchMetadata;
private ExportMetadata exportMetadata;
public MetadataCollector(FetchMetadata fetchMetadata, ExportMetadata exportMetadata) {
this.fetchMetadata = fetchMetadata;
this.exportMetadata = exportMetadata;
}
public void collectMetadata() {
List<Object> metadata = fetchMetadata.fetchMetadata();
exportMetadata.export(metadata);
}
}
코드에서 두 가지 주요 변경 사항을 관찰할 수 있습니다. 첫째, 클래스는 구체적인 유형이 아닌 추상화에만 의존합니다. 반면에 저수준 모듈에 대한 의존성을 제거합니다. 수집기 모듈에 저수준 모듈 생성과 관련된 로직을 보관할 필요가 없습니다. 이러한 모듈과의 상호 작용은 표준 인터페이스를 통해 이루어집니다. 이 디자인의 장점은 데이터를 가져오고 내보내는 새 모듈을 추가할 수 있고 수집기 코드가 변경되지 않는다는 것입니다.
DIP 및 IoC를 적용하여 시스템 설계를 개선합니다. 컨트롤을 반전(변경)하면 애플리케이션이 분리되고, 테스트 가능하고, 확장 가능하고, 유지보수 가능하게 됩니다. 다음 이미지는 현재 디자인이 어떻게 보이는지 보여줍니다.
마지막으로 코드 기반에서 밀접하게 결합된 코드를 제거하고 IoC와 함께 DIP를 사용하여 느슨하게 결합된 코드로 초기 설계를 개선합니다.
6. 결론
이 기사에서는 Java의 커플링에 대해 설명했습니다. 먼저 일반적인 커플링 정의를 살펴보았습니다. 그런 다음 단단히 결합과 느슨하게 결합의 차이점을 관찰했습니다. 나중에 우리는 느슨하게 결합된 코드를 얻기 위해 IoC와 함께 DIP를 적용하는 방법을 배웠습니다. 이 연습은 예제 디자인을 따라 수행되었습니다. 좋은 디자인 패턴을 적용하여 각 단계에서 코드가 어떻게 개선되었는지 관찰할 수 있었습니다.
평소와 같이 GitHub에서 코드를 사용할 수 있습니다 .