클래스가 자체 공개 메소드를 사용하는 것이 괜찮습니까?


23

배경

현재 장치에서 전송 및 수신 하는 객체 가있는 상황이 있습니다 . 이 메시지에는 다음과 같은 몇 가지 구성이 있습니다.

public void ReverseData()
public void ScheduleTransmission()

ScheduleTransmission방법은 필요 통화에 ReverseData가 호출 될 때마다 방법을. 그러나 응용 프로그램에서 객체가 인스턴스화되는 곳에서 ReverseData외부 로 호출 해야 하며 네임 스페이스 외부를 완전히 추가 해야하는 경우가 있습니다 .

"수신"에 관해서 는 데이터 처리를 취소하기 위해 이벤트 핸들러 ReverseData에서 외부 적으로 호출 object_received됩니다.

의문

객체가 자신의 공개 메소드를 호출하는 것이 일반적으로 허용됩니까?


5
자체적으로 공개 메소드를 호출하는 데 아무런 문제가 없습니다. 그러나 메서드 이름을 기준으로 ReverseData ()는 내부 데이터를 되 돌리면 공개 메서드가되는 것이 조금 위험합니다. ReverseData ()가 객체 외부에서 호출 된 다음 ScheduleTransmission ()을 사용하여 다시 호출되면 어떻게됩니까?
Kaan

@Kaan 이들은 내 방법의 실제 이름은 아니지만 밀접한 관련이 있습니다. 실제로 "역 데이터"는 전체 워드의 8 비트 만 역전시키고 수신 및 전송시 수행됩니다.
Snoop

문제는 여전히 유효합니다. 전송이 예약되기 전에 8 비트가 두 번 반전되면 어떻게됩니까? 이것은 공용 인터페이스에 큰 구멍처럼 느껴집니다. 내 대답에서 언급 한 것처럼 공용 인터페이스를 생각하면이 문제를 밝히는 데 도움이 될 수 있습니다.
Daniel T.

2
@StevieV 나는 이것이 Kaan이 제기 한 우려를 증폭시킬 뿐이라고 믿는다. 객체의 상태를 변경하는 메소드를 노출한다고 생각하는 것처럼 들리며 상태는 주로 메소드 호출 횟수에 따라 다릅니다. 이것은 코드 전체에서 객체의 상태를 추적하려는 악몽을 만듭니다. 이러한 개념적 상태와 분리 된 데이터 유형의 이점을 누릴 수있는 것처럼 들리 므로 걱정할 필요없이 특정 코드 조각에 무엇이 있는지 알 수 있습니다.
jpmc26

2
@StevieV는 Data 및 SerializedData와 같은 것입니다. 데이터는 코드에 표시되는 것이고 SerializedData는 네트워크를 통해 전송되는 것입니다. 그런 다음 리버스 데이터 (또는 데이터를 직렬화 / 직렬화 해제)를 한 유형에서 다른 유형으로 변환하십시오.
csiz

답변:


33

확장 기능을 허용하려는 경우 특히 허용되는 것이 아니라 특히 권장됩니다. C #에서 클래스의 확장을 지원하려면 아래 주석에 따라 메소드를 가상으로 플래그 지정해야합니다. 그러나 ReverseData ()를 재정의 할 때 다른 사람이 놀라지 않도록 ScheduleTransmission ()의 작동 방식을 누군가가 놀라게하지 않도록이를 문서화 할 수 있습니다.

그것은 실제로 수업의 디자인에 달려 있습니다. ReverseData ()는 클래스의 기본 동작처럼 들립니다. 다른 장소에서이 동작을 사용해야하는 경우 다른 버전을 원하지 않을 것입니다. ScheduleTransmission ()과 관련된 세부 정보가 ReverseData ()로 누출되지 않도록주의해야합니다. 문제가 생길 것입니다. 그러나 이미 클래스 외부에서 이것을 사용하고 있기 때문에 이미 그것을 통해 생각했을 것입니다.


와, 나에게는 정말로 이상해 보였다. 그러나 이것은 도움이된다. 다른 사람들의 말을 듣고 싶습니다. 고맙습니다.
Snoop

2
"ReverseData ()를 재정의 할 때 누군가가 놀라지 않도록 ScheduleTransmission ()의 작동 방식을 변경합니다." : 아니요, 실제로는 아닙니다. 적어도 C #에는 해당되지 않습니다 (질문에는 C # 태그가 있음에 유의하십시오). 하지 않는 한 ReverseData()추상적이다, 당신은 자식 클래스에 무슨 상관없이, 항상 호출됩니다 원래의 방법입니다.
Arseni Mourzenko

2
@MainMa Or virtual... 그리고 메소드를 재정의하는 경우 정의에 따라 virtual또는 이어야 abstract합니다.
Pokechu22

1
@ Pokechu22 : 실제로 virtual작동합니다. 모든 경우에 OP에 따르면 서명은 public void ReverseData()물건을 재정의하는 것에 대한 대답의 일부가 약간 오도됩니다.
Arseni Mourzenko

@MainMa 기본 클래스 메소드가 가상 메소드를 호출하면 일반적인 가상 방식으로 작동하지 않는다고 말하는 것 abstract입니까? 나는 abstractvs에 대한 답변을 찾았고 virtual언급 된 것을 보지 못했습니다. 오히려 추상은 단지이베이스에 주어진 버전이 없다는 것을 의미합니다.
JDługosz

20

전혀.

메소드의 가시성은 클래스 외부 또는 하위 클래스 내의 메소드에 대한 액세스를 허용하거나 거부하는 유일한 목적을 가지고 있습니다. 공개, 보호 및 개인 메소드는 항상 클래스 자체 내에서 호출 할 수 있습니다.

퍼블릭 메서드 호출에는 아무런 문제가 없습니다. 귀하의 질문에 나오는 그림은 두 가지 공개 방법이 있어야하는 상황의 완벽한 예입니다.

그러나 다음 패턴을주의해서 살펴보십시오.

  1. 메소드 Hello()는 다음 World(string, int, int)과 같이 호출 합니다.

    Hello()
    {
        this.World("Some magic value here", 0, 100);
    }

    이 패턴을 피하십시오. 대신 선택적 인수를 사용하십시오 . 선택적 인수는 검색을 쉽게 해줍니다. 입력하는 호출자 Hello(는 기본값으로 메소드를 호출 할 수있는 메소드가 있다는 것을 반드시 알 필요는 없습니다. 선택적 인수도 자체 문서화입니다. World()발신자에게 실제 기본값을 표시하지 않습니다.

  2. 메소드 Hello(ComplexEntity)는 다음 World(string, int, int)과 같이 호출 합니다.

    Hello(ComplexEntity entity)
    {
        this.World(entity.Name, entity.Start, entity.Finish);
    }

    대신, 과부하를 사용하십시오 . 같은 이유 : 더 나은 검색 가능성. 발신자는 IntelliSense를 통해 모든 과부하를 한 번에보고 올바른 것을 선택할 수 있습니다.

  3. 메소드는 실질적인 가치를 추가하지 않고 단순히 다른 공용 메소드를 호출합니다.

    두 번 생각. 이 방법이 정말로 필요합니까? 아니면 그것을 제거하고 발신자가 다른 방법을 호출하게해야합니까? 힌트 : 방법의 이름이 맞지 않거나 찾기 어려운 경우 제거해야합니다.

  4. 메소드는 입력을 확인한 후 다른 메소드를 호출합니다.

    Hello(string name, int start, int end)
    {
        if (name == null) throw new ArgumentNullException(...);
        if (start < 0) throw new OutOfRangeException(...);
        ...
        if (end < start) throw new ArgumentException(...);
    
        this.World(name, start, end);
    }

    대신 World매개 변수 자체의 유효성을 검증하거나 개인용이어야합니다.


ReverseData가 실제로 내부에 있고 "object"인스턴스를 포함하는 네임 스페이스 전체에서 사용 된 경우에도 동일한 생각이 적용됩니까?
스눕

1
@StevieV : 물론입니다.
Arseni Mourzenko

@MainMa 나는 포인트 12
Kapol

@Kapol : 의견 주셔서 감사합니다. 처음 두 지점에 대한 설명을 추가하여 답변을 편집했습니다.
Arseni Mourzenko

1의 경우에도 일반적으로 선택적 인수보다 과부하를 선호합니다. 옵션이 과부하를 사용하는 것이 옵션이 아니거나 특히 많은 수의 과부하를 생성하는 경우 사용자 정의 유형을 작성하고 인수로 제안하거나 (제안 2에서와 같이) 코드를 리팩터링합니다.
Brian

8

공개 된 것이 있으면 시스템에서 언제든지 호출 할 수 있습니다. 그러한 시스템 중 하나도 될 수없는 이유는 없습니다!

고도로 최적화 된 라이브러리에서는에있는 관용구를 복사하려고합니다 java.util.ArrayList#ensureCapacity(int).

수용량

  • ensureCapacity 공개
  • ensureCapacity 필요한 모든 범위 검사 및 기본값 등이 있습니다.
  • ensureCapacity 전화 ensureExplicitCapacity

ensureCapacityInternal

  • ensureCapacityInternal 비공개
  • ensureCapacityInternal 모든 입력이 클래스 내부에서 제공되므로 오류 검사가 최소화됩니다.
  • ensureCapacityInternal 또한 전화 ensureExplicitCapacity

ensureExplicitCapacity

  • ensureExplicitCapacity 또한 비공개입니다
  • ensureExplicitCapacity오류 검사 가 없습니다
  • ensureExplicitCapacity 실제 작업을 수행
  • ensureExplicitCapacityensureCapacity및 이외의 곳에서는 호출되지 않습니다.ensureCapacityInternal

이런 식으로, 당신이 신뢰하는 코드는 입력이 좋다는 것을 알기 때문에 특권이있는 (그리고 더 빨리) 액세스 할 수 있습니다. 신뢰하지 않는 코드는 무결성과 폭탄을 확인하거나 기본값을 제공하거나 잘못된 입력을 처리하기 위해 특정 엄격함을 거칩니다. 둘 다 실제로 작동하는 장소로 연결됩니다.

그러나 이것은 JDK에서 가장 많이 사용되는 클래스 중 하나 인 ArrayList에서 사용됩니다. 귀하의 사례가 복잡성과 엄격 성을 요구하지 않을 가능성이 매우 높습니다. 간단히 말해서, 모든 ensureCapacityInternal통화가 통화로 교체 된 ensureCapacity경우에도 성능은 여전히 ​​정말 좋습니다. 이것은 아마도 광범위한 고려 후에 만 ​​이루어진 미세 최적화입니다.


3

몇 가지 좋은 대답이 이미 주어졌으며 객체가 다른 메서드에서 공개 메서드를 호출 할 수 있다는 것에 동의합니다. 그러나 조심해야 할 약간의 디자인 경고가 있습니다.

공용 메소드는 일반적으로 "일관된 상태로 오브젝트를 가져 가서, 합리적인 무언가를 수행하고, (아마도 다른) 오브젝트를 일관된 상태로 두십시오"라는 계약을 가지고 있습니다. 여기서, "일관된"은 예를 들어, Length의 a List<T>가 그것보다 크지 않으며 Capacity인덱스에서 요소를 참조하는 것을 던지지 않음 0을 의미 Length-1할 수있다.

그러나 객체의 메소드 내에서 객체가 일관성이없는 상태에있을 수 있으므로 공용 메소드 중 하나를 호출하면 그러한 가능성을 염두에두고 작성되지 않았기 때문에 매우 잘못된 일을 할 수 있습니다. 따라서 다른 방법에서 공용 메서드를 호출하려는 경우 계약이 "일부 상태에서 개체를 가져 와서 적절한 것을 수행하고, 개체를 (아마도 다른) 상태로 두십시오 (일관되지 않을 수 있음) 상태가 일치하지 않습니다) "


1

다른 공용 메서드 내에서 공용 메서드를 호출 할 때의 또 다른 예는 CanExecute / Execute 방식입니다. 유효성 검사와 고정 보존이 모두 필요할 때 사용합니다 .

그러나 일반적으로 나는 항상 그것에 대해 신중합니다. 메소드 a()가 메소드 내부에서 호출 b()되면 메소드 a()가 구현 세부 사항 임을 의미합니다 b(). 종종 그것들이 다른 추상화 레벨에 속한다는 것을 나타냅니다. 그리고 둘 다 공개되어 있다는 사실이 단일 책임 원칙 위반 인지 아닌지 궁금해합니다 .


-5

미안하지만 메신저는 다른 대부분의 '그렇습니다'라는 대답에 동의하지 않고 다음과 같이 말합니다.

하나의 공개 메소드를 다른 메소드에서 호출하는 클래스를 권장하지 않습니다.

이 연습에는 몇 가지 잠재적 인 문제가 있습니다.

1 : 상속 된 클래스의 무한 루프

따라서 기본 클래스는 method2에서 method1을 호출하지만 사용자 또는 다른 누군가가 상속하거나 method2를 호출하는 새로운 메소드로 method1을 숨 깁니다.

2 : 이벤트, 로깅 등

예를 들어 '1 added!'이벤트를 발생시키는 Add1 메소드가 있습니다. 아마도 Add10 메소드가 해당 이벤트를 발생시키고 로그에 쓰거나 10 번 쓰는 것을 원하지 않을 것입니다.

3 : 스레딩 및 기타 교착 상태

예를 들어 InsertComplexData는 DB 연결을 열고 트랜잭션을 시작하고 테이블을 잠근 다음 InsertSimpleData를 호출하고 연결을 열고 트랜잭션을 시작하며 테이블이 잠금 해제 될 때까지 기다립니다 ....

더 많은 이유가 있다고 확신합니다. 다른 대답 중 하나가 '방법 1을 편집하고 방법 2가 다르게 동작하기 시작하는 것에 놀랐습니다.'

일반적으로 코드를 공유하는 두 개의 공개 메소드가있는 경우 둘 다 호출하지 않고 개인 메소드를 호출하는 것이 좋습니다.

편집하다 ----

OP의 특정 사례를 확장 할 수 있습니다.

우리는 많은 세부 사항을 가지고 있지 않지만 ReverseData는 ScheduleTransmission 메소드뿐만 아니라 어떤 종류의 이벤트 핸들러에 의해 호출된다는 것을 알고 있습니다.

역 데이터가 객체의 내부 상태를 변경한다고 가정합니다.

이 경우에는 스레드 안전이 중요하므로 실습에 대한 세 번째 이의가 적용됩니다.

ReverseData 스레드를 안전하게하기 위해 잠금을 추가 할 수 있습니다. 그러나 ScheduleTransmission도 스레드로부터 안전해야하는 경우 동일한 잠금을 공유해야합니다.

이 작업을 수행하는 가장 쉬운 방법은 ReverseData 코드를 개인용 메서드로 옮기고 두 개의 공용 메서드에서 호출하는 것입니다. 그런 다음 공개 메소드에 잠금 문을 넣고 잠금 오브젝트를 공유 할 수 있습니다.

분명히 "그렇지 않을 것입니다!" 또는 "자물쇠를 다른 방법으로 프로그래밍 할 수 있습니다."그러나 좋은 코딩 방법에 대한 요점은 먼저 코드를 잘 구성하는 것입니다.

학문적으로 나는 이것이 L을 확실하게 위반한다고 말할 것입니다. 공개 방법은 공개적으로 접근 할 수있는 것 이상입니다. 또한 상속자에 의해 수정 될 수도 있습니다. 수정을 위해 코드를 닫아야합니다. 즉, 공개 및 보호 된 메소드에서 수행하는 작업에 대해 생각해야합니다.

여기 또 하나 : 당신은 또한 잠재적으로 DDD를 위반합니다. 오브젝트가 도메인 오브젝트 인 경우 공용 메소드는 비즈니스에 의미가있는 도메인 용어 여야합니다. 이 경우 '수십 개의 계란을 사다'는 방식으로 시작하더라도 '1 개의 계란을 12 번 사다'와 동일하지는 않습니다.


3
이러한 문제는 디자인 원칙에 대한 일반적인 비난보다는 생각이 부족한 아키텍처와 비슷합니다. (매번 "1 added"라고 부르는 것은 괜찮습니다. 그렇지 않으면 다르게 프로그래밍해야합니다. 두 개의 메소드가 동일한 자원을 독점적으로 잠그려고 시도하는 경우 서로 호출하지 않아야합니다. .) 잘못된 구현을 제시하기보다는 공용 메소드 액세스를 보편적 원칙으로 다루는보다 확실한 주장에 초점을 맞추고 싶을 수도 있습니다. 예를 들어, 깔끔한 코드가 좋은 이유는 무엇입니까?
doppelgreener

정확하지 않은 경우, 행사를 할 장소가 없습니다. 마찬가지로 # 3과 마찬가지로 체인을 내리는 방법 중 하나가 거래를 필요로 할 때까지 모든 것이 잘됩니다.
Ewan

이러한 모든 문제는 결함이있는 일반적인 원칙보다 품질이 좋지 않은 코드 자체를 더 많이 반영합니다. 세 가지 경우 모두 나쁜 코드입니다. 그들은 아마도 무엇인지 "를 묻는와 ("이 대신 할, 그렇게하지 않는다 "의 일부 버전에서 해결 될 수 있습니다 ? 당신이 정확하게 원하는"), 그 자신의 공개를 호출 질문에 클래스의 일반 원칙을 호출하지 않습니다 행동 양식. 무한 루프에 대한 모호한 잠재력은 여기서 원칙적으로 문제에 도달하고 철저히 다루지 않는 유일한 것입니다.
doppelgreener 2

내 예제에서 '코드'가 공유하는 유일한 thibg는 다른 공용 메서드입니다. '나쁜 코드'가 좋다고 생각하면! 저의 논쟁입니다
Ewan

망치로 자신의 손을 깰 수 있다고해서 망치가 나쁜 것은 아닙니다. 손이 부러 질 일을하지 않아도됩니다.
doppelgreener
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.