생성자 대신 팩토리 메소드를 사용해야했습니다. 변경할 수 있고 여전히 이전 버전과 호환됩니까?


15

문제

파일 에서 데이터를 읽는 방법 DataSource을 제공 하는 클래스를 가지고 있다고 가정 해 봅시다 .ReadData.mdb

var source = new DataSource("myFile.mdb");
var data = source.ReadData();

몇 년 후, 나는 .xml파일 외에도 .mdb데이터 소스 로 파일 을 지원할 수 있기를 원합니다 . "데이터 읽기"구현은 파일 .xml.mdb파일 마다 상당히 다릅니다 . 따라서 처음부터 시스템을 설계하려면 다음과 같이 정의하십시오.

abstract class DataSource {
    abstract Data ReadData();
    static DataSource OpenDataSource(string fileName) {
        // return MdbDataSource or XmlDataSource, as appropriate
    }
}

class MdbDataSource : DataSource {
    override Data ReadData() { /* implementation 1 */ }
}

class XmlDataSource : DataSource {
    override Data ReadData() { /* implementation 2 */ }
}

Factory 메소드 패턴을 완벽하게 구현했습니다. 불행히도 DataSource라이브러리에 있으며 코드를 리팩터링하면 기존의 모든 호출이 중단됩니다.

var source = new DataSource("myFile.mdb");

라이브러리를 사용하는 다양한 클라이언트에서. 내가 왜 공장 식 방법을 사용하지 않았습니까?


솔루션

이것들은 내가 생각해 낼 수있는 해결책입니다.

  1. DataSource 생성자가 하위 유형 ( MdbDataSource또는 XmlDataSource)을 반환하도록 합니다. 그것은 내 모든 문제를 해결할 것입니다. 불행히도 C #은이를 지원하지 않습니다.

  2. 다른 이름을 사용하십시오.

    abstract class DataSourceBase { ... }    // corresponds to DataSource in the example above
    
    class DataSource : DataSourceBase {      // corresponds to MdbDataSource in the example above
        [Obsolete("New code should use DataSourceBase.OpenDataSource instead")]
        DataSource(string fileName) { ... }
        ...
    }
    
    class XmlDataSource : DataSourceBase { ... }

    그것이 코드를 이전 버전과 호환 가능하게 유지하기 때문에 결국 사용했습니다 (즉, new DataSource("myFile.mdb")여전히 작동하도록 호출 ). 결점 : 이름은 설명 그대로가 아닙니다.

  3. DataSource실제 구현을위한 "래퍼"를 만듭니다 .

    class DataSource {
        private DataSourceImpl impl;
    
        DataSource(string fileName) {
            impl = ... ? new MdbDataSourceImpl(fileName) : new XmlDataSourceImpl(fileName);
        }
    
        Data ReadData() {
            return impl.ReadData();
        }
    
        abstract private class DataSourceImpl { ... }
        private class MdbDataSourceImpl : DataSourceImpl { ... }
        private class XmlDataSourceImpl : DataSourceImpl { ... }
    }

    단점 : 모든 데이터 소스 방법 (예 ReadData:)은 상용구 코드로 라우팅해야합니다. 상용구 코드가 마음에 들지 않습니다. 중복되고 코드가 복잡합니다.

어떤 거기에 우아한 내가 놓친 것을 해결책은?


5
# 3 문제를 좀 더 자세히 설명해 주시겠습니까? 그것은 나에게 우아해 보인다. (또는 이전 버전과의 호환성을 유지하면서 얻을 수있는 우아함)
pdr

API를 게시하는 인터페이스를 정의한 다음 팩토리가 인터페이스를 구현하는 클래스의 해당 인스턴스를 작성하도록하여 이전 코드 및 새 코드 주위에 랩퍼를 작성하여 기존 메소드를 재사용합니다.
Thomas

@pdr : 1. 메소드 서명에 대한 모든 변경은 한곳에서 더 이루어져야합니다. 2. 클라이언트가 Xml 데이터 소스에서만 사용할 수있는 특정 기능에 액세스하려는 경우 불편한 Impl 클래스를 내부 및 개인으로 만들 수 있습니다. 또는 공개 할 수 있습니다. 즉, 클라이언트가 동일한 작업을 수행하는 두 가지 방법이 있습니다.
Heinzi

2
@Heinzi : 옵션 3을 선호합니다. 이것은 표준 "Facade"패턴입니다. 실제로 모든 데이터 소스 메소드를 구현 또는 일부 에 위임해야하는지 확인해야합니다 . 아마도 "DataSource"에 남아있는 일반적인 코드가 있습니까?
Doc Brown

그것은 new클래스 객체의 메소드가 아니기 때문에 부끄러운 일입니다 (따라서 클래스 자체를 메타 클래스라고하는 기술을 서브 클래스 화하고 new실제로 수행 하는 것을 제어 할 수 는 있지만) C # (또는 Java 또는 C ++)의 작동 방식은 아닙니다.
Donal Fellows

답변:


12

나는 두 번째 옵션을 변형하여 이전의 너무 일반적인 이름을 단계적으로 제거 할 수 있습니다 DataSource.

abstract class AbstractDataSource { ... } // corresponds to the abstract DataSource in the ideal solution

class XmlDataSource : AbstractDataSource { ... }
class MdbDataSource : AbstractDataSource { ... } // contains all the code of the existing DataSource class

[Obsolete("New code should use AbstractDataSource instead")]
class DataSource : MdbDataSource { // an 'empty shell' to keep old code working.
    DataSource(string fileName) { ... }
}

여기서 유일한 단점은 새 기본 클래스가 가장 명백한 이름을 가질 수 없다는 것입니다. 그 이름은 원래 클래스에 대해 이미 소유권을 주장했으며 이전 버전과의 호환성을 위해 그대로 유지되어야하기 때문입니다. 다른 모든 클래스에는 설명적인 이름이 있습니다.


1
+1, 그 질문을 읽을 때 정확히 내 마음에 왔습니다. OP의 옵션 3을 더 좋아하지만.
Doc Brown

모든 새 코드를 새 네임 스페이스에 넣으면 기본 클래스가 가장 분명한 이름을 가질 수 있습니다. 그러나 그것이 좋은 생각인지 확실하지 않습니다.
svick

기본 클래스에는 접미사 "Base"가 있어야합니다. DataSourceBase
Stephen

6

최선의 해결책은 옵션 # 3에 가까운 것입니다. DataSource대부분의 현재 상태를 유지하고 독자 부분 만 자체 클래스로 추출하십시오.

class DataSource {
    private Reader reader;

    DataSource(string fileName) {
        reader = ... ? new MdbReader(fileName) : new XmlReader(fileName);
    }

    Data ReadData() {
        return reader.next();
    }

    abstract private class Reader { ... }
    private class MdbReader : Reader { ... }
    private class XmlReader : Reader { ... }
}

이런 방식으로 중복 코드를 피하고 추가 확장 기능을 사용할 수 있습니다.


+1, 좋은 옵션. 그래도 명시 적으로 인스턴스화하여 XmlDataSource 관련 기능에 액세스 할 수 있기 때문에 옵션 2를 선호합니다 XmlDataSource.
Heinzi
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.