비즈니스 객체 클래스 디자인의“완전한 공개”사고 방식에 반박하는 방법


9

우리는 비즈니스 객체에 대해 많은 단위 테스트 및 리팩토링을 수행하고 있으며 다른 동료와 클래스 디자인에 대한 의견 이 매우 다른 것 같습니다 .

내가 팬이 아닌 예제 클래스 :

public class Foo
{
    private string field1;
    private string field2;
    private string field3;
    private string field4;
    private string field5;

    public Foo() { }

    public Foo(string in1, string in2)
    {
        field1 = in1;
        field2 = in2;
    }

    public Foo(string in1, string in2, string in3, string in4)
    {
        field1 = in1;
        field2 = in2;
        field3 = in3;
    }

    public Prop1
    { get { return field1; } }
    { set { field1 = value; } }

    public Prop2
    { get { return field2; } }
    { set { field2 = value; } }

    public Prop3
    { get { return field3; } }
    { set { field3 = value; } }

    public Prop4
    { get { return field4; } }
    { set { field4 = value; } }

    public Prop5
    { get { return field5; } }
    { set { field5 = value; } }

}

"실제"클래스에서는 모두 문자열이 아니지만 경우에 따라 완전히 공용 속성을위한 30 개의 백업 필드가 있습니다.

나는 이 수업이 싫고 내가 까다로운 것인지 모르겠다. 몇 가지 참고 사항 :

  • 속성에 로직이없는 개인 백업 필드는 불필요하며 클래스를 팽창시킵니다.
  • 여러 생성자 (약간 괜찮지 만)와 결합
  • 공개 세터가있는 모든 속성, 나는 팬이 아닙니다.
  • 비어있는 생성자로 인해 속성에 값이 할당되지 않을 수 있습니다. 호출자가 알지 못하면 잠재적으로 매우 원치 않는 동작을 테스트하기 어려울 수 있습니다.
  • 너무 많은 속성입니다! (30 건)

구현 자로서 주어진 시간에 객체의 상태 를 실제로 아는 것이 훨씬 더 어렵다는 것을 알았 습니다 Foo. " Prop5객체를 만들 때 필요한 정보가 없을 수도 있습니다 . 알겠습니다. 이해가 되겠지만 Prop5세터 만 공개하면 클래스의 속성은 항상 30 개까지는 아닙니다."

나는 "쓰기 쉬운 (모든 사람이 쓰는)"과는 반대로 "사용하기 쉬운"클래스를 원한다는 점에서 엄청나거나 까다로운가요? 위와 같은 수업이 나에게 비명을 지르며, 이것이 어떻게 사용 될지 모르겠 기 때문에 모든 경우를 대비하여 공개 할 것 입니다.

내가 엄청나게 까다 롭지 않다면, 이런 생각에 맞서기위한 좋은 주장은 무엇입니까? 나는 의도적으로 논쟁하지 않고 논란의 여지가 많기 때문에 논쟁을 잘 표현하지 못한다.



3
객체는 항상 유효한 상태 여야합니다. 즉, 부분적으로 인스턴스화 된 객체 를 가질 필요는 없습니다 . 다른 정보가있는 동안 일부 정보는 객체의 핵심 상태에 포함되지 않습니다 (예 : 테이블에는 다리가 있어야하지만 천은 선택 사항 임). 선택적 정보는 인스턴스화 후 제공 될 수 있으며 핵심 정보는 인스턴스화시 제공되어야합니다. 일부 정보가 핵심이지만 인스턴스화에 알려지지 않은 경우 클래스 리팩토링이 필요하다고 말합니다. 수업의 맥락을 모르고 더 많은 것을 말하기는 어렵습니다.
Marvin

1
"개체를 만들 때 Prop5를 설정하는 데 필요한 정보가 없을 수도 있습니다"라고 말하면 "건축 할 때와 그렇지 않을 때 다른 시간에 정보를 갖게 될 것입니다. 그 정보가 있습니까? " 실제로이 한 클래스가 나타내는 두 가지 다른 것이 있는지 또는이 클래스를 더 작은 클래스로 나눌 것인지 궁금합니다. 그러나 만약 그것이 "만약"이라면, 그것은 또한 나쁘다. IMO; 그것은 객체가 어떻게 생성되고 채워지는지에 대한 명확한 디자인이 없다고 말합니다.
윌리 박사의 견습생

3
속성에 로직이없는 개인 백업 필드의 경우 자동 속성을 사용하지 않는 이유가 있습니까?
Carson63000

2
@Kritner 자동 속성의 요점은 미래에 실제 논리가 필요한 경우 자동 get또는 set:-) 대신 완벽하게 추가 할 수 있다는 것입니다.
Carson63000

답변:


10

완전 공개 클래스는 특정 상황뿐만 아니라 하나의 공개 메소드 (그리고 아마도 많은 개인 메소드)를 가진 다른 극단적 클래스에 대한 정당성을 가지고 있습니다. 그리고 공개, 비공개 메소드가있는 클래스.

그것은 모두 당신이 그것들로 모델링 할 추상화의 종류, 시스템에있는 레이어, 다른 레이어에 필요한 캡슐화의 정도, 그리고 수업의 저자가 오는 학교가 무엇인지에 달려 있습니다. 에서. SOLID 코드에서 이러한 유형을 모두 찾을 수 있습니다.

언제 어떤 종류의 디자인을 선호해야하는지에 관해 쓰여진 책들이 많이 있으므로 여기서는 어떤 규칙도 나열하지 않겠습니다.이 섹션의 공간은 충분하지 않습니다. 그러나 클래스를 사용하여 모델링하려는 추상화에 대한 실제 사례가 있다면 여기의 커뮤니티가 디자인 개선에 행복하게 도움이 될 것입니다.

다른 요점을 해결하려면 :

  • "속성에 로직이없는 개인 백업 필드": 그렇습니다. 사소한 게터와 세터에게는 이것이 불필요한 "노이즈"입니다. 이러한 종류의 "부풀림"을 피하기 위해 C #에는 속성 get / set 메서드에 대한 간단한 구문이 있습니다.

그래서 대신

   private string field1;
   public string Prop1
   { get { return field1; } }
   { set { field1 = value; } }

쓰다

   public string Prop1 { get;set;}

또는

   public string Prop1 { get;private set;}
  • "다중 생성자": 그 자체로는 문제가되지 않습니다. 예제에 표시된 것처럼 불필요한 코드 중복이 있거나 호출 계층이 복잡하면 문제가 발생합니다. 공통 부분을 별도의 함수로 리팩토링하고 생성자 체인을 단방향으로 구성하여 쉽게 해결할 수 있습니다.

  • "빈 생성자로 인해 속성에 값이 할당되지 않을 것입니다.": C #에서 모든 데이터 유형에는 명확하게 정의 된 기본값이 있습니다. 생성자에서 속성이 명시 적으로 초기화되지 않으면이 기본값이 할당됩니다. 이것이 의도적 으로 사용된다면 완벽하게 괜찮아요. 저자가 자신이하는 일을 알고 있다면 빈 생성자가 괜찮을 것입니다.

  • "너무 많은 속성입니다! (30의 경우)": 예, 만약 당신이 그린 필드 방식으로 그러한 클래스를 자유롭게 디자인한다면, 30은 너무 많습니다, 나는 동의합니다. 그러나 우리 모두 가이 사치를 누릴 수있는 것은 아닙니다 (아래의 의견에 레거시 시스템이라고 쓰지 않습니까?). 때로는 기존 데이터베이스, 파일 또는 써드 파티 API의 데이터에서 시스템으로 레코드를 맵핑해야합니다. 따라서이 경우 30 가지 속성이 있어야 할 수도 있습니다.


감사합니다. 예, POCO / POJO가 그 자리를 차지하고 있다는 것을 알고 있습니다. 그러나 소설을 보지 않고 더 구체적으로 얻을 수 있다고 생각하지 않습니다.
Kritner

@ 크리 트너 : 내 편집 참조
Doc Brown

예, 보통 새로운 클래스를 만들 때 자동 속성 경로를 사용합니다. 개인 지원 필드를 "가장 생각하기 때문에"필요하지 않으며 문제가 발생하기도합니다. 클래스에 ~ 30 개의 속성이 있고 100 개 이상의 클래스에 속성 설정이 있거나 잘못된 백킹 필드에서 가져올 것이라고 말하는 것은 그리 멀지 않습니다. 사실 리팩토링에서 이미 여러 번 발생했습니다. :)
Kritner

감사합니다, 문자열의 기본값 null은 그렇지 않습니까? 따라서 실제로에 사용 .ToUpper()되지만 값이 설정되지 않은 속성 중 하나가 실행되면 예외가 발생합니다. 이것은 왜 또 다른 좋은 예입니다 필요한 클래스에 대한 데이터가 있어야 객체 생성시 설정해야합니다. 사용자에게 맡기는 것이 아닙니다. 감사
Kritner

설정되지 않은 속성이 너무 많을뿐만 아니라이 클래스의 주된 문제는 논리가 0이며 (ZRP) 빈혈 모델의 원형입니다. DTO와 같이 단어로 표현할 수있는이 기능이 필요하지 않으면 디자인이 좋지 않습니다.
user949300

2
  1. "속성에 로직이없는 개인 백업 필드는 불필요하게 보이고 클래스를 부 풀린다"고 말하면 이미 괜찮은 주장이 있습니다.

  2. 여러 생성자가 문제가되는 것처럼 보이지 않습니다.

  3. 모든 속성을 공개로 사용하는 것과 관련하여 ... 아마도 이런 식으로 생각할 수 있습니다. 스레드간에 이러한 모든 속성에 대한 액세스를 동기화하려는 경우 동기화 코드를 모든 곳에서 작성해야 할 수 있기 때문에 힘든 시간이 될 것입니다 익숙한. 그러나 모든 속성이 getter / setter로 묶여 있으면 클래스에 동기화를 쉽게 구축 할 수 있습니다.

  4. 나는 당신이 "행동을 테스트하기 어렵다"고 말할 때 그 주장이 그 자체를 의미한다고 생각합니다. 테스트에서 null이 아닌지 확인해야 할 수도 있습니다 (속성이 사용되는 모든 단일 위치). 테스트 / 응용 프로그램의 모양을 모르기 때문에 잘 모르겠습니다.

  5. 너무 많은 속성이 올바른 것입니다. 상속을 사용하여 (속성이 비슷한 경우) 제네릭을 사용하여 목록 / 배열을 사용할 수 있습니까? 그런 다음 getter / setter를 작성하여 정보에 액세스하고 모든 속성의 상호 작용 방식을 단순화 할 수 있습니다.


1
고마워-# 1과 # 4 나는 확실히 내 주장을 가장 편안하게 느낀다. # 3의 의미가 확실하지 않습니다. # 5 클래스의 대부분 의 속성은 db의 기본 키를 구성합니다. 우리는 경쟁 키, 때로는 최대 8 열을 사용하지만 이는 또 다른 문제입니다. 나는 (적어도 특성을 많이 제거하는 것 자체 클래스 / 구조체에 키를 구성하는 부품을 넣어 노력에 대해 생각했다 클래스) - 그러나 이것은 여러과 코드 라인의 수천 레거시 응용 프로그램입니다 발신자. 그래서 나는 생각해야 할 것이 많다고 생각합니다 :)
Kritner

뭐라고. 클래스가 변경 불가능한 경우 클래스 내부에 동기화 코드를 작성할 이유가 없습니다.
RubberDuck

@RubberDuck이 클래스는 불변입니까? 무슨 소리 야?
Snoop

3
확실히입니다 하지 불변의 @StevieV. 속성 설정자가있는 모든 클래스는 변경 가능합니다.
RubberDuck

1
@Kritner 속성은 어떻게 기본 키로 사용됩니까? 아마도 일부 논리 (널 체크) 또는 규칙 (예 : NAME이 AGE보다 우선)이 필요합니다. OOP에 따르면이 코드는이 클래스에 속합니다. 그것을 인수로 사용할 수 있습니까?
user949300

2

당신이 말했듯 Foo이, 비즈니스 객체입니다. 도메인에 따라 메소드의 일부 동작을 캡슐화해야하며 데이터는 가능한 한 캡슐화되어 있어야합니다.

당신이 보여주는 예에서 Foo, DTO 처럼 보이고 모든 OOP 원칙에 어긋납니다 ( 빈혈증 도메인 모델 참조 )!

개선 사항으로 제안합니다.

  • 이 클래스를 가능한 한 불변 으로 만듭니다. 당신이 말했듯이, 당신은 몇 가지 단위 테스트를하고 싶습니다. 단위 테스트를위한 필수 기능 중 하나 는 결정 성 이며, 부작용 문제를 해결하기 때문에 불변성을 통해 결정 성을 얻을 수 있습니다 .
  • 신 클래스 를 각각 하나만 수행하는 여러 클래스 로 나눕니다 ( SRP 참조 ). 실제로 PITA 에서 30 개의 속성 클래스를 단위 테스트합니다 .
  • 하나의 기본 생성자 와 다른 생성자가 기본 생성자를 호출하여 생성자 중복성을 제거하십시오 .
  • 불필요한 게터를 제거하십시오. 모든 게터가 실제로 유용하다고 의심합니다.
  • 비즈니스 클래스에서 비즈니스 로직을 다시 가져 오십시오.

이것은 다음과 같은 말로 잠재적으로 리팩토링 된 클래스가 될 수 있습니다.

public sealed class Foo
{
    private readonly string field1;
    private readonly string field2;
    private readonly string field3;
    private readonly string field4;

    public Foo() : this("default1", "default2")
    { }

    public Foo(string in1, string in2) : this(in1, in2, "default3", "default4")
    { }

    public Foo(string in1, string in2, string in3, string in4)
    {
        field1 = in1;
        field2 = in2;
        field3 = in3;
        field4 = in4;
    }

    //Methods with business logic

    //Really needed ?
    public Prop1
    { get; }

    //Really needed ?
    public Prop2
    { get; }

    //Really needed ?
    public Prop3
    { get; }

    //Really needed ?
    public Prop4
    { get; }

    //Really needed ?
    public Prop5
    { get; }
}

2

비즈니스 객체 클래스 디자인의“완전한 공개”사고 방식에 반박하는 방법

  • "클라이언트 클래스에는 '단순한 DTO 의무'이상의 작업을 수행하는 여러 가지 방법이 있습니다.
  • " 'DTO'일 수도 있지만 비즈니스 아이덴티티가 있습니다. Equals를 무시해야합니다."
  • "우리는 이것을 정렬해야합니다. IComparable을 구현해야합니다."
  • "우리는 이러한 속성 중 몇 가지를 함께 묶고 있습니다. 모든 클라이언트가이 코드를 작성할 필요가 없도록 ToString을 재정의하겠습니다."
  • "우리는이 클래스에 대해 동일한 작업을 수행하는 여러 클라이언트가 있습니다. 코드를 건조시켜야합니다."
  • "클라이언트는 이러한 것들의 콜렉션을 조작하고있다. 커스텀 콜렉션 클래스 여야한다는 것은 명백하다. 초기의 많은 점들이 그 커스텀 콜렉션을 현재보다 기능적으로 만들 것이다."
  • "지루하고 노출 된 모든 문자열 조작을보십시오! 비즈니스 관련 방법으로 캡슐화하면 코딩 생산성이 향상됩니다."
  • "이러한 메소드를 클래스로 함께 가져 오면 더 복잡한 클라이언트 클래스를 처리 할 필요가 없기 때문에 테스트 할 수 있습니다."
  • "이제 리팩토링 된 메소드를 단위 테스트 할 수 있습니다. x, y, z 이유로 클라이언트는 테스트 할 수 없습니다.

  • 위의 주장 중 하나라도 종합적으로 제시 할 수 있습니다.

    • 기능을 재사용된다.
    • 건조하다
    • 클라이언트와 분리되어 있습니다.
    • 클래스에 대한 코딩은 더 빠르고 오류가 덜 발생합니다.
  • "잘 수행 된 클래스는 상태를 숨기고 기능을 노출합니다. 이것은 OO 클래스를 설계하기위한 시작 제안입니다."
  • "최종 결과는 비즈니스 영역, 별개의 엔티티 및 명시 적 상호 작용을 표현하는 데 훨씬 더 효과적입니다."
  • "이 '오버 헤드'기능 (즉, 명시적인 기능 요구 사항은 아니지만 완료되어야 함)은 명백히 단일 책임 원칙을 준수합니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.