2012-09-06 2 views
17

나는 다양한 이유로 디자인에서 불변의 타입을 사용하기 시작했다. 순간, 나는이 같은 기존 클래스가있는 프로젝트에 일하고 있어요 :불변의 디자인 : 생성자의 정신 이상을 다루는 것

public class IssueRecord 
{ 
    // The real class has more readable names :) 
    public string Foo { get; set; } 
    public string Bar { get; set; } 
    public int Baz { get; set; } 
    public string Prop { get; set; } 
    public string Prop2 { get; set; } 
    public string Prop3 { get; set; } 
    public string Prop4 { get; set; } 
    public string Prop5 { get; set; } 
    public string Prop6 { get; set; } 
    public string Prop7 { get; set; } 
    public string Prop8 { get; set; } 
    public string Prop9 { get; set; } 
    public string PropA { get; set; } 
} 

이 클래스는 정말이 많은 특성을 가지고 몇 가지 온 디스크 포맷을 나타내는, 그래서 작은 비트로 리팩토링 꽤입니다 이 시점에서 많은 의문을 품는다.

이 클래스의 생성자가 실제로 변경할 수없는 디자인에 13 개의 매개 변수가 있어야한다는 의미입니까? 그렇지 않다면이 디자인을 변경하지 못하게하려면 생성자에서 허용되는 매개 변수 수를 줄이기 위해 어떤 단계를 수행해야합니까?

+0

클래스 외부에서 값을 설정해야하거나 클래스 자체에서 값을 설정할 수 있습니까? 클래스가 그것을 할 수 있다면, 매개 변수없는 생성자를 가질 수 있습니다. –

+1

참조 : http://stackoverflow.com/questions/355172/how-to-design-an-immutable-object-with-complex-initialization?rq=1 – aquinas

+0

어떻게 이러한 속성을 초기화 하시겠습니까? –

답변

13

이 클래스의 생성자가 실제로 불변 디자인에 13 개의 매개 변수가 있어야한다는 의미입니까?

일반적으로 예. 13 개의 속성을 갖는 불변의 타입은 모든 값을 초기화 할 수있는 방법이 필요합니다.

모두 사용하지 않거나 다른 속성을 기반으로 일부 속성을 결정할 수있는 경우 매개 변수가 적은 하나 이상의 오버로드 된 생성자가있을 수 있습니다. 그러나 생성자 (유형이 불변인지 여부에 관계 없음)는 실제로 이어야합니다.은 유형이 논리적으로 "정확하고"완료되는 방식으로 유형에 대한 데이터를 완전히 초기화해야합니다.

이 클래스는 실제로 많은 특성을 가지고있는 디스크 내장 포맷을 나타내므로 더 작은 비트로 리팩토링하면이 시점에서 거의 문제가되지 않습니다.

"디스크상의 형식"이 런타임에 결정되는 경우 초기화 데이터 (예 : 파일 이름 등)를 가져 와서 완전히 초기화 된 팩터 리 방법이나 생성자가있을 수 있습니다 당신을 위해 타입을 입력하십시오.

2

구조체를 만들 수 있지만 구조체를 선언해야합니다. 그러나 항상 배열 등이 있습니다. 데이터 유형이 모두 동일한 경우 배열, 목록 또는 문자열과 같은 여러 가지 방법으로 그룹화 할 수 있습니다. 당신이 맞다면 보이지만 모든 불변 타입은 어떤 식 으로든 생성자를 통과해야합니다. 13 개 매개 변수를 통해 또는 구조체, 배열,리스트 등을 통해 날씨를 따라야합니다.

+0

아, 나는 그 중 대부분이 문자열이라는 사실을 깨달았습니다. 배열을 사용한다고 말하는 것이 옳았습니다. –

+2

구조체의 매개 변수의 의미를 파괴하기 때문에 배열이 실제로 작동하지 않습니다. 클래스를 사용하는 누군가는 배열의 다섯 번째 요소가 "세부 사항"을 의미하거나 클래스를 변경 가능하게 유지하는 것보다 나쁘다는 것을 기억해야합니다. –

+0

오른쪽 구조체는 유일한 실제 옵션입니다. 게다가 C#에서는 struct가 기본값을 사용할 수 있으므로 상황에 유용 할 수 있습니다. –

3

아마도 현재 클래스를 유지하십시오. 가능한 경우 현명한 기본값을 제공하고 IssueRecordOptions로 이름을 바꿉니다. 이것을 불변의 IssueRecord에 대한 단일 초기화 매개 변수로 사용하십시오.

+0

불변의 요구 사항이 시스템의 일부에만 필요하면이 옵션이 좋습니다. 그러나 원래의 "옵션"유형을 변경해야하므로 변경 가능한 유형을 처리하고 있습니다. –

3

생성자에서 명명 된 인수와 선택적 인수를 조합하여 사용할 수 있습니다. 값이 항상 다른 경우, 예, 당신은 미친 생성자가 붙어 있습니다.

14

인수의 수를 줄이려면 합리적인 세트로 그룹화 할 수 있지만 진정으로 변경 불가능한 개체를 가지려면 생성자/팩토리 메서드에서 초기화해야합니다.

일부 변형은 유창한 인터페이스로 구성하고 최종 개체를 요청할 수있는 "빌더"클래스를 만드는 것입니다. 코드의 다른 위치에 이러한 객체를 실제로 만들 계획이라면이 방법이 유용 할 것입니다. 그렇지 않으면 한 곳에서 많은 인수가 허용 될 수 있습니다.

var immutable = new MyImmutableObjectBuilder() 
    .SetProp1(1) 
    .SetProp2(2) 
    .Build(); 
0

컴파일 타임 동안 할당이 금지되는 경우 생성자 지정 및 개인 설정자에 충실해야합니다. 이 많은 단점이있다 그러나 - 당신은 내가 이런 식으로 뭔가를 제안 등

새 멤버 초기화,도 XML을 deseralization 등을 사용할 수 없습니다 : 여기

public class IssuerRecord 
    { 
     public string PropA { get; set; } 
     public IList<IssuerRecord> Subrecords { get; set; } 
    } 

    public class ImmutableIssuerRecord 
    { 
     public ImmutableIssuerRecord(IssuerRecord record) 
     { 
      PropA = record.PropA; 
      Subrecords = record.Subrecords.Select(r => new ImmutableIssuerRecord(r)); 
     } 

     public string PropA { get; private set; } 
     // lacks Count and this[int] but it's IReadOnlyList<T> is coming in 4.5. 
     public IEnumerable<ImmutableIssuerRecord> Subrecords { get; private set; } 

     // you may want to get a mutable copy again at some point. 
     public IssuerRecord GetMutableCopy() 
     { 
      var copy = new IssuerRecord 
          { 
           PropA = PropA, 
           Subrecords = new List<IssuerRecord>(Subrecords.Select(r => r.GetMutableCopy())) 
          }; 
      return copy; 
     } 
    } 

IssuerRecord 훨씬 더 설명하고 유용하다. 다른 곳으로 전달하면 쉽게 변경할 수없는 버전을 만들 수 있습니다. 불변에 대해 작동하는 코드는 읽기 전용 논리를 가져야하므로 IssuerRecord와 동일한 유형인지는 중요하지 않습니다. 개체를 래핑하는 대신 각 필드의 복사본을 만듭니다. 개체가 다른 곳에서 변경 될 수 있기 때문입니다. 그러나 특히 순차적 인 동기화 호출에는 필요하지 않을 수 있습니다. 그러나 어떤 불변 복제본을 "나중에"컬렉션에 저장하는 것이 더 안전합니다. 일부 코드에서 수정을 금지하지만 객체 상태에 대한 업데이트를받을 수있는 기능이 필요한 경우 응용 프로그램에서 래퍼가 될 수 있습니다.

var record = new IssuerRecord { PropA = "aa" }; 
if(!Verify(new ImmutableIssuerRecord(record))) return false; 

당신이 C++ 측면에서, 당신은 "IssuerRecord의 CONST"로 ImmutableIssuerRecords을 볼 수 있다고 생각합니다. 모든 자식 (Subrecords 예제)에 대한 복사본을 만들 것을 제안하는 이유는 불변 객체가 소유하고있는 객체를 보호하기 위해 여분의 조치를 취해야한다는 것입니다.

ImmutableIssuerRecord.Subrecors는 여기에서 IEnumerable이고 Count 및 this []가 부족하지만 IReadOnlyList가 4.5 버전으로 제공되며 원하는 경우 docs에서 복사하여 나중에 쉽게 마이그레이션 할 수 있습니다.

등은 Freezable로,뿐만 아니라 다른 방법이 있습니다 :

다시 코드가 덜 읽을 수있게하고, 컴파일시 보호를 제공하지 않습니다
public class IssuerRecord 
{ 
    private bool isFrozen = false; 

    private string propA; 
    public string PropA 
    { 
     get { return propA; } 
     set 
     { 
      if(isFrozen) throw new NotSupportedOperationException(); 
      propA = value; 
     } 
    } 

    public void Freeze() { isFrozen = true; } 
} 

. 그러나 객체가 정상적으로 생성 된 다음 준비가 끝나면 고정 할 수 있습니다.

작성자 패턴 또한 고려해야 할 사항이지만 내 관점에서 너무 많은 "서비스"코드를 추가합니다.

+0

XML 비 직렬화를 사용할 수 있습니다. XmlSerializer 대신 DataContractSerializer를 사용해야합니다. 그건 나에게 잘됐다. –

+0

예, 이것이 제 두 번째 제안이었습니다. 응용 프로그램이 Rube Goldberg Machine처럼 보일뿐입니다. –

관련 문제