programing

컨스트럭터에서 덮어쓸 수 있는 메서드 호출에 문제가 있습니까?

yoursource 2022. 8. 14. 13:02
반응형

컨스트럭터에서 덮어쓸 수 있는 메서드 호출에 문제가 있습니까?

추상적인 방법의 결과에 따라 페이지 제목을 설정하는 Wicket 페이지 클래스가 있습니다.

public abstract class BasicPage extends WebPage {

    public BasicPage() {
        add(new Label("title", getTitle()));
    }

    protected abstract String getTitle();

}

NetBeans는 "Overridable method call in constructor"라는 메시지로 경고합니다만, 어떤 문제가 있을까요?내가 상상할 수 있는 유일한 대안은 추상적인 메서드의 결과를 서브클래스의 슈퍼 컨스트럭터에게 전달하는 것이다.하지만 많은 매개변수로 인해 읽기 어려울 수 있습니다.

생성자에서 재정의 가능한 메서드를 호출하는 경우

간단히 말해서, 이것은 많은 버그에게 불필요하게 가능성을 열어주기 때문에 잘못된 것입니다.언제?@Override이 호출되면 오브젝트 상태가 부정합하거나 불완전할 수 있습니다.

유효한 Java 2nd Edition, 항목 17: 상속을 위한 설계문서화 또는 금지:

클래스가 상속을 허용하기 위해 준수해야 하는 몇 가지 제한이 더 있습니다.생성자는 재정의 가능한 메서드를 직접 또는 간접적으로 호출해서는 안 됩니다.이 규칙을 위반하면 프로그램 오류가 발생합니다.슈퍼 클래스 생성자는 하위 클래스 생성자보다 먼저 실행되므로 하위 클래스 생성자가 실행되기 전에 하위 클래스의 재정의 메서드가 호출됩니다.덮어쓰기 메서드가 하위 클래스 생성자에 의해 수행된 초기화에 따라 달라지는 경우 메서드는 예상대로 작동하지 않습니다.

다음은 예를 제시하겠습니다.

public class ConstructorCallsOverride {
    public static void main(String[] args) {

        abstract class Base {
            Base() {
                overrideMe();
            }
            abstract void overrideMe(); 
        }

        class Child extends Base {

            final int x;

            Child(int x) {
                this.x = x;
            }

            @Override
            void overrideMe() {
                System.out.println(x);
            }
        }
        new Child(42); // prints "0"
    }
}

「」, 「」의 경우Base 콜 " " "overrideMe,Child은 아직 .final int x메서드가 잘못된 값을 가져옵니다.이는 거의 틀림없이 버그와 오류로 이어질 것입니다.

관련 질문

「 」를 참조해 주세요.


매개 변수가 많은 개체 구성

매개 변수가 많은 생성자는 가독성을 저하시킬 수 있으며 더 나은 대안이 존재합니다.

다음은 Effective Java 2nd Edition, Item 2의 인용문입니다. 많은 컨스트럭터 파라미터에 직면했을 때 빌드 패턴을 고려하십시오.

전통적으로 프로그래머는 텔레스코핑 컨스트럭터 패턴을 사용해 왔습니다.이 패턴에서는 필요한 파라미터만 컨스트럭터에 제공하고, 다른 파라미터는 1개의 옵션 파라미터로, 다른 파라미터는 2개의 옵션 파라미터로 제공하고 있습니다.

텔레스코핑 컨스트럭터 패턴은 기본적으로 다음과 같습니다.

public class Telescope {
    final String name;
    final int levels;
    final boolean isAdjustable;

    public Telescope(String name) {
        this(name, 5);
    }
    public Telescope(String name, int levels) {
        this(name, levels, false);
    }
    public Telescope(String name, int levels, boolean isAdjustable) {       
        this.name = name;
        this.levels = levels;
        this.isAdjustable = isAdjustable;
    }
}

이제 다음 중 하나를 수행할 수 있습니다.

new Telescope("X/1999");
new Telescope("X/1999", 13);
new Telescope("X/1999", 13, true);

,, 재, 음, 음, 음, 음, 음, 음, 음, 음, 음, 음, 음, 음, 음, 음, 음, 음, 음, 음, 음, 음, 음, 음, you the, ,name ★★★★★★★★★★★★★★★★★」isAdjustable, leaving, leaving, 。levels수 있지만 수가 그 수가 하고 여러 개의 컨스트럭터 오버로드를 가질 있습니다.boolean ★★★★★★★★★★★★★★★★★」int모든 것을 엉망으로 만들 수 있는 논쟁들

보시다시피, 이것은 쓰기 좋은 패턴이 아닙니다.또, 사용의 즐거움도 떨어집니다(여기서 "true"는 무엇을 의미합니까?)13이 뭐죠?)

Bloch는 빌더 패턴을 사용할 것을 권장합니다.이 패턴을 사용하면 다음과 같은 내용을 대신 쓸 수 있습니다.

Telescope telly = new Telescope.Builder("X/1999").setAdjustable(true).build();

이제 파라미터에 이름이 지정되고 원하는 순서로 설정할 수 있으며 기본값으로 유지할 파라미터는 건너뛸 수 있습니다.이것은 텔레스코프 컨스트럭터보다 훨씬 낫습니다.특히 같은 타입의 파라미터가 다수 존재하는 경우에는 더욱 그렇습니다.

「 」를 참조해 주세요.

  • 위키백과/빌더 패턴
  • 효과적인 Java 2nd Edition, Item 2: 많은 컨스트럭터 파라미터(온라인 제외)에 직면했을 빌더 패턴을 고려합니다.

관련 질문

이를 이해하는 데 도움이 되는 예를 다음에 제시하겠습니다.

public class Main {
    static abstract class A {
        abstract void foo();
        A() {
            System.out.println("Constructing A");
            foo();
        }
    }

    static class C extends A {
        C() { 
            System.out.println("Constructing C");
        }
        void foo() { 
            System.out.println("Using C"); 
        }
    }

    public static void main(String[] args) {
        C c = new C(); 
    }
}

이 코드를 실행하면 다음과 같은 출력이 표시됩니다.

Constructing A
Using C
Constructing C

그??foo()는 C의 컨스트럭터를 실행하기 전에 C를 사용합니다. iffoo()에서는 C가 정의된 상태(즉, 컨스트럭터가 종료)여야 합니다.그러면 C에서 정의되지 않은 상태가 발생하게 되어 파손될 수 있습니다.A에서는 무엇을 덮어썼는지 알 수 없기 때문에foo()예상합니다. 경고를 받습니다.

생성자에서 재정의 가능한 메서드를 호출하면 하위 클래스가 코드를 전복시킬 수 있으므로 더 이상 작동하지 않습니다.그래서 경고를 받는 거야

예에서는 가 「」를 덮어쓰면 ?getTitle()null을할 수 ?

이를 "수정"하기 위해 컨스트럭터 대신 팩토리 메서드를 사용할 수 있습니다.이것은 오브젝트 설치의 일반적인 패턴입니다.

다음은 슈퍼 컨스트럭터에서 재정의 가능한 메서드를 호출할 때 발생할 수 있는 논리적 문제를 보여주는 예입니다.

class A {

    protected int minWeeklySalary;
    protected int maxWeeklySalary;

    protected static final int MIN = 1000;
    protected static final int MAX = 2000;

    public A() {
        setSalaryRange();
    }

    protected void setSalaryRange() {
        throw new RuntimeException("not implemented");
    }

    public void pr() {
        System.out.println("minWeeklySalary: " + minWeeklySalary);
        System.out.println("maxWeeklySalary: " + maxWeeklySalary);
    }
}

class B extends A {

    private int factor = 1;

    public B(int _factor) {
        this.factor = _factor;
    }

    @Override
    protected void setSalaryRange() {
        this.minWeeklySalary = MIN * this.factor;
        this.maxWeeklySalary = MAX * this.factor;
    }
}

public static void main(String[] args) {
    B b = new B(2);
    b.pr();
}

결과는 다음과 같습니다.

최소 주급: 0

max Weekly Salary: 0

이는 클래스 B의 생성자가 먼저 클래스 A의 생성자를 호출하고 여기서 B 내부의 재정의 가능한 메서드가 실행되기 때문입니다.그러나 메서드 내부에서는 아직 초기화되지 않은 인스턴스 변수 계수를 사용하고 있습니다(A의 생성자가 아직 완료되지 않았기 때문에). 따라서 계수는 0이고 1이 아니며 2가 아닙니다(프로그래머가 생각할 수 있는 것).계산 논리가 10배 이상 꼬이면 오류를 추적하는 것이 얼마나 어려울지 상상해 보십시오.

그게 누군가에게 도움이 됐으면 좋겠어요.

컨스트럭터에서 서브클래스가 덮어쓰는 메서드를 호출하면 초기화를 컨스트럭터와 메서드 간에 논리적으로 분할하면 아직 존재하지 않는 변수가 참조될 가능성이 낮아집니다.

이 링크의 샘플은, http://www.javapractices.com/topic/TopicAction.do?Id=215 를 참조해 주세요.

Wicket의 구체적인 경우:이것이 바로 Wicket 개발자에게 컴포넌트를 구성하는 프레임워크의 라이프 사이클에서 명시적인 2상 컴포넌트 초기화 프로세스의 지원을 추가하도록 의뢰한 이유입니다.

  1. 시공 - 시공자 경유
  2. 초기화 - onInitilize 경유(가상 메서드가 동작할 때 구축 후!)

이 링크가 http://apache-wicket.1842946.n4.nabble.com/VOTE-WICKET-3218-Component-onInitialize-is-broken-for-Pages-td3341090i20.html)를 나타내듯이, 이것이 필요한지 아닌지에 대해서는 꽤 활발한 논의가 있었습니다(IMHO가 완전히 필요).

좋은 소식은 Wicket의 우수한 개발자들이 결국 2단계 초기화를 도입했다는 것입니다(가장 놀라운 Java UI 프레임워크를 더욱 훌륭하게 만들기 위해). 따라서 Wicket을 사용하면 프레임워크에 의해 자동으로 호출되는 onInitialize 메서드로 모든 포스트 구축 초기화를 수행할 수 있습니다(이 시점에서).구성 요소의 ecycle 생성자가 작업을 완료하여 가상 메서드가 예상대로 작동합니다.

위켓은 전화하는 게 나을 것 같아add의 메서드onInitialize()(컴포넌트 라이프 사이클 참조):

public abstract class BasicPage extends WebPage {

    public BasicPage() {
    }

    @Override
    public void onInitialize() {
        add(new Label("title", getTitle()));
    }

    protected abstract String getTitle();
}

건설업자에게서 몇 가지 방법을 부르지 않는 것이 좋을 수도 있다는 것에 나는 확실히 동의한다.

그들을 사적으로 만드는 것은 모든 의심을 없애준다: "당신은 합격할 수 없다."

그러나 열린 상태를 유지하려면 어떻게 해야 합니까?

여기서 설명했듯이 진짜 문제는 액세스 수식어뿐만이 아닙니다.솔직히 말해서private쇼스토퍼의 확실한 존재입니다.protected에서는, 통상, 회피책(예외)을 사용할 수 있습니다.

보다 일반적인 조언:

  • 생성자에서 스레드 시작 안 함
  • 컨스트럭터의 파일을 읽지 않음
  • 컨스트럭터에서 API나 서비스를 호출하지 마십시오.
  • 생성자에서 데이터베이스로 데이터 로드 안 함
  • 생성자에서 json 또는 xml 문서를 구문 분석하지 않음

컨스트럭터에서 직접 그렇게 하지 마십시오.여기에는 컨스트럭터에 의해 호출되는 개인/보호 함수에서 이러한 작업 중 하나를 수행하는 것이 포함됩니다.

호출 중start()컨스트럭터의 메서드는 레드 플래그가 될 수 있습니다.

대신, 당신은 공공 서비스를 제공해야 합니다. init(),start()또는connect()방법.그리고 책임을 소비자에게 떠넘긴다.

간단히 말하면, 「준비」의 순간과 「점화」의 순간을 분리하고 싶다고 하는 것입니다.

  • 컨스트럭터를 확장할 수 있다면 스스로 설계해서는 안 됩니다.
  • 자체 발화하면 완전히 구축되기 전에 발사될 위험이 있습니다.
  • 결국, 언젠가 하위 클래스의 생성자에 더 많은 준비가 추가될 수 있습니다.그리고 당신은 슈퍼클래스의 제작자의 실행 순서를 통제할 수 없습니다.

PS: Closeable 인터페이스와 함께 실장을 검토해 주십시오.

언급URL : https://stackoverflow.com/questions/3404301/whats-wrong-with-overridable-method-calls-in-constructors

반응형