게터와 세터를 어떻게 피합니까?


85

나는 클래스를 디자인하는 데 어려움을 겪고 있습니다. 객체가 데이터가 아니라 동작을 노출한다는 것을 읽었습니다. 따라서 getter / setter를 사용하여 데이터를 수정하는 대신 지정된 클래스의 메소드는 "동사"또는 오브젝트에서 작동하는 조치 여야합니다. 예를 들어, '계정'객체에, 우리는 방법이있을 것입니다 Withdraw()Deposit()보다는 setAmount()등을 참조하십시오 : getter와 setter 메소드가 악 이유 .

예를 들어, Name, DOB, Tel, Address 등과 같이 고객에 대한 많은 정보를 유지하는 Customer 클래스가 주어지면 어떻게 모든 속성을 가져오고 설정하기 위해 getter / setter를 피할 수 있습니까? 모든 데이터를 채우기 위해 어떤 '동작'유형 방법을 쓸 수 있습니까?




8
나는 당신이 처리해야하는 경우 지적 것 자바 콩 사양 , 당신은 것입니다 getter 및 setter를 가진합니다. 많은 것들이 Java Beans (jsps의 표현 언어)를 사용하며 그러한 것을 피하려고 시도하는 것은 어려울 것입니다.

4
... 그리고 MichaelT의 반대 관점에서 : JavaBeans 사양을 사용하지 않는 경우 (필요한 컨텍스트에서 객체를 사용하지 않는 한 개인적으로 피해야한다고 생각합니다.) "get"은 getter에 대해, 특히 해당 setter가없는 특성에 대해 사마귀입니다. 호출 name()된 메소드가 호출 된 Customer메소드보다 명확하거나 명확 하다고 생각합니다 getName().
Daniel Pryden

2
@Daniel Pryden는 : 이름은 () 모두 설정하거나 얻을 의미 할 수있다 ...?
IntelliData

답변:


55

꽤 많은 답변과 의견에서 언급했듯이 DTO 일부 상황에서, 특히 경계를 넘어 데이터를 전송하는 경우 (예 : 웹 서비스를 통해 JSON으로 직렬화)에 적합하고 유용합니다. 이 답변의 나머지 부분에서는 도메인 클래스를 무시하고 도메인 클래스와 게터와 세터를 최소화 (제거하지 않을 경우)하도록 설계하고 대규모 프로젝트에서 여전히 유용하게 사용할 수있는 방법에 대해 이야기하겠습니다. 또한 게터 나 세터를 제거 해야하는지 , 언제 그렇게 해야하는지에 대해서는 이야기하지 않을 것 입니다.

예를 들어, 프로젝트가 체스 또는 전함과 같은 보드 게임이라고 가정하십시오. 프리젠 테이션 레이어 (콘솔 앱, 웹 서비스, GUI 등)에서이를 나타내는 다양한 방법이있을 수 있지만 핵심 도메인도 있습니다. 당신이 가질 수있는 한 클래스 Coordinate는 보드의 위치를 ​​나타냅니다. 그것을 쓰는 "악"방법은 다음과 같습니다.

public class Coordinate
{
    public int X {get; set;}
    public int Y {get; set;}
}

(저는 간결하게하기 위해 Java 대신 C #으로 코드 예제를 작성하려고합니다. 더 익숙해지기를 바랍니다. 문제가되지 않기를 바랍니다. 개념은 같고 번역은 간단해야합니다.)

세터 제거 : 불변성

퍼블릭 게터와 세터는 모두 잠재적으로 문제가 있지만 세터는 둘 중 훨씬 더 "악"입니다. 또한 일반적으로 제거하기가 더 쉽습니다. 프로세스는 생성자 내에서 값을 설정하는 간단한 것입니다. 이전에 객체를 변경 한 메소드는 대신 새로운 결과를 반환해야합니다. 그래서:

public class Coordinate
{
    public int X {get; private set;}
    public int Y {get; private set;}

    public Coordinate(int x, int y)
    {
        X = x;
        Y = y;
    }
}

이 클래스는 X와 Y를 변경하는 클래스의 다른 메소드로부터 보호되지 않습니다. 더 엄격하게 변경할 수 없도록 readonly( finalJava에서) 사용할 수 있습니다 . 그러나 속성을 불변으로 만들거나 세터를 통한 직접적인 공개 변이를 막는 방법은 퍼블릭 세터를 제거하는 비결입니다. 대부분의 상황에서 이것은 잘 작동합니다.

게터 제거, 1 부 : 동작 설계

위의 내용은 세터 모두에게 좋고 좋지만 게터 측면에서 실제로 시작하기 전에 실제로 발을 맞았습니다. 우리의 프로세스는 좌표가 무엇인지, 그것이 나타내는 데이터 를 생각 하고 그 주위에 클래스를 만드는 것이 었습니다. 대신 좌표에서 필요한 동작으로 시작 해야합니다. 그건 그렇고,이 과정은 TDD가 지원합니다 .TDD는 필요한 클래스가 필요할 때만 클래스를 추출하므로 원하는 동작으로 시작하여 거기서부터 작업합니다.

먼저 Coordinate충돌 감지가 필요한 곳이라고 가정 해 봅시다 . 두 조각이 보드의 동일한 공간을 차지하는지 확인하고 싶었습니다. 다음은 "이블"방식입니다 (간결하게하기 위해 생성자가 생략 됨).

public class Piece
{
    public Coordinate Position {get; private set;}
}

public class Coordinate
{
    public int X {get; private set;}
    public int Y {get; private set;}
}

    //...And then, inside some class
    public bool DoPiecesCollide(Piece one, Piece two)
    {
        return one.X == two.X && one.Y == two.Y;
    }

그리고 여기 좋은 방법이 있습니다 :

public class Piece
{
    private Coordinate _position;
    public bool CollidesWith(Piece other)
    {
        return _position.Equals(other._position);
    }
}

public class Coordinate
{
    private readonly int _x;
    private readonly int _y;
    public bool Equals(Coordinate other)
    {
        return _x == other._x && _y == other._y;
    }
}

( IEquatable단순화를 위해 구현이 간략화 됨). 데이터를 모델링하는 대신 행동을 설계함으로써 게터를 제거했습니다.

이것은 귀하의 예와도 관련이 있습니다. ORM을 사용 중이거나 웹 사이트 또는 기타 정보에 고객 정보를 표시 할 수 있습니다.이 경우 어떤 종류의 CustomerDTO가 적합 할 수 있습니다. 그러나 시스템에 고객이 포함되어 있고 데이터 모델에 고객이 있다고해서 Customer도메인에 클래스 가 있다는 것을 자동으로 의미하지는 않습니다 . 어쩌면 행동을 디자인 할 때 나타날 수도 있지만, 게터를 피하려면 미리 선제 적으로 만들지 마십시오.

게터 제거, 2 부 : 외부 행동

그래서 위의 좋은 시작이지만, 조만간 당신은 아마 당신이 어떤 방법으로 클래스의 상태에 의존하는 클래스와 연관된 동작을 상황에 실행되지만 어떤 속하지 않는 에서 클래스. 이러한 종류의 동작은 일반적으로 응용 프로그램 의 서비스 계층 에 존재합니다.

우리의 Coordinate예를 들어, 결국 당신은 게임을 사용자에게 표현하고 싶을 것입니다. 예를 들어, Vector2화면에서 점을 나타내는 데 사용하는 UI 프로젝트가있을 수 있습니다 . 그러나 Coordinate수업에서 좌표에서 화면상의 지점으로의 변환을 담당 하는 것은 부적절 할 것입니다. 이로 인해 모든 종류의 프리젠 테이션 문제가 핵심 영역에 포함됩니다. 불행히도 이러한 유형의 상황은 OO 디자인에 내재되어 있습니다.

가장 일반적으로 선택되는 첫 번째 옵션 은 지독한 게터를 노출시키고 지옥과 대화하는 것입니다. 이것은 단순성의 이점이 있습니다. 그러나 우리가 게터를 피하는 것에 대해 이야기하고 있기 때문에 논증을 위해 이것을 거부하고 다른 옵션이 있는지 봅시다.

두 번째 옵션.ToDTO()클래스에 어떤 종류의 메소드 를 추가하는 것입니다 . 예를 들어 게임을 저장하려면 거의 모든 상태를 캡처해야합니다. 그러나 서비스를 위해이 작업을 수행하고 게터에 직접 액세스하는 것의 차이점은 다소 미적입니다. 그것은 여전히 ​​많은 "악"을 가지고 있습니다.

두 번째 Pluralsight 비디오에서 Zoran Horvat 가 옹호 한 세 번째 옵션 은 방문자 패턴의 수정 된 버전을 사용하는 것입니다. 이것은 패턴의 매우 특이한 사용 및 변형이며 사람들의 마일리지는 실제 이득이없는 복잡성을 추가하는지 또는 상황에 대한 훌륭한 타협인지에 따라 크게 달라질 것이라고 생각합니다. 아이디어는 기본적으로 표준 방문자 패턴을 사용하는 것이지만 Visit메소드가 방문하는 클래스 대신 필요한 상태를 매개 변수로 사용하도록합니다. 예는 여기 에서 찾을 수 있습니다 .

우리 문제의 경우이 패턴을 사용하는 솔루션은 다음과 같습니다.

public class Coordinate
{
    private readonly int _x;
    private readonly int _y;

    public T Transform<T>(IPositionTransformer<T> transformer)
    {
        return transformer.Transform(_x,_y);
    }
}

public interface IPositionTransformer<T>
{
    T Transform(int x, int y);
}

//This one lives in the presentation layer
public class CoordinateToVectorTransformer : IPositionTransformer<Vector2>
{
    private readonly float _tileWidth;
    private readonly float _tileHeight;
    private readonly Vector2 _topLeft;

    Vector2 Transform(int x, int y)
    {
        return _topLeft + new Vector2(_tileWidth*x + _tileHeight*y);
    }
}

당신은 아마 말할 수로 _x_y되지 않습니다 정말 더 이상 캡슐화. IPositionTransformer<Tuple<int,int>>그냥 직접 반환 하는 것을 만들어서 추출 할 수 있습니다. 취향에 따라 운동이 무의미하다고 느낄 수 있습니다.

그러나 공개 게터를 사용하면 데이터를 직접 꺼내서 Tell, Do n't Ask 을 위반하여 데이터를 잘못 사용하여 잘못된 방식으로 작업하는 것이 매우 쉽습니다 . 이 패턴을 사용하는 것이 실제로 올바른 방법으로 수행하는 것이 더 간단 합니다. 동작을 만들려면 연관된 유형을 만들어 자동으로 시작합니다. TDA 위반은 매우 명백하게 냄새가 나며 아마도 더 간단하고 더 나은 해결책을 찾아야 할 것입니다. 실제로, 이러한 요점은 게터가 권장하는 "악한"방법보다 OO를 올바르게 수행하는 것이 훨씬 쉽습니다.

마지막으로 , 처음에는 명확하지 않더라도 실제로 는 상태를 노출 할 필요를 피하기 위해 행동으로 필요한 것을 충분히 노출시키는 방법이있을 수 있습니다 . 예를 들어 Coordinate공개 멤버 만있는 이전 버전 Equals()(실제로는 전체 IEquatable구현 이 필요함 )을 사용하여 프리젠 테이션 계층에 다음 클래스를 작성할 수 있습니다.

public class CoordinateToVectorTransformer
{
    private Dictionary<Coordinate,Vector2> _coordinatePositions;

    public CoordinateToVectorTransformer(int boardWidth, int boardHeight)
    {
        for(int x=0; x<boardWidth; x++)
        {
            for(int y=0; y<boardWidth; y++)
            {
                _coordinatePositions[new Coordinate(x,y)] = GetPosition(x,y);
            }
        }
    }

    private static Vector2 GetPosition(int x, int y)
    {
        //Some implementation goes here...
    }

    public Vector2 Transform(Coordinate coordinate)
    {
        return _coordinatePositions[coordinate];
    }
}

놀랍게도, 목표를 달성하기 위해 좌표에서 실제로 필요한 모든 행동 은 평등 검사였습니다! 물론이 솔루션은이 문제에 맞게 조정되었으며 수용 가능한 메모리 사용량 / 성능에 대한 가정을합니다. 일반적인 솔루션의 청사진이 아니라이 특정 문제 영역에 맞는 예제 일뿐입니다.

그리고 다시 말하지만 실제로 이것이 불필요한 복잡성인지에 따라 의견이 달라질 것입니다. 어떤 경우에는 이와 같은 해결책이 존재하지 않거나 엄청나게 이상하거나 복잡 할 수 있습니다.이 경우 위의 세 가지로 되돌릴 수 있습니다.


아름답게 대답했습니다! 동의하지만 먼저 몇 가지 의견을 듣고 싶습니다. 나는 toDTO ()가 훌륭하다고 생각합니다 .bcuz ur는 get / set에 액세스하지 않으므로 기존 코드를 깨지 않고 DTO에 주어진 필드를 변경할 수 있습니다. 2. 고객이 엔티티로 만드는 것을 정당화 할 수있는 충분한 행동이 있다고 말하십시오. 주소 / 전화 변경 등을 위해 소품에 어떻게 접근 할 수
IntelliData

@IntelliData 1. "필드 변경"이라고 말하면 클래스 정의를 변경하거나 데이터를 변경한다는 의미입니까? 후자는 공개 세터를 제거하지만 게터를 남겨두면 피할 수 있으므로 dto 측면은 관련이 없습니다. 전자는 게터가 "악한"이유가 아니다. 예를 들어 programmers.stackexchange.com/questions/157526/… 을 참조하십시오 .
Ben Aaronson

@IntelliData 2. 이것은 행동을 알지 못하면 대답하기가 어렵습니다. 그러나 아마도 대답은 : 당신은하지 않을 것입니다. Customer전화 번호를 변경할 수있는 클래스의 행동은 무엇입니까 ? 아마도 고객의 전화 번호가 변경되어 데이터베이스에서 해당 변경 사항을 유지해야하지만 그 중 어느 것도 동작 제공 도메인 객체의 책임이 아닙니다. 이는 데이터 액세스 문제이며 DTO 및 저장소와 같이 처리 될 수 있습니다.
Ben Aaronson

@IntelliData Customer도메인 객체의 데이터를 db와 동기화하여 비교적 최신 상태로 유지하는 것은 수명주기 관리의 문제이며, 이는 또한 자체 책임이 아니며 저장소 또는 공장 또는 IOC 컨테이너 또는 무엇이든 인스턴스화 Customer합니다.
Ben Aaronson

2
나는 행동을위한 디자인 개념 정말 좋아합니다 . 이것은 기본 "데이터 구조"를 알려주고 너무 일반적인 빈약하고 사용하기 어려운 클래스를 피하는 데 도움이됩니다. 하나 추가.
radarbob 2016 년

71

setter를 피하는 가장 간단한 방법 은 객체 new올릴 때 값을 생성자 메서드에 전달 하는 것입니다. 이것은 객체를 불변 으로 만들고 싶을 때 일반적인 패턴 입니다. 즉, 현실에서 상황이 항상 명확하지는 않습니다.

방법이 행동에 관한 것이어야합니다. 그러나 고객과 같은 일부 개체는 주로 정보보유 하기 위해 존재 합니다. 그것들은 게터와 세터로부터 가장 많은 혜택을받는 객체들입니다. 그러한 방법을 전혀 필요로하지 않았다면, 우리는 그 방법들을 완전히 제거 할 것입니다.

게터와 세터가 정당화 될 때 더 읽을 거리


3
왜 'Getter / Setters'에 대한 과대 광고가 사악합니까?
IntelliData

46
더 잘 알고 있어야하는 소프트웨어 개발자의 평범한 위조. 링크 된 기사의 경우, 저자는 "getters and setters are evil"라는 문구를 사용하여주의를 끌지 만, 그 말이 범주 적으로 사실임을 의미하지는 않습니다.
Robert Harvey

12
@IntelliData 어떤 사람들은 자바 자체가 악하다고 말합니다.
null

18
@MetaFightsetEvil(null);

4
확실히 평가는 악의 화신입니다. 또는 가까운 사촌.
주교

58

행동보다는 데이터를 노출시키는 객체를 갖는 것이 완벽합니다. 그냥 "데이터 객체"라고 부릅니다. 패턴은 데이터 전송 오브젝트 또는 값 오브젝트와 같은 이름으로 존재합니다. 오브젝트의 목적이 데이터를 보유하는 것이라면 게터와 세터가 데이터에 액세스하는 데 유효합니다.

그렇다면 누군가가 "getter 및 setter 메소드가 악하다"고 말할까요? 당신은 이것을 많이 볼 것입니다-누군가는 특정 상황에서 완벽하게 유효한 지침을 취한 다음 더 어려운 타격을 줄 수 있도록 상황을 제거합니다. 예를 들어 " 상속에 대한 선호 구성 "은 훌륭한 원칙이지만 곧 누군가가 문맥을 제거하고 " 왜 악을 확장 하는가 "(이봐, 같은 저자, 우연의 일치인가!) 또는 " 상속이 악한 것 "이라고 쓸 것입니다. "를 파괴 .

기사의 내용을 보면 실제로 몇 가지 유효한 포인트가 있으며 클릭 베이트 헤드 라인을 만들기 위해 포인트를 늘립니다. 예를 들어이 기사에서는 구현 세부 정보를 공개해서는 안된다고 명시하고 있습니다. 이것이 OO의 기본 캡슐화 및 데이터 숨기기 원칙입니다. 그러나 getter 메소드는 정의에 따라 구현 세부 사항을 노출하지 않습니다. Customer 데이터 객체 의 경우 Name , Address 등 의 속성은 구현 세부 정보가 아니라 객체의 전체 목적이므로 공용 인터페이스의 일부 여야합니다.

악의 세터를 사용하지 않고 '직원'개체에서 '이름'과 '봉급'과 같은 속성을 실제로 설정하도록 제안하는 방법을 보려면 링크 된 기사 의 연속 을 읽으십시오 . 그는 Add Name 이라는 메소드로 채워진 'Exporter'가있는 패턴을 사용하고 , Salary를 추가 하여 동일한 이름의 필드를 설정합니다. 결국 그는 정확히 setter 패턴을 사용하여 끝납니다. 다른 명명 규칙.

이것은 싱글 톤의 함정을 동일한 구현을 유지하면서 하나만 바꿀 수 있다는 점을 생각하는 것과 같습니다.


죄송합니다. 소위 전문가는 이것이 사실이 아니라고 말합니다.
IntelliData

8
그럼 다시, 어떤 사람들은 '결코 OOP를 사용하지 않는'말했다 : harmful.cat-v.org/software/OO_programming
JacquesB

7
FTR, 나는 더 많은“전문가” 가 결코 전혀 창조하지 않고 모든 것을 위해 의미없는 게터 / 세터를 만드는 법을 가르치고 있다고 생각 합니다. IMO는 후자가 덜 잘못된 조언을한다.
leftaroundabout

4
@ leftaroundabout : 좋아,하지만 '항상'과 '절대'사용하지 않는 '적절한 경우'사이의 중간 지점을 제안하고 있습니다.
JacquesB

3
문제는 많은 프로그래머가 모든 객체 (또는 너무 많은 객체)를 DTO로 바꾸는 것입니다. 때로는 필요하지만 데이터를 동작과 분리하므로 가능한 한 피하십시오. (당신이 독실한 OOPer라고 가정)
user949300

11

Customer데이터 클래스에서 -class 를 변환하기 위해 데이터 필드에 대해 다음과 같은 질문을 할 수 있습니다.

{데이터 필드}를 어떻게 사용하고 싶습니까? {데이터 필드}는 어디에 사용됩니까? {data field} 사용을 클래스로 옮길 수 있습니까?

예 :

  • 의 목적은 Customer.Name무엇입니까?

    가능한 답변, 로그인 웹 페이지에 이름을 표시하고 고객에게 메일을 보낼 때 이름을 사용하십시오.

    어떤 방법으로 이어질까요 :

    • Customer.FillInTemplate (…)
    • Customer.IsApplicableForMailing (…)
  • 의 목적은 Customer.DOB무엇입니까?

    고객의 연령을 확인합니다. 고객의 생일 할인. 우편물.

    • Customer.IsApplicableForProduct ()
    • Customer.GetPersonalDiscount ()
    • Customer.IsApplicableForMailing ()

의견을 감안할 때 예제 개체 ( Customer데이터 개체와 자체 책임이있는 "실제"개체)는 너무 광범위합니다. 즉, 속성 / 책임이 너무 많습니다. 에 따라 구성 요소 중 하나 많은 리드 Customer(속성을 읽어)하거나 Customer구성 요소의 많은에 따라 달라집니다. 아마도 고객에 대한 다른 견해가있을 수 있으며, 각각의 고유 한 클래스 1 이 있어야합니다 .

  • Account화폐 거래 의 맥락에서 고객 은 아마도 다음 용도로만 사용됩니다.

    • 인간이 돈을 송금하여 올바른 사람에게가는 것을 식별하도록 돕는다. 과
    • 그룹 Accounts.

    이 고객은 같은 분야를 필요로하지 않는 DOB, FavouriteColour, Tel, 아마조차 Address.

  • 뱅킹 웹 사이트에 로그인하는 사용자와 관련된 고객.

    관련 필드는 다음과 같습니다.

    • FavouriteColour개인화 된 테마의 형태로 나타날 수 있습니다.
    • LanguagePreferences,
    • GreetingName

    게터와 세터가있는 속성 대신 단일 메서드로 캡처 할 수 있습니다.

    • PersonaliseWebPage (템플릿 페이지);
  • 마케팅 및 개인화 된 메일 링의 맥락에서 고객.

    여기서는 데이터 객체의 속성에 의존하지 않고 객체의 책임에서 시작합니다. 예 :

    • IsCustomerInterestedInAction (); 과
    • GetPersonalDiscounts ().

    이 고객 객체 FavouriteColourAddress속성이 있거나 속성이 관련 이 없다는 사실은 아마도 구현시 이러한 속성을 사용합니다. 그러나 일부 기계 학습 기술을 사용하고 고객과의 이전 상호 작용을 사용하여 고객이 관심을 가질 수있는 제품을 찾을 수도 있습니다.


1. 물론 CustomerAccount클래스는 예제였으며 간단한 예제 또는 숙제 연습에서는이 고객을 분할하는 것이 과도 할 수 있지만, 분할의 예를 통해 데이터 개체를 개체로 변환하는 방법을 책임이 작동합니다.


4
실제로 질문에 대답하기 때문에 공감 :) 그러나 제안 된 솔루션이 gettes / setter를 갖는 것보다 훨씬 나쁘다는 것도 분명합니다. 질문의 전제에 결함이 있음을 보여줍니다.
JacquesB

@Kasper van den Berg : 일반적으로 고객의 속성이 많을 때 처음에는 어떻게 설정합니까?
IntelliData

2
@IntelliData 값은 데이터베이스, XML 등에서 나올 수 있습니다. 고객 또는 CustomerBuilder는 값을 읽은 다음 (예 : Java) 개인 / 패키지 / 내부 클래스 액세스 또는 (ick) 리플렉션을 사용하여 설정합니다. 완벽하지는 않지만 일반적으로 공개 세터를 피할 수 있습니다. (자세한 내용은 내 답변을 참조하십시오)
user949300

6
나는 고객 클래스가 이러한 것들에 대해 알아야한다고 생각하지 않습니다.
코드 InChaos

어떻게 같은 약 Customer.FavoriteColor?
Gabe

8

TL; DR

  • 행동 모델링이 좋습니다.

  • 좋은 추상화를위한 모델링이 더 좋습니다.

  • 때로는 데이터 개체가 필요합니다.


행동과 추상화

게터와 세터를 피해야하는 몇 가지 이유가 있습니다. 하나는 앞서 언급했듯이 모델링 데이터를 피하는 것입니다. 이것이 실제로 사소한 이유입니다. 더 큰 이유는 추상화를 제공하는 것입니다.

은행 계좌가 분명한 예에서 : setBalance()잔액을 설정하는 것이 계좌에 사용되는 것이 아니기 때문에 방법이 실제로 나쁠 것입니다. 계정의 동작은 가능한 한 현재 잔액에서 추상화되어야합니다. 인출 실패 여부를 결정할 때 잔액을 고려할 수 있으며 현재 잔액에 액세스 할 수 있지만 은행 계좌와의 상호 작용을 수정하면 사용자가 새 잔액을 계산하지 않아도됩니다. 그것이 계정 자체가해야 할 일입니다.

심지어 한 쌍 deposit()withdraw()방법은 은행 계좌를 모델링하는 것이 적합하지 않습니다. 더 좋은 방법 transfer()은 다른 계정과 금액을 인수로 사용하는 한 가지 방법 만 제공하는 것 입니다. 이를 통해 계정 클래스는 사소하게 시스템에서 돈을 창출 / 파기하지 않으며 매우 유용한 추상화를 제공하며 실제로는 특수 계정을 사용하게되므로 더 많은 통찰력을 제공 할 수 있습니다. 수입 / 투자 / 손실 돈 ( 이중 회계 참조 ). 물론 계정을 사용할 때마다이 수준의 추상화가 필요한 것은 아니지만 클래스가 제공 할 수있는 추상화의 양을 고려할 가치가 있습니다.

추상화를 제공하고 내부 데이터를 숨기는 것이 항상 같은 것은 아닙니다. 거의 모든 응용 프로그램에는 사실상 데이터 인 클래스가 포함되어 있습니다. 튜플, 사전 및 배열이 자주 사용되는 예입니다. 사용자에게 포인트의 x 좌표를 숨기고 싶지 않습니다. 요점으로 할 수 있거나해야 할 추상화는 거의 없습니다.


고객 클래스

고객은 확실히 시스템에서 유용한 추상화를 제공해야하는 엔티티입니다. 예를 들어 장바구니와 관련이있을 수 있으며 장바구니와 고객의 조합은 구매를 허용해야합니다. 이로 인해 요청한 제품을 보내거나 돈을 청구하는 등 (선택한 지불을 고려하여) 방법) 등

캐치 (catch)는 언급 한 모든 데이터가 고객과 연관 될뿐만 아니라 모든 데이터도 변경 가능하다는 것입니다. 고객이 움직일 수 있습니다. 신용 카드 회사를 변경할 수 있습니다. 그들은 이메일 주소와 전화 번호를 변경할 수 있습니다. 그들은 심지어 그들의 이름이나 성별을 바꿀 수도 있습니다! 따라서 완전한 기능을 갖춘 고객 클래스는 이러한 모든 데이터 항목에 대한 완전한 수정 액세스를 제공해야합니다.

아직도, 호텔은 / 비 - 사소한 서비스를 제공해야 할 수 있습니다 그들은 마찬가지로, "게터"는 이메일-adresses를 제공 같은 높은 수준의 서비스를 제공 할 수 있습니다 이메일 adresses, 우편 adresses의 검증 등의 올바른 형식을 보장 할 수 있습니다 Name <user@server.com>형식 이름 필드와 입금 된 이메일 주소를 사용하거나 올바른 형식의 우편 주소 등을 제공하십시오. 물론이 고급 기능의 의미는 사용 사례에 따라 크게 다릅니다. 완전히 과잉이거나 다른 학급에서 제대로해야 할 수도 있습니다. 추상화 수준의 선택은 쉽지 않습니다.


섹스 파트에 동의하지 않더라도 소리가납니다 ...;)
IntelliData

6

카스퍼의 답변을 넓히려 고 시도하면 세터를 반대하고 제거하는 것이 가장 쉽습니다. 다소 모호하고, 손을 흔들며 (그리고 희망적으로 유머러스 한) 주장 :

Customer.Name은 언제 변경됩니까?

드물게. 아마 결혼했을 수도 있습니다. 또는 증인 보호에 들어갔다. 그러나이 경우 거주지, 친척 및 기타 정보를 확인하고 변경하고 싶을 수도 있습니다.

DOB는 언제 바뀔까요?

초기 생성시 또는 데이터 입력 중단시에만. 또는 그들이 Domincan 야구 선수라면. :-)

이 필드는 일반적인 일반 세터를 사용하여 액세스 할 수 없습니다. 어쩌면 당신은이 Customer.initialEntry()방법, 또는 Customer.screwedUpHaveToChange()특별한 권한이 필요 방법. 그러나 공개 Customer.setDOB()방법 이 없습니다 .

일반적으로 고객은 데이터베이스, REST API, 일부 XML 등에서 읽습니다. 방법 Customer.readFromDB()이 있거나 SRP / 문제 분리에 대해 더 엄격한 경우 별도의 작성기 (예 : 방법이 있는 CustomerPersister객체)가 read()있습니다. 내부적으로 그들은 어떻게 든 필드를 설정합니다 (패키지 액세스 또는 내부 클래스 인 YMMV를 선호합니다). 그러나 다시 한 번 공공 세터를 피하십시오.

(질문의 부록이 다소 변경되었습니다 ...)

애플리케이션이 관계형 데이터베이스를 많이 사용한다고 가정 해 봅시다. Customer.saveToMYSQL()또는 Customer.readFromMYSQL()메소드 를 갖는 것은 어리석은 일입니다 . 이는 비표준적이고 구체적이며 실체 를 바꿀 수있는 바람직하지 않은 결합을 만듭니다 . 예를 들어 스키마를 변경하거나 Postgress 또는 Oracle로 변경할 때.

그러나, IMO, 그것은에 커플 고객에게 완벽하게 허용 추상적 인 표준 , ResultSet. 별도의 도우미 객체 ( CustomerDBHelper아마도 서브 클래스 라고 부릅니다 AbstractMySQLHelper)는 DB에 대한 모든 복잡한 연결에 대해 알고 까다로운 최적화 세부 사항을 알고 테이블을 쿼리하고 조인하는 등을 알고 있습니다. ORM like Hibernate)를 사용하여 ResultSet을 생성하십시오. 객체 가 변경 될 가능성 ResultSet이없는 추상 표준 인와 대화합니다 . 당신은 기본 데이터베이스를 변경하거나 스키마를 변경하는 경우, 고객은 변경되지 않습니다 ,하지만 CustomerDBHelper는 않습니다. 운이 좋으면 Customer, Merchant, Shipping 등을 자동으로 변경하는 것은 AbstractMySQLHelper뿐입니다.

이런 식으로 게터와 세터의 필요성을 피하거나 줄일 수 있습니다.

그리고 Holub 기사의 요점은 위의 내용을 getter와 setter를 사용하여 데이터베이스를 변경했을 때의 상황과 비교하고 대조합니다.

마찬가지로 많은 XML을 사용한다고 가정 해 봅시다. IMO, 고객을 Python xml.etree.ElementTree 또는 Java org.w3c.dom.Element 와 같은 추상 표준에 연결하는 것이 좋습니다 . 고객은 그로부터 자신을 얻고 설정합니다. 다시 말하지만, 게터와 세터의 필요성을 줄일 수 있습니다.


빌더 패턴을 사용하는 것이 좋습니다.
IntelliData

Builder는 객체를보다 쉽고 강력하게 구성 할 수 있으며 원하는 경우 객체를 변경할 수 없도록하는 데 유용합니다. 그러나 기본 개체에는 여전히 DOB 필드가 있다는 사실이 (부분적으로) 노출되어 있기 때문에 모든 것이 끝이 아닙니다.
user949300 2016 년

1

게터와 세터를 갖는 문제는 클래스가 비즈니스 로직에서 한 가지 방식으로 사용될 수 있다는 사실의 문제 일 수 있지만 데이터베이스 나 파일 또는 기타 영구 저장소에서 데이터를 직렬화 / 역 직렬화하는 도우미 클래스가있을 수도 있습니다.

데이터를 저장 / 검색하는 많은 방법이 있으며 데이터 객체를 저장된 방식에서 분리하려는 경우 이러한 멤버를 공개하거나 게터를 통해 액세스 할 수있게하여 캡슐화를 "손상"시킬 수 있습니다. 공개적으로 만드는 것만큼이나 나쁜 세터.

이 문제에는 여러 가지 방법이 있습니다. 한 가지 방법은 "친구"에게 데이터를 제공하는 것입니다. 우정은 물려받지 않지만, 이것은 친구에게 정보를 요청하는 시리얼 라이저, 즉베이스 시리얼 라이저가 정보를 "전달"하는 것에 의해 극복 될 수있다.

클래스에는 일반적인 "fromMetadata"또는 "toMetadata"메소드가있을 수 있습니다. From-metadata는 객체를 생성하므로 생성자가 될 수도 있습니다. 동적으로 유형이 지정된 언어 인 경우 메타 데이터는 해당 언어의 표준이며 아마도 이러한 객체를 구성하는 기본 방법 일 것입니다.

귀하의 언어가 구체적으로 C ++ 인 경우,이 문제를 해결하는 한 가지 방법은 공개 "struct"데이터를 보유하고 클래스가이 "struct"인스턴스를 멤버로, 실제로 저장하려는 모든 데이터를 보유하는 것입니다. 저장하기 위해 검색하십시오. 그런 다음 "래퍼"를 쉽게 작성하여 여러 형식으로 데이터를 읽고 쓸 수 있습니다.

언어가 "structs"가없는 C # 또는 Java 인 경우 비슷하게 수행 할 수 있지만 struct는 이제 2 차 클래스입니다. 데이터 또는 constnessness의 "소유권"에 대한 실제 개념이 없으므로 데이터가 포함 된 클래스의 인스턴스를 제공하고 모든 공개 데이터를 보유한 경우 보류중인 모든 데이터를 수정할 수 있습니다. 이것이 비쌀 수 있지만 "복제"할 수 있습니다. 또는이 클래스에 개인 데이터가 있지만 접근자를 사용할 수 있습니다. 클래스의 사용자에게 데이터를 얻는 방법은 있지만 클래스와의 직접적인 인터페이스는 아니며 유스 케이스 인 클래스의 데이터를 저장하는 데 실제로 세부 사항입니다.


0

OOP는 객체 내부의 동작을 캡슐화하고 숨기는 것입니다. 개체는 블랙 박스입니다. 이것은 물건을 디자인하는 방법입니다. 많은 경우에 자산은 다른 구성 요소의 내부 상태를 알 필요가 없으며 알 필요가없는 것이 좋습니다. 주로 인터페이스를 사용하거나 가시성이있는 개체 내부에서 해당 아이디어를 적용 할 수 있으며 호출자에게는 허용 된 동사 / 동작 만 처리 할 수 ​​있습니다.

이것은 어떤 종류의 문제에 잘 작동합니다. 예를 들어 사용자 인터페이스에서 개별 UI 구성 요소를 모델링합니다. 텍스트 상자와 상호 작용하면 텍스트를 설정하거나 가져 오거나 텍스트 변경 이벤트를 듣는 데만 집중할 수 있습니다. 일반적으로 커서의 위치, 텍스트를 그리는 데 사용되는 글꼴 또는 키보드 사용 방법에 대해서는 신경 쓰지 않습니다. 캡슐화는 여기에서 많은 것을 제공합니다.

반대로 네트워크 서비스를 호출 할 때 명시적인 입력을 제공하십시오. 일반적으로 문법 (예 : JSON 또는 XML)이 있으며 서비스를 호출하는 모든 옵션을 숨길 이유가 없습니다. 아이디어는 원하는 방식으로 서비스를 호출 할 수 있고 데이터 형식이 공개 및 게시된다는 것입니다.

이 경우 또는 다른 많은 (예 : 데이터베이스 액세스) 실제로 공유 데이터로 작업합니다. 따라서 숨길 이유가 없지만 반대로 사용할 수 있기를 원합니다. 읽기 / 쓰기 액세스 또는 데이터 검사 일관성에 대한 우려가있을 수 있지만이 핵심에서는 이것이 공개 개념 인 경우 핵심 개념입니다.

캡슐화를 피하고 물건을 공개하고 명확하게 표현하려는 설계 요구 사항의 경우 객체를 피하고 싶습니다. 실제로 필요한 것은 객체가 아닌 튜플, C 구조체 또는 이와 동등한 것입니다.

그러나 Java와 같은 언어에서도 발생합니다. 모델링 할 수있는 유일한 것은 객체 또는 객체 배열입니다. 객체는 몇 가지 원주민 유형 (int, float ...)을 보유 할 수 있지만 그게 전부입니다. 그러나 객체는 공공 장소 만있는 단순한 구조체처럼 행동 할 수 있습니다.

따라서 데이터를 모델링하면 더 이상 필요하지 않기 때문에 객체 내부의 공용 필드만으로도 작업을 수행 할 수 있습니다. 캡슐화는 필요하지 않기 때문에 사용하지 않습니다. 이것은 여러 언어로 이런 식으로 수행됩니다. Java에서는 역사적으로 getter / setter를 사용하면 최소한 읽기 / 쓰기 제어가 가능하고 (예를 들어 setter를 추가하지 않음) introspection API를 사용하여 툴링 및 프레임 워크가 getter / setter 메소드를 찾아서 사용하는 표준이 상승했습니다. 자동 생성 된 사용자 인터페이스에서 내용을 자동으로 채우거나 수정 가능한 필드로 표시합니다.

setter 메소드에 로직 / 체크를 추가 할 수도 있다는 주장도 있습니다.

실제로 getter / setter는 순수한 데이터를 모델링하는 데 가장 많이 사용되므로 거의 정당화되지 않습니다. 객체를 사용하는 프레임 워크와 개발자는 getter / setter가 필드를 설정 / 가져 오는 것 이상을 기대하지 않습니다. 공공 장소에서 할 수있는 것보다 게터 / 세터를 더 이상 효과적으로 사용하지 않습니다.

그러나 그것은 오래된 습관과 오래된 습관을 제거하기가 어렵습니다 ... 당신은 게터 / 세터를 맹목적으로 두지 않으면 서 자신이 무엇인지, 그들이 무엇인지 더 잘 이해할 수있는 배경이 없다면 동료 나 교사에 의해 위협받을 수 있습니다 아니.

모든 게터 / 세터 상용구 코드를 사용하려면 언어를 변경해야 할 것입니다. (C # 또는 lisp와 같은). 저에게 게터 / 세터는 또 다른 10 억 달러의 실수입니다.


6
C # 속성은 게터와 세터보다 캡슐화를 실제로 구현하지 않습니다.
IntelliData

1
[gs] etters의 장점은 일반적으로하지 않는 작업 (값 확인, 관찰자에게 알리기), 존재하지 않는 필드 등을 시뮬레이션 할 수 있고 나중에 나중에 변경할 수 있다는 것 입니다. 함께 롬복 , 상용구는 사라 : @Getter @Setter class MutablePoint3D {private int x, y, z;}.
maaartinus

1
@maaartinus 확실히 [gs] etter는 다른 방법으로 무엇이든 할 수있다. 즉, 모든 호출자 코드는 설정 한 값이 예외를 발생 시키거나 변경되거나 잠재적으로 변경 알림을 보낼 수 있습니다. 더 많거나 적은 egs가 필드에 대한 액세스를 제공하지 않고 중재 코드를 수행하고 있습니다.
Nicolas Bousquet

@IntelliData C # 속성을 사용하면 보일러 플레이트의 쓸모없는 단일 문자를 쓸 수없고이 모든 게터 / 세터를 관리하지 않아도됩니다. 이것은 이미 롬복 프로젝트보다 더 나은 성과입니다. 또한 getters / setter 만있는 POJO는 캡슐화를 제공하는 것이 아니라 서비스와 교환하여 자유롭게 읽거나 쓸 수있는 데이터 형식을 게시하는 것입니다. 캡슐화는 디자인 요구 사항과 반대입니다.
Nicolas Bousquet

나는 속성이 정말 좋다고 생각하지 않습니다. 물론, 당신은 접두사와 괄호, 즉 호출 당 5 문자를 저장하지만 1. 필드처럼 보이므로 혼동됩니다. 2. 그것들은 당신의 성찰에 도움이 필요한 추가적인 것입니다. 큰 문제는 없지만 큰 장점은 없습니다 (Java + 롬복과 비교할 때 순수 Java는 분명 손실입니다).
maaartinus

0

예를 들어, Name, DOB, Tel, Address 등과 같은 고객에 대한 정보가 많은 경우 계속 유지하는 Customer 클래스가 있다면 모든 속성을 가져오고 설정하기 위해 getter / setter를 어떻게 피할 수 있습니까? 모든 데이터를 채우기 위해 어떤 '동작'유형 방법을 쓸 수 있습니까?

데이터를 채우는 동작 방법에 대해 걱정하기 때문에이 질문은 의욕적이라고 생각하지만 Customer객체 클래스가 캡슐화하려는 동작에 대한 징후는 보이지 않습니다 .

소프트웨어를 사용하여 다른 작업을 수행 하는 사용자 / 액터 로서 '고객'과 객체 클래스Customer 로 혼동하지 마십시오 .

고객에 대한 정보가 고객에 대한 정보가 많으면 행동이 진행되는 한 많은 고객 클래스가 있다고 말하면 고객 클래스가 암석과 거의 구별하지 못하는 것처럼 보입니다. A는 Rock당신의 현재 주소를 저장하는 필드를 가질 수있다, 당신이 그것을 이름을 줄 수있는 색상을 가질 수 있지만, 우리는 바위에서 지능적인 행동의 어떤 종류를 기대하지 않습니다.

게터 / 세터가 악한 것에 관한 링크 된 기사에서 :

OO 디자인 프로세스는 사용 사례에 중점을 둡니다. 사용자는 유용한 결과를 얻는 독립 실행 형 작업을 수행합니다. (로그온은 문제 영역에서 유용한 결과가 없기 때문에 유스 케이스가 아닙니다. 급여 확인은 유스 케이스입니다.) OO 시스템은 유스 케이스를 구성하는 다양한 시나리오를 실행하는 데 필요한 활동을 구현합니다.

동작이 정의 Customer되지 않은 상태 에서 암석을 참조한다고 해서 추적하려는 속성이있는 객체 일 뿐이며 게터로부터 벗어나기 위해 어떤 속임수를 사용하든 상관 없습니다. 세터. 록은 이름이 올바른지 상관하지 않으며, 록은 주소가 유효한지 여부를 알 수 없습니다.

주문 시스템은 Rock구매 주문과 구매 주문을 연관시킬 수 Rock있으며, 주소가 정의되어있는 한 시스템의 일부는 품목이 바위로 배달되도록 할 수 있습니다.

이 모든 경우에 Rock이것은 데이터 개체 일 뿐이며 가설 대신 유용한 결과로 특정 동작을 정의 할 때까지 계속됩니다.


이 시도:

잠재적으로 다른 두 가지 의미로 '고객'이라는 단어가 과부하되는 것을 피하면 개념화가 더 쉬워집니다.

합니까 Rock오브젝트 장소는 주문 또는 인간이 UI 요소를 클릭하여 않는 무언가가 시스템에서 작업을 실행하는 것입니다?


고객은 많은 일을하는 행위자이지만 그와 관련된 많은 정보를 가지고 있습니다. 그것은 두 개의 개별 클래스를 생성하는 것을 정당화합니까?
IntelliData

@IntelliData는 레이어를 통해 풍부한 개체를 전달하는 것이 까다로운 부분입니다. 컨트롤러에서 뷰로 객체를 보내는 경우 뷰는 객체의 일반 계약 (예 : JavaBeans 표준)을 이해해야합니다. 와이어를 통해 객체를 전송하는 경우 JaxB 등은 완전한 객체를 제공하지 않았기 때문에 멍청한 객체로 객체를 복원 할 수 있어야합니다. 리치 오브젝트는 데이터 조작에 훌륭합니다. 상태 전송에 적합하지 않습니다. 반대로, 바보 개체는 데이터 조작에 좋지 않으며 상태 전송에 좋습니다.

0

SQL을 사용하는 객체 접근 방식을 언급하면서 여기에 2 센트를 추가 합니다 .

이 접근법은 자체 포함 된 객체의 개념을 기반으로합니다. 여기에는 동작을 구현하는 데 필요한 모든 리소스가 있습니다. 작업을 수행 하는 방법 필요는 없습니다 . 선언적 요청으로 충분합니다. 그리고 객체는 모든 데이터를 클래스 속성으로 유지할 필요는 없습니다. 그들이 어디에서 왔는지는 중요하지 않습니다.

집합체 에 대해 이야기 하면 불변성은 문제가되지 않습니다. 예를 들어, 집계가 보유 할 수있는 일련의 상태가 있습니다. 사가로 집계 루트 각 상태를 독립형 객체로 구현하는 것이 좋습니다. 아마도 더 나아갈 수도 있습니다. 도메인 전문가와 채팅하십시오. 그 또는 그녀는이 집합체를 통합 된 개체로 보지 않을 가능성이 있습니다. 아마도 각 주마다 고유 한 의미를 지니고 있으며, 고유 한 목적이 있습니다.

마지막으로, 객체 찾기 프로세스는 서브 시스템 으로의 시스템 분해 와 매우 유사 합니다. 둘 다 행동이 아닌 다른 것을 기반으로합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.