캡슐화 남용으로 고통 받습니까?


11

코드 냄새가 나고 나쁜 것으로 보이는 다양한 프로젝트에서 내 코드의 무언가를 발견했지만 처리 할 수 ​​없습니다.

"깨끗한 코드"를 작성하는 동안 코드를보다 쉽게 ​​읽을 수 있도록 개인 메서드를 과도하게 사용하는 경향이 있습니다. 문제는 코드가 실제로 깨끗하지만 테스트하기가 더 어렵다는 것입니다 (예, 개인 메서드를 테스트 할 수 있다는 것을 알고 있습니다 ...). 일반적으로 나에게 나쁜 습관 인 것 같습니다.

다음은 .csv 파일에서 일부 데이터를 읽고 고객 그룹 (다양한 필드와 속성을 가진 다른 개체)을 반환하는 클래스의 예입니다.

public class GroupOfCustomersImporter {
    //... Call fields ....
    public GroupOfCustomersImporter(String filePath) {
        this.filePath = filePath;
        customers = new HashSet<Customer>();
        createCSVReader();
        read();
        constructTTRP_Instance();
    }

    private void createCSVReader() {
        //....
    }

    private void read() {
        //.... Reades the file and initializes the class attributes
    }

    private void readFirstLine(String[] inputLine) {
        //.... Method used by the read() method
    }

    private void readSecondLine(String[] inputLine) {
        //.... Method used by the read() method
    }

    private void readCustomerLine(String[] inputLine) { 
        //.... Method used by the read() method
    }

    private void constructGroupOfCustomers() {
        //this.groupOfCustomers = new GroupOfCustomers(**attributes of the class**);
    }

    public GroupOfCustomers getConstructedGroupOfCustomers() {
        return this.GroupOfCustomers;
    }
}

알 수 있듯이 클래스에는 작업을 수행하기 위해 일부 개인 메소드를 호출하는 생성자 만 있음을 알 수 있지만 일반적으로 좋은 방법 은 아니지만 어떤 경우에 메소드를 공개하지 않고 클래스의 모든 기능을 캡슐화하는 것을 선호합니다 클라이언트는 다음과 같이 작동해야합니다.

GroupOfCustomersImporter importer = new GroupOfCustomersImporter(filepath)
importer.createCSVReader();
read();
GroupOfCustomer group = constructGoupOfCustomerInstance();

클라이언트 측 코드에 쓸모없는 코드 줄을 구현 세부 사항으로 클라이언트 클래스를 괴롭 히고 싶지 않기 때문에 이것을 선호합니다.

그래서 이것은 실제로 나쁜 습관입니까? 그렇다면 어떻게 피할 수 있습니까? 위의 내용은 간단한 예입니다. 조금 더 복잡한 상황에서 같은 상황이 발생한다고 상상해보십시오.

답변:


17

클라이언트에서 구현 세부 정보를 숨기려는 한 실제로 올바른 길을 가고 있다고 생각합니다. 클라이언트가 보는 인터페이스가 가장 단순하고 간결한 API가되도록 클래스를 설계하려고합니다. 클라이언트는 구현 세부 정보를 "걱정하지"않을뿐만 아니라 해당 코드의 호출자를 수정해야한다는 걱정없이 기본 코드를 리팩터링 할 수 있습니다. 서로 다른 모듈 간의 커플 링을 줄이면 실질적인 이점이 있으며이를 위해 반드시 노력해야합니다.

따라서 방금 제공 한 조언을 따르면 코드에서 이미 발견 한 것, 공용 인터페이스 뒤에 숨겨져 있고 쉽게 액세스 할 수없는 모든 논리가 생깁니다.

이상적으로는 공용 인터페이스에 대해서만 클래스를 단위 테스트 할 수 있어야하며 외부 종속성이있는 경우 테스트중인 코드를 격리하기 위해 가짜 / 모의 / 스텁 객체를 도입해야 할 수도 있습니다.

그러나 이렇게해도 여전히 수업의 모든 부분을 쉽게 테스트 할 수 없다고 느끼면 한 수업이 너무 많은 것일 가능성이 큽니다. SRP 원칙 의 정신에 따라 Michael Feathers 가 "Sprout Class Pattern"이라고 부르는 것을 따라 원래 클래스 덩어리를 새 클래스로 추출 할 수 있습니다.

당신의 예제에는 텍스트 파일을 읽는 책임이있는 importer 클래스가 있습니다. 옵션 중 하나는 파일 읽기 코드 청크를 별도의 InputFileParser 클래스로 추출하는 것입니다. 이제 모든 개인 기능이 공개되므로 쉽게 테스트 할 수 있습니다. 동시에 파서 클래스는 외부 클라이언트가 볼 수있는 것이 아닙니다 ( "내부"로 표시하거나 헤더 파일을 게시하지 않거나 단순히 API의 일부로 광고하지 마십시오). 인터페이스가 계속 짧고 달콤합니다.


1

많은 메소드 호출을 클래스 생성자에 넣는 대안-일반적으로 피하는 습관입니다-대신 클라이언트를 귀찮게하지 않으려는 여분의 초기화 항목을 모두 처리하는 팩토리 메소드를 작성할 수 있습니다 옆에. 즉, 메서드와 같은 것을 constructGroupOfCustomers()공용 으로 보이도록 메서드를 노출하고 클래스의 정적 메서드로 사용합니다.

GroupOfCustomersImporter importer = 
    new GroupOfCustomersImporter.CreateInstance();

또는 별도의 팩토리 클래스의 방법으로 :

ImporterFactory factory = new ImporterFactory();
GroupOfCustomersImporter importer = factory.CreateGroupOfCustomersImporter();

그것들은 내 머리 꼭대기에서 똑바로 생각한 첫 번째 옵션입니다.

또한 본능이 당신에게 무언가를 말하려고 노력하고 있음을 고려할 가치가 있습니다. 장에서 코드 냄새가 나기 시작하면 냄새가 나기 때문에 냄새가 나기 전에 무언가를하는 것이 좋습니다! 표면적으로는 코드 자체에 본질적으로 아무런 문제가 없을 수 있지만 빠른 재확인 및 리 팩터는 문제에 대한 생각을 정리하는 데 도움이되므로 문제를 해결할 때 걱정하지 않고 다른 자료로 넘어갈 수 있습니다. 잠재적 인 카드 하우스. 이 특별한 경우, 팩토리에 대한 또는 생성자가 호출해야하는 생성자의 책임 중 일부를 위임하면 클래스의 인스턴스화와 미래의 잠재적 후손에 대한 통제력을 높일 수 있습니다.


1

댓글에 너무 오래 걸리기 때문에 내 자신의 답변을 추가합니다.

캡슐화와 테스트는 매우 반대되는 것이 옳습니다. 거의 모든 것을 숨기면 테스트하기가 어렵습니다.

그러나 파일 이름 대신 스트림을 제공하여 예제를 더 테스트 가능하게 만들 수 있습니다. 이렇게하면 메모리에서 알려진 csv를 제공하거나 원하는 경우 파일을 제공 할 수 있습니다. 또한 http 지원을 추가해야 할 때 클래스를보다 강력하게 만듭니다.)

또한 lazy-init 사용을 살펴보십시오.

public class Csv
{
  private CustomerList list;

  public CustomerList get()
  {
    if(list == null)
        load();
     return list;
  }
}

1

생성자는 일부 변수 또는 객체의 상태를 초기화하는 것을 제외하고는 너무 많이해서는 안됩니다. 생성자를 너무 많이 사용하면 런타임 예외가 발생할 수 있습니다. 나는 클라이언트가 이와 같은 것을 피하려고 노력할 것이다.

MyClass a;
try {
   a = new MyClass();
} catch (MyException e) {
   //do something
}

대신에 :

MyClass a = new MyClass(); // Or a factory method
try {
   a.doSomething();
} catch (MyException e) {
   //do something
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.