Cum se gestionează inițializarea variabilelor în circumstanțe specificate

Am o clasă a cărei constructor arată cam așa:

abstract class BasePanel extends JPanel {
  public BasePanel(A a) {
   //initializing fields from values passed to ctor
    this.a = a;
   //initializing gui components
    initializeComponents();
    setupPanels();
    concludeUiSetup();
  }

 //stuff
}

În constructor, sunt inițializate câmpurile care urmează să fie inițializate cu valori transmise constructorului. Apoi, alte metode necesare pentru configurarea UI sunt numite în ordine. Două dintre aceste metode trebuie suprimate în subclasă pentru configurarea UI specifică acestora.

Acum, luați în considerare o clasă FooPanel care extinde BasePanel . Este nevoie de câțiva parametri de inițializare în constructorul său.

class FooPanel extends BasePanel {
  public FooPanel(A a, B b) {
    super(a);
    this.b = b;
  }

  @Override
  public void initializeComponents() {
    super.initializeComponents();
   //I require b here, but oops, b is not initialized at this point, and so 
   //this will throw NPE.
    someTextField.setText(b.get());
  }

 //stuff
} 

initializeComponents method here requires b, which unfortunately is not initialized at that point.

Care ar fi modul potrivit de a restructura acest cod astfel încât:

  • câmpurile necesare sunt setate înainte de a fi necesare.
  • codul care utilizează FooPanel (și alte panouri) nu este aglomerat prea mult de această modificare.

Orice ajutor foarte apreciat. Mulțumiri.

1
erm, aveți o metodă init() pentru a inițializa lucrurile? Și prin principiul coeziunii mari, init() se potrivește cu suma licitată?
adăugat autor Kazekage Gaara, sursa
Și cred că ar trebui să te uiți la acest răspuns . Se spune că "constructorii nu trebuie să invocă metode suprascriere, direct sau indirect".
adăugat autor Kazekage Gaara, sursa
a întâlnit aceeași eroare în Java, nu se întâmplă în C# deși: stackoverflow.com/questions/10611726/…
adăugat autor Hao, sursa
@KazekageGaara, mulțumesc pentru răspunsul la întrebarea lui Hunter pentru mine, și mulțumesc pentru link-ul firului. Apropo, eu sunt, de asemenea, un fan Naruto. :)
adăugat autor missingfaktor, sursa
@BheshGurung, mulțumesc. Cred că ar fi mai potrivit ca răspuns.
adăugat autor missingfaktor, sursa
@ Brady, mulțumesc. Cred că ar fi mai potrivit ca răspuns.
adăugat autor missingfaktor, sursa
de ce nu este inițializat b ? acesta trece în interiorul constructorului.
adăugat autor Hunter McMillen, sursa
@assylias Da, și care inițializează câmpurile superclasei. B nu ar trebui să fie inițializat în superclaj, deoarece nu este membru acolo
adăugat autor Hunter McMillen, sursa
@HunterMcMillen FooPanel (a, b) -> super (a) -> #initializecomponente -> NPE
adăugat autor assylias, sursa
@HunterMcMillen deoarece initializeComponents este numit de super (a) înainte de a fi setat b.
adăugat autor assylias, sursa
@missingfaktor, dacă sugerăm să introduc un alt răspuns, cred că KazekageGaara ar trebui să obțină creditul, de vreme ce el a spus-o mai întâi, tocmai l-am formalizat :)
adăugat autor Brady, sursa
Ce a spus @KazekageGaara, folosiți constructorii pentru a seta valorile, apoi inițiați apelând o metodă init ().
adăugat autor Brady, sursa
Sunt mereu prudent când văd codul încercând să utilizeze moștenirea pe un widget GUI. Acesta este un lucru foarte dificil de făcut și este adesea o utilizare abuzivă a moștenirii. Nu spun că este cazul aici, dar dacă aș fi fost cel care va revizui codul dvs., ar fi declanșat alarme și aș vrea să văd ceva care să justifice acest lucru.
adăugat autor Hovercraft Full Of Eels, sursa
Nu trebuie să apelați o metodă suprascriptibilă în constructor.
adăugat autor Bhesh Gurung, sursa

2 răspunsuri

Nu trebuie să apelați metode suprascriptibile de la un constructor. Ceea ce ar trebui să faceți în acest caz este definirea unui constructor care inițializează numai câmpurile de instanță și pune inițializarea GUI într-o metodă initialize() suprascriptibilă, care nu este apelată de la constructor.

Deci, pentru a construi un FooPanel, faci:

FooPanel p = new FooPanel(a, b);
p.initialize();

Și dacă nu îi forțați pe toți clienții FooPanel să facă acest lucru, definiți constructorul privat și oferiți o metodă din fabrică:

public static FooPanel create(A a, B b) {
    FooPanel p = new FooPanel(a, b);
    p.initialize();
    return p;
}
7
adăugat
Mulțumesc. În mod independent am venit cu aceeași soluție. Se pare că, în acest context, nu se poate face ceva mai curat cu Java.
adăugat autor missingfaktor, sursa

Practic, încercați să evitați să apelați metode virtuale (adică suprascriere) în cadrul constructorilor. Cauzează exact acest tip de problemă. Dacă intenționați să apelați o metodă virtuală într-un constructor, trebuie să o documentați - și, eventual, să evitați să o numiți în altă parte. O astfel de metodă trebuie scrisă pentru a se ocupa de faptul că obiectul nu este complet inițializat încă, ceea ce îl pune într-un loc ciudat.

Este greu să știu sfaturi mai specifice pentru a da mai multă informație, dar aș încuraja să îmbrățișați compoziția asupra moștenirii acolo unde este posibil - sau cel puțin întotdeauna să o ia în considerare și să decideți despre cea mai elegantă abordare.

Dacă vrei cu adevărat moștenire aici, chiar ai nevoie de initializeComponents deloc? Nu poate fiecare clasă să își inițieze propria sa constructoră, fără să se bazeze pe nimic din starea sa de subclasă?

5
adăugat
Acest lucru va necesita fiecare clasă să apeleze la aceste metode în ordine, provocând astfel duplicarea evidentă și făcând codul mai predispus la erori. Cred că eu merg cu soluția lui JB aici.
adăugat autor missingfaktor, sursa
Vă mulțumim pentru sfaturile privind metodele suprascriptibile. În ceea ce privește compoziția obiectului, uitând la imaginea de ansamblu (care nu este descrisă în postul meu), ar fi o modalitate foarte greșită de a face acest lucru. Re: initializeComponents , clasele au multe in comun si evitand moștenirea aici ar cauza o multiplicare de duplicare a codului.
adăugat autor missingfaktor, sursa
@missingfaktor: Bine, poate doriți să folosiți moștenirea - dar asta nu explică încă de ce trebuie să inițializați totul într-o metodă suprascrisă. De ce nu poate fiecare clasă să efectueze propria inițializare în cadrul constructorului său (sau o metodă privată chemată direct de la constructorul său)? După cum vă spun, abordarea pe care o aveți este pur și simplu să vă provoace probleme. Este un anti-model cunoscut, și este aproape niciodată cea mai bună abordare.
adăugat autor Jon Skeet, sursa
@missingfaktor: Fiecare clasă trebuie doar să descopere modul de inițializare a ei însuși . Veți obține complexitate locală care este ușor de explicat. Comparați acest lucru cu forțând fiecare apelantului să sune la initialize înainte de a face orice - ceea ce duce la complexitatea globală. Chiar îmi displace clasele care nu sunt valabile după revenirea constructorului. Ick. Dar e chemarea ta, desigur ...
adăugat autor Jon Skeet, sursa