Bräcklig basklass

Det bräckliga basklassproblemet är ett grundläggande arkitektoniskt problem för objektorienterade programmeringssystem där basklasser ( superklasser ) anses vara "bräckliga" eftersom till synes säkra modifieringar av en basklass, när de ärvs av de härledda klasserna , kan orsaka att de härledda klasserna inte fungerar . Programmeraren kan inte avgöra om en förändring av basklassen är säker genom att enbart undersöka basklassens metoder.

En möjlig lösning är att göra instansvariabler privata för sin definierande klass och tvinga underklasser att använda accessorer för att modifiera superklasstillstånd. Ett språk skulle också kunna göra det så att underklasser kan styra vilka ärvda metoder som exponeras offentligt. Dessa ändringar förhindrar underklasser från att förlita sig på implementeringsdetaljer för superklasser och tillåter underklasser att exponera endast de superklassmetoder som är tillämpliga på dem själva.

En annan alternativ lösning kan vara att ha ett gränssnitt istället för superklass.

Det bräckliga basklassproblemet har skyllts på öppen rekursion (dynamisk sändning av metoder på detta ), med förslaget att anropa metoder på denna standard till sluten rekursion (statisk sändning, tidig bindning) snarare än öppen rekursion (dynamisk sändning, sen bindning) , endast med öppen rekursion när det är specifikt efterfrågat; externa samtal (som inte använder detta ) skulle skickas dynamiskt som vanligt.

Java exempel

Följande triviala exempel är skrivet i programmeringsspråket Java och visar hur en till synes säker modifiering av en basklass kan orsaka att en ärvd underklass inte fungerar genom att gå in i en oändlig rekursion som kommer att resultera i ett stackspill .

  

      0

    
    
  

    
    
  



    

  
    
    
  

 klass  Super  {  privat  int  räknare  =  ;  void  inc1  ()  {  counter  ++  ;  }  void  inc2  ()  {  counter  ++  ;  }  }  klass  Sub  utökar  Super  {  @Override  void  inc2  ()  {  inc1  ();  }  } 

Att anropa den dynamiskt bundna metoden inc2() på en instans av Sub kommer korrekt att öka fälträknaren med ett. Om dock koden för superklassen ändras på följande sätt:

  

      0

    
    
  

    
    
  
 klass  Super  {  privat  int  räknare  =  ;  void  inc1  ()  {  inc2  ();  }  void  inc2  ()  {  counter  ++  ;  }  } 

ett anrop till den dynamiskt bundna metoden inc2() på en instans av Sub kommer att orsaka en oändlig rekursion mellan sig själv och metoden inc1() för superklassen och till slut orsaka ett stackspill. Detta problem kunde ha undvikits genom att deklarera metoderna i superklassen som final , vilket skulle göra det omöjligt för en underklass att åsidosätta dem. Detta är dock inte alltid önskvärt eller möjligt. Därför är det god praxis för superklasser att undvika att ändra anrop till dynamiskt bundna metoder.

Lösningar

  • Objective-C har kategorier såväl som icke-bräckliga instansvariabler .
  • Komponent Pascal fasar ut superklassanrop .
  • Java , C++ (Sedan C++11) och D tillåter att nedärvning eller åsidosättande av en klassmetod förbjuds genom att märka en deklaration av en respektive klass eller metod med nyckelordet " final ". I boken Effective Java skriver författaren Joshua Bloch (i punkt 17) att programmerare ska "Designa och dokumentera för arv eller annars förbjuda det".
  • C# och VB.NET som Java har " förseglade " och " No Inheritable " klassdeklarationsnyckelord för att förbjuda arv, och kräver en underklass för att använda nyckelordet " override " på åsidosättande metoder, samma lösning som senare antogs av Scala.
  • Scala kräver en underklass för att använda nyckelordet " åsidosätta " uttryckligen för att åsidosätta en överordnad klassmetod. I boken "Programmering i Scala, 2nd Edition" skriver författaren att (med modifieringar här) om det inte fanns någon metod f(), kunde klientens ursprungliga implementering av metod f() inte ha haft en överstyrningsmodifierare. När du väl lägger till metoden f() till den andra versionen av din biblioteksklass, skulle en omkompilering av klientkoden ge ett kompileringsfel istället för fel beteende.
  • I Kotlin är klasser och metoder slutgiltiga som standard. För att aktivera klassarv bör klassen markeras med den öppna modifieraren. Likaså bör en metod markeras som öppen för att tillåta åsidosättande av metoden.
  • Julia tillåter endast subtypning av abstrakta typer och använder komposition som ett alternativ till arv . Den har dock flera utskick .

Se även

externa länkar