정적 메서드를 재정의 할 수없는 이유는 무엇입니까?


13

질문에 대한 답변 에서 일반적인 합의는 정적 메서드를 재정의해서는 안된다는 것입니다 (따라서 C #의 정적 함수는 가상 또는 추상적 일 수 없음). C #의 경우에만 해당되는 것은 아닙니다. Java도 이것을 금지하고 C ++도 좋아하지 않는 것 같습니다. 그러나 자식 클래스에서 재정의하려는 정적 함수 (예 : 팩토리 메서드)의 많은 예를 생각할 수 있습니다. 이론적으로는 주변을 둘러 볼 수있는 방법이 있지만 깨끗하거나 단순하지는 않습니다.

정적 함수를 무시할 수없는 이유는 무엇입니까?


4
델파이는 정적 메소드와 매우 유사하며 재정의 될 수있는 클래스 메소드를 지원합니다 . 그들은 self클래스의 인스턴스가 아닌 클래스를 가리키는 포인터를 전달받습니다 .
코드 InChaos

정적 정의 중 하나는 변화가 없다는 것입니다. 오버라이드하려는 정적 함수를 왜 만들 었는지 궁금합니다.
JeffO

4
@JeffO : "정적"의 영어 정의는 정적 멤버 함수의 고유 한 의미와 전혀 관련이 없습니다.
궤도에서 가벼움

5
귀하의 질문은 " 정적 메소드 가 단일 가상 디스패치 대신 정적 유형 디스패치를 사용해야하는 이유 "입니다. 당신이 그런 식으로 질문을 표현할 때 나는 그것이 스스로 대답한다고 생각합니다.
Eric Lippert

1
@EricLippert이 질문은 "C #이 클래스 메소드 대신 정적 메소드를 제공하는 이유는 무엇입니까?"로 더 잘 표현 될 수 있지만 여전히 유효한 질문입니다.
코드 InChaos

답변:


13

정적 메서드를 사용하면 재정의 메커니즘을 올바르게 제어 할 수있는 개체가 없습니다.

일반적인 클래스 / 인스턴스 가상 메서드 메커니즘을 사용하면 다음과 같이 재정의를 미세하게 조정할 수 있습니다. 각 실제 개체는 정확히 하나의 클래스 인스턴스입니다. 해당 클래스는 재정의 동작을 결정합니다. 항상 가상 메서드에서 첫 번째 균열을 얻습니다. 그런 다음 구현을 위해 적시에 부모 메소드를 호출하도록 선택할 수 있습니다. 그런 다음 각 상위 메소드는 차례를 가져 와서 상위 메소드를 호출합니다. 이로 인해 부모 호출이 연속적으로 진행되어 객체 방향으로 알려진 코드 재사용 개념 중 하나를 달성합니다. (여기서 기본 / 수퍼 클래스의 코드는 비교적 복잡한 방식으로 재사용되고 있습니다. OOP에서 코드 재사용에 대한 또 다른 직교 개념은 단순히 동일한 클래스의 여러 객체를 갖는 것입니다.)

기본 클래스는 다양한 하위 클래스에서 재사용 할 수 있으며 각 클래스는 편안하게 공존 할 수 있습니다. 자신의 행동을 지시하는 객체를 인스턴스화하는 데 사용되는 각 클래스는 다른 클래스와 평화롭게 동시에 공존합니다. 클라이언트는 객체를 인스턴스화하고 원하는대로 다른 클래스로 전달하는 데 사용할 클래스를 선택하여 원하는 동작과시기를 제어 할 수 있습니다.

(물론 항상 지원되지 않는 기능을 식별 할 수 있기 때문에 완벽한 메커니즘은 아닙니다. 이것이 팩토리 메소드 및 종속성 주입과 같은 패턴이 맨 위에 배치되는 이유입니다.)

따라서 다른 것을 변경하지 않고 정적에 대한 재정의 기능을 만들려면 재정의를 주문하는 데 어려움이 있습니다. 재정의 적용에 대해 제한된 컨텍스트를 정의하기가 어려우므로 객체와 같이 로컬보다 재정의를 전역 적으로 얻을 수 있습니다. 비헤이비어를 전환 할 인스턴스 객체가 없습니다. 따라서 누군가 다른 클래스에 의해 재정의 된 정적 메서드를 호출 한 경우 재정의를 제어해야합니까? 그러한 재정의가 여러 개인 경우 누가 먼저 통제권을 얻습니까? 둘째? 인스턴스 객체 재정의를 사용하면 이러한 질문에 모두 의미 있고 합리적인 답변이 있지만 정적으로는 그렇지 않습니다.

정적에 대한 재정의는 실질적으로 혼란 스러울 수 있으며 이와 같은 작업은 이전에 수행되었습니다.

예를 들어, Mac OS System 7 및 이전 버전에서는 운영 체제보다 먼저 응용 프로그램에서 만든 시스템 호출을 제어하여 시스템을 확장하기 위해 트랩 패치 메커니즘을 사용했습니다. 시스템 호출 패치 테이블 은 단일 전역 테이블이라는 점을 제외하고 인스턴스 객체 의 vtable 과 매우 유사한 함수 포인터의 배열로 생각할 수 있습니다.

이것은 트랩 패치의 질서 정연한 특성으로 인해 프로그래머에게 말할 수없는 슬픔을 초래했습니다. 트랩을 패치해야하는 사람은 원치 않더라도 기본적으로 이깁니다. 트랩의 각 패치는 일종의 상위 호출 기능에 대해 이전 트랩 값을 캡처하며, 이는 매우 취약했습니다. 패치를 제거하는 데 필요한 정보가 없기 때문에 시스템 호출에 대해 더 이상 알 필요가 없을 때 트랩 패치를 제거하는 것은 잘못된 형식으로 간주되었습니다. 당신).

이것은 정적 정의를 재정의하는 메커니즘을 만드는 것이 불가능하다는 것을 말하는 것이 아니라 정적 필드와 정적 메소드를 메타 클래스의 인스턴스 필드와 인스턴스 메소드로 바꾸어 정상적인 객체를 만드는 것입니다 오리엔테이션 기술이 적용됩니다. 주뿐만 아니라이 작업을 수행 할 시스템이 있다는 것을 : CSE 341 : 스몰 토크 클래스와 메타 클래스 ; 다음을 참조하십시오. 스몰 토크는 Java 정적과 동등한 것입니까?


합리적으로 잘 작동하려면 심각한 언어 기능 디자인을 수행해야한다고 말하고 싶습니다. 예를 들어, 순진한 접근 방식이 수행되었고 절름발이가 있었지만 매우 문제가 많았으며 불완전하고 사용하기 어려운 추상화를 제공함으로써 구조적으로 결함이있을 수 있습니다 (즉, 논쟁의 여지가 있습니다).

정적 재정의 기능을 훌륭하게 작동하도록 디자인 할 때 클래스 기반 메서드에 대한 OOP의 자연스러운 확장 인 일부 메타 클래스 형식을 발명했을 수 있습니다. 따라서이 작업을 수행하지 않을 이유가 없으며 일부 언어는 실제로 사용합니다. 아마도 많은 언어들이하지 않기로 선택하는 것이 조금 더 까다로운 요구 사항 일 것입니다.


내가 올바르게 이해한다면 : 이론적으로 당신이 이런 식으로하고 싶은지 여부는 중요하지 않지만 프로그래밍 방식으로 구현하는 것이 어렵고 반드시 유용하지는 않습니까?
PixelArtDragon

@Garan, 내 부록 참조.
Erik Eidt

1
나는 주문 인수를 사지 않습니다. 메소드 를 호출 한 클래스에서 제공 한 메소드 (또는 선택한 언어에 따라 메소드를 제공하는 첫 번째 수퍼 클래스)를 가져옵니다.
호브

1
@PierreArlaud : 그러나 this.instanceMethod()동적 해상도가 있으므로 self.staticMethod()동일한 해상도, 즉 동적이면 더 이상 정적 방법이 아닙니다.
Jörg W Mittag

1
정적 메소드에 대한 대체 시맨틱을 추가해야합니다. 가상의 Java + 메타 클래스는 아무것도 추가 할 필요가 없습니다! 클래스 객체를 만들고 정적 메소드를 일반 인스턴스 메소드로 만듭니다. 객체, 클래스 및 인스턴스 메소드와 같은 이미 존재하는 개념 만 사용하므로 언어에 무언가를 추가 할 필요가 없습니다. 보너스로 실제로 언어에서 정적 메소드 를 제거 할 수 있습니다! 언어에서 개념과 복잡성을 제거 하여 기능 을 추가 할 수 있습니다!
Jörg W Mittag

9

재정의는 가상 디스패치에 따라 다릅니다. this매개 변수 의 런타임 유형을 사용하여 호출 할 메소드를 결정하십시오. 정적 메서드에는 this매개 변수 가 없으므로 전달할 항목이 없습니다 .

델파이와 파이썬과 같은 일부 언어에는 클래스 메소드 라는 "중간"범위가 있습니다 . 클래스 메소드는 일반적인 인스턴스 메소드는 아니지만 정적이 아닙니다. 해당 유형의 인스턴스가 아닌 객체 유형 자체에 대한 참조 인 self매개 변수를 수신합니다 (두 언어 모두 this매개 변수를 호출 함 self). 이 값으로 가상 디스패치를 ​​수행 할 수있는 유형이 생겼습니다.

불행히도 JVM이나 CLR 모두 비슷한 것을 가지고 있지 않습니다.


self스몰 토크, 루비, 다트, 오브젝티브 -C, 셀프, 파이썬 등 대체 가능한 메소드를 가진 실제 인스턴스화 된 객체로 클래스 를 사용 하는 언어 입니까? C ++ 이후에 사용되는 언어와 비교할 때 리플렉션 액세스가 있어도 클래스가 일류 객체가 아닌 클래스는 무엇입니까?
Jerry101

분명히 말하면, 파이썬이나 델파이에서 클래스 메소드를 재정의 할 수 있다고 말하고 있습니까?
Erik Eidt

@ErikEidt 파이썬에서는 거의 모든 것이 무시할 수 있습니다. 델파이에서 클래스 메소드는 virtual인스턴스 메소드와 마찬가지로 명시 적으로 선언 된 후 하위 클래스에서 재정의 될 수 있습니다 .
메이슨 휠러

이 언어들은 일종의 메타 클래스 개념을 제공합니다.
Erik Eidt

1
@ErikEidt Python에는 "true"메타 클래스가 있습니다. 델파이에서 클래스 레퍼런스는 클래스 자체가 아닌 특별한 데이터 타입입니다.
메이슨 휠러

3

물어

정적 함수를 무시할 수없는 이유는 무엇입니까?

나는 묻습니다

재정의하려는 함수가 정적이어야하는 이유는 무엇입니까?

특정 언어는 정적 방법으로 쇼를 시작하도록합니다. 그러나 그 후에는 더 이상 정적 인 방법을 사용하지 않고도 많은 문제를 해결할 수 있습니다.

어떤 사람들은 객체의 상태에 의존하지 않을 때마다 정적 메서드를 사용하는 것을 좋아합니다. 어떤 사람들은 다른 객체를 만들 때 정적 방법을 사용하는 것을 좋아합니다. 어떤 사람들은 정적 방법을 최대한 피하는 것을 좋아합니다.

이 사람들 중 누구도 틀리지 않습니다.

재정의가 필요한 경우 정적 레이블을 중지하십시오. 무국적 물체가 날아 다니기 때문에 아무것도 깨지지 않습니다.


언어가 구조체를 지원하는 경우 단위 유형은 크기가 0 인 구조체 일 수 있으며 논리 입력 방법으로 전달됩니다. 해당 객체의 생성은 구현 세부 사항입니다. 우리가 구현 세부 사항을 실제 사실로 이야기하기 시작하면 실제 구현에서는 실제로 크기를 지정할 필요가 없습니다 (로드 할 명령이 필요 없기 때문에) 또는 저장).
Theodoros Chatzigiannakis

그리고 더 많은 "장면 뒤"(예 : 실행 레벨에서) 말하면 환경 인수는 언어의 유형으로 정의 될 수 있으며 프로그램의 "실제"진입 점은 인스턴스 메소드가 될 수 있습니다. OS 로더가 프로그램에 전달한 모든 인스턴스에서 작동하는 해당 유형.
Theodoros Chatzigiannakis

나는 당신이 그것을 어떻게 보는가에 달려 있다고 생각합니다. 예를 들어, 프로그램이 시작될 때 OS는 프로그램을 메모리에로드 한 다음 프로그램의 이진 진입 점에 대한 개별 구현 (예 : _start()Linux 의 기호) 이있는 주소로 이동합니다 . 이 예제에서 실행 파일은 객체 (자체 "디스패치 테이블"및 모든 것을 가짐)이며 진입 점은 동적으로 디스패치 된 작업입니다. 따라서 프로그램의 진입 점은 본질적으로 외부에서 볼 때 가상이며 내부에서 볼 때 쉽게 가상으로 보이도록 만들 수 있습니다.
Theodoros Chatzigiannakis

1
"무국적 물체가 날아 다니기 때문에 아무것도 깨지지 않습니다." ++++++
RubberDuck

@TheodorosChatzigiannakis 당신은 강한 논쟁을합니다. 당신이 저를 설득하지 못한 것이 없다면 그 선은 내가 의도 한 선을 고무시키지 않습니다. 나는 사람들에게 정적 방식과 맹목적으로 관련되는 습관적인 관행 중 일부를 으깨도록 허락하려고 노력했다. 그래서 업데이트했습니다. 생각?
candied_orange

2

정적 메서드를 재정의 할 수없는 이유는 무엇입니까?

"should"의 문제는 아닙니다.

"재정의"는 " 동적 디스패치"를 의미 합니다. "정적 방법"은 "정적으로 디스패치"를 의미합니다. 정적 인 경우 재정의 할 수 없습니다. 재정의 할 수있는 것은 정적 인 것이 아닙니다.

당신의 질문은 "왜 세발 자전거가 네 바퀴를 가질 수 없는가?"라고 묻는 것과 비슷하다. "세발 자전거" 의 정의 는 3 개의 바퀴가 있다는 것입니다. 세발 자전거이면 바퀴가 네 개가 될 수없고, 네 바퀴가 있으면 세발 자전거가 될 수 없습니다. 마찬가지로 "정적 메소드" 의 정의 는 정적으로 디스패치된다는 것입니다. 정적 메서드 인 경우 동적으로 디스패치 할 수없고 동적으로 디스패치 할 수 있으면 정적 메서드가 될 수 없습니다.

물론 재정의 할 수있는 클래스 메서드 를 갖는 것이 가능합니다 . 또는 Ruby와 같은 언어를 사용할 수 있습니다. 여기서 클래스는 다른 객체와 마찬가지로 객체이므로 인스턴스 메소드를 가질 수 있으므로 클래스 메소드가 전혀 필요하지 않습니다. (Ruby에는 한 가지 종류의 메소드가 있습니다 : 인스턴스 메소드. 클래스 메소드, 정적 메소드, 생성자, 함수 또는 프로시 저는 없습니다.)


1
"정의에 의해"잘 넣어.
candied_orange

1

따라서 C #의 정적 함수는 가상 또는 추상 일 수 없습니다

C #에서는 항상 클래스를 사용하여 정적 멤버를 호출합니다 (예 : BaseClass.StaticMethod()not) baseObject.StaticMethod(). 따라서 결국 및 의 인스턴스를 ChildClass상속받은 경우에서 정적 메소드를 호출 할 수 없습니다 . 항상 실제 클래스를 명시 적으로 사용해야하므로 이해가되지 않습니다.BaseClasschildObjectChildClasschildObjectstatic virtual

당신이 할 수있는 일은 static자녀 클래스에서 동일한 방법을 재정의 하고 new키워드를 사용하는 것 입니다.

class BaseClass {
    public static int StaticMethod() { return 1; }
}

class ChildClass {
    public static new int StaticMethod() { return BaseClass.StaticMethod() + 2; }
}

int result;    
var baseObj = new BaseClass();
var childObj = new ChildClass();

result = BaseClass.StaticMethod();
result = baseObj.StaticMethod(); // DOES NOT COMPILE

result = ChildClass.StaticMethod();
result = childObj.StaticMethod(); // DOES NOT COMPILE

에 전화 할 수 있다면 baseObject.StaticMethod()귀하의 질문이 의미가 있습니다.

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