Flytande gränssnitt

Inom mjukvaruteknik är ett flytande gränssnitt ett objektorienterat API vars design i stor utsträckning bygger på metodkedja . Dess mål är att öka kodläsbarheten genom att skapa ett domänspecifikt språk ( DSL). Termen myntades 2005 av Eric Evans och Martin Fowler .

Genomförande

Ett flytande gränssnitt implementeras normalt genom att använda metodkedjning för att implementera metodkaskadning (på språk som inte har stöd för kaskadkoppling), konkret genom att låta varje metod returnera objektet som det är kopplat till, ofta kallat detta eller själv . Mer abstrakt uttryckt, ett flytande gränssnitt vidarebefordrar instruktionskontexten för ett efterföljande anrop i metodkedja, där kontexten generellt är

  • Definieras genom returvärdet för en anropad metod
  • Självrefererande , där det nya sammanhanget är likvärdigt med det sista sammanhanget
  • Avslutas genom återlämnande av ett tomt sammanhang

Observera att ett "flytande gränssnitt" betyder mer än bara metodkaskad via kedja; det innebär att designa ett gränssnitt som läser som en DSL, med andra tekniker som "kapslade funktioner och objektomfattning".

Historia

Termen "flytande gränssnitt" myntades i slutet av 2005, även om den här övergripande gränssnittsstilen dateras till uppfinningen av metoden kaskad i Smalltalk på 1970-talet, och många exempel på 1980-talet. Ett vanligt exempel är iostream- biblioteket i C++ , som använder operatorerna << eller >> för att skicka meddelandet, skicka flera data till samma objekt och tillåta "manipulatorer" för andra metodanrop. Andra tidiga exempel inkluderar Garnet-systemet (från 1988 i Lisp) och Amulet-systemet (från 1994 i C++) som använde denna stil för att skapa objekt och tilldela egenskaper.

Exempel

C#

C# använder i stor utsträckning flytande programmering i LINQ för att skapa frågor med "standard frågeoperatorer". Implementeringen baseras på förlängningsmetoder .

     

     
     
     
     




   
	  
	  
	  


       
           
           var  translations  =  new  Dictionary  <  string  ,  string  >  {  {  "cat"  ,  "chat"  },  {  "dog"  ,  "chien"  },  {  "fish"  ,  "poisson"  },  {  "bird"  ,  "oiseau"  }  };  // Hitta översättningar för engelska ord som innehåller bokstaven "a",  // sorterade efter längd och visas med versaler  IEnumerable  <  string  >  query  =  translations  .  Där  (  t  =>  t  .  Nyckel  .  Innehåller  (  "a"  ))  .  OrderBy  (  t  =>  t  .  Value  .  Length  )  .  Välj  (  t  =>  t  .  Värde  .  Till Övre  ());  // Samma fråga konstruerad successivt:  var  filtered  =  translations  .  Där  (  t  =>  t  .  Nyckel  .  Innehåller  (  "a")  );  var  sorterad  =  filtrerad  .  OrderBy  (  t  =>  t  .  Value  .  Length  );  var  finalQuery  =  sorterad  .  Välj  (  t  =>  t  .  Värde  .  Till Övre  ()); 

Flytande gränssnitt kan också användas för att kedja en uppsättning metoder som driver/delar samma objekt. Istället för att skapa en kundklass kan vi skapa en datakontext som kan dekoreras med ett flytande gränssnitt enligt följande.


 

          
          
          
          


 

          

    
       
    
          
         
    

       
    
          
         
    

       
    
          
         
    

       
    
          
         
    

    
      
    
        
    


 

       
    
        
            
        
        
    
 //  Definierar datakontextklassen  Context  {  public  string  FirstName  {  get  ;  set  ;  }  offentlig  sträng  Efternamn  {  get  ;  set  ;  }  offentlig  sträng  Sex  {  get  ;  set  ;  }  public  string  Adress  {  get  ;  set  ;  }  }  class  Customer  {  private  Context  _context  =  new  Context  ();  // Initierar sammanhanget  // ställer in värdet för egenskaper  public  Customer  FirstName  (  string  firstName  )  {  _context  .  Förnamn  =  förnamn  ;  returnera  detta  ;  }  public  Customer  LastName  (  sträng  efternamn  )  {  _context  .  Efternamn  =  efternamn  ;  returnera  detta  ;  }  offentligt  kundsex  (  strängsex  )  {  _context  .  _  _  Sex  =  sex  ;  returnera  detta  ;  }  offentlig  kundadress  (  strängadress  )  {  _context  .  _  _  Adress  =  adress  ;  returnera  detta  ;  }  // Skriver ut data till konsol  public  void  Skriv ut  ()  {  Console  .  WriteLine  (  $"Förnamn: {_context.FirstName} \nEfternamn: {_context.LastName} \nKön: {_context.Sex} \nAdress: {_context.Address}" )  ;  }  }  class  Program  {  static  void  Main  (  string  []  args  )  {  // Objektskapande  Kund  c1  =  ny  kund  ();  // Använda metoden chaining för att tilldela och skriva ut data med en enda rad  c1  .  Förnamn  (  "vinod"  ).  Efternamn  (  "srivastav"  ).  Sex  (  "man"  ).  Adress  (  "bangalore"  ).  Skriv ut  ();  }  } 

C++

En vanlig användning av det flytande gränssnittet i C++ är standarden iostream , som kedjer överbelastade operatörer .

Följande är ett exempel på att tillhandahålla ett flytande gränssnitt ovanpå ett mer traditionellt gränssnitt i C++:

 
   
 
           
      
      
 
         
           
           
     
        
           
     
       
          
     
          
           
           
     
          
           
           
     
         
           
     
      
 
 
      
       
      
       
      
     
     
 

 
      
 
             
       
           
          
     
       
           
          
     
       
           
          
     
       
           
          
     
          
          
          
     
          
          
          
     
         
         
          
     
     
       
         
     
 
 
      
      
         
           
         
         
  // Basic definition  class  GlutApp  {  private  :  int  w_  ,  h_  ,  x_  ,  y_  ,  argc_  ,  display_mode_  ;  char  **  argv_  ;  char  *  title_  ;  public  :  GlutApp  (  int  argc  ,  char  **  argv  )  {  argc_  =  argc  ;  argv_  =  argv  ;  }  void  setDisplayMode  (  int  mode  )  {  display_mode_  =  mode  ;  }  int  getDisplayMode  ()  {  return  display_mode_  ;  }  void  setWindowSize  (  int  w  ,  int  h  )  {  w_  =  w  ;  h_  =  h  ;  }  void  setWindowPosition  (  int  x  ,  int  y  )  {  x_  =  x  ;  y_  =  y  ;  }  void  setTitle  (  const  char  *  title  )  {  title_  =  title  ;  }  void  skapa  (){;}  };  // Grundläggande användning  int  main  (  int  argc  ,  char  **  argv  )  {  GlutApp  app  (  argc  ,  argv  );  app  .  setDisplayMode  (  GLUT_DOUBLE  |  GLUT_RGBA  |  GLUT_ALPHA  |  GLUT_DEPTH  );  // Ställ in framebuffer params  app  .  setWindowSize  (  500  ,  500  );  // Ställ in fönsterparametrar  app  .  setWindowPosition  (  200  ,  200  );  app  .  setTitle  (  "Min OpenGL/GLUT-app" )  ;  app  .  skapa  ();  }  // Fluent wrapper  class  FluentGlutApp  :  privat  GlutApp  {  public  :  FluentGlutApp  (  int  argc  ,  char  **  argv  )  :  GlutApp  (  argc  ,  argv  )  {}  // Ärv överordnad konstruktör  FluentGlutApp  &  withDoubleBuffer  ()  {  getDisplayUTMode  ( ) (   setDisplay  |  GLUBMode  ()  );  returnera  *  detta  ;  }  FluentGlutApp  &  withRGBA  ()  {  setDisplayMode  (  getDisplayMode  ()  |  GLUT_RGBA  );  returnera  *  detta  ;  }  FluentGlutApp  &  withAlpha  ()  {  setDisplayMode  (  getDisplayMode  ()  |  GLUT_ALPHA  );  returnera  *  detta  ;  }  FluentGlutApp  &  withDepth  ()  {  setDisplayMode  (  getDisplayMode  ()  |  GLUT_DEPTH  );  returnera  *  detta  ;  }  FluentGlutApp  &  tvärs  (  int  w  ,  int  h  )  {  setWindowSize  (  w  ,  h  );  returnera  *  detta  ;  }  FluentGlutApp  &  at  (  int  x  ,  int  y  )  {  setWindowPosition  (  x  ,  y  );  returnera  *  detta  ;  }  FluentGlutApp  &  named  (  const  char  *  title  )  {  setTitle  (  title  );  returnera  *  detta  ;  }  // Det är inte meningsfullt att kedja efter create(), så returnera inte *this  void  create  ()  {  GlutApp  ::  create  ();  }  };  // Flytande användning  int  main  (  int  argc  ,  char  **  argv  )  {  FluentGlutApp  (  argc  ,  argv  )  .  withDoubleBuffer  ().  medRGBA  ().  med Alpha  ().  withDepth  ()  .  vid  (  200  ,  200  ).  tvärsöver  (  500  ,  500  )  .  heter  (  "Min OpenGL/GLUT-app"  )  .  skapa  ();  } 

Java

Ett exempel på en flytande testförväntning i jMock-testramverket är:

 
                                            håna  .  förväntar sig  (  en gång  ()).  metod  (  "m"  ).  med  (  eller  (  stringContains  (  "hej"  ),  stringContains  (  "hej" ))  )  ; 

jOOQ - biblioteket modellerar SQL som ett flytande API i Java:

   

      
                   
                   
                    Författare  författare  =  FÖRFATTARE  .  som  (  "författare"  );  skapa  .  väljFrån  (  författare  )  .  där  (  finns  (  välj En  ()  .  från  (  BOK  )  .  där  (  BOK  .  STATUS  .  eq  (  BOOK_STATUS  .  SOLD_OUT  ))  .  och  (  BOOK  .  AUTHOR_ID  .  eq  (  författare  .  ID  )))); 

Influensannoteringsprocessorn möjliggör skapandet av ett flytande API med Java-annoteringar.

JaQue-biblioteket gör att Java 8 Lambdas kan representeras som objekt i form av uttrycksträd under körning, vilket gör det möjligt att skapa typsäkra flytande gränssnitt, dvs istället för:

   
 Kundobj  =  ...  obj  .  _  egenskap  (  "namn"  ).  eq  (  "John"  ) 

Man kan skriva:

     metod  <  Kund  >  (  kund  ->  kund  .  getName  ()  ==  "John"  ) 

Dessutom använder det skenbara objekttestningsbiblioteket EasyMock i stor utsträckning denna typ av gränssnitt för att tillhandahålla ett uttrycksfullt programmeringsgränssnitt.

   

    
     
     Samling  mockCollection  =  EasyMock  .  createMock  (  Collection  .  class  );  EasyMock  .  förvänta  (  mockCollection  .  ta bort  (  null  ))  .  andThrow  (  ny  NullPointerException  ())  .  åtminstone en gång  (); 

I Java Swing API definierar LayoutManager-gränssnittet hur Container-objekt kan ha kontrollerad komponentplacering. En av de mer kraftfulla LayoutManager- implementeringarna är GridBagLayout-klassen som kräver användning av GridBagConstraints -klassen för att specificera hur layoutkontroll sker. Ett typiskt exempel på användningen av denna klass är något i stil med följande.

    
    
  

    
    

    
  0
  0
  
   

  
  
  
    GridBagLayout  gl  =  ny  GridBagLayout  ();  JPanel  p  =  ny  JPanel  ();  p  .  setLayout  (  gl  );  JLabel  l  =  ny  JLabel  (  "Namn:"  );  JTextField  nm  =  nytt  JTextField  (  10  );  GridBagConstraints  gc  =  nya  GridBagConstraints  ();  gc  .  gridx  =  ;  gc  .  gridy  =  ;  gc  .  fill  =  GridBagConstraints  .  INGEN  ;  p  .  add  (  l  ,  gc  );  gc  .  gridx  =  1  ;  gc  .  fill  =  GridBagConstraints  .  HORISONTALT  ;  gc  .  viktx  =  1  ;  p  .  add  (  nm  ,  gc  ); 

Detta skapar mycket kod och gör det svårt att se exakt vad som händer här. Packer - klassen ger en flytande mekanism, så du skulle istället skriva:

    
      

    
    

  00
  0 JPanel  p  =  ny  JPanel  ();  Packer  pk  =  ny  Packer  (  p  );  JLabel  l  =  ny  JLabel  (  "Namn:"  );  JTextField  nm  =  nytt  JTextField  (  10  );  pk  .  pack  (  l  ).  gridx  (  ).  gridy  (  );  pk  .  pack  (  nm  ).  gridx  (  1  ).  rutnät  (  ).  fillx  (); 

Det finns många ställen där flytande API:er kan förenkla hur programvara skrivs och hjälpa till att skapa ett API-språk som hjälper användarna att vara mycket mer produktiva och bekväma med API:t eftersom returvärdet för en metod alltid ger ett sammanhang för ytterligare åtgärder i det sammanhanget.

JavaScript

Det finns många exempel på JavaScript-bibliotek som använder någon variant av detta: jQuery är förmodligen det mest kända. Vanligtvis används flytande byggare för att implementera "databasfrågor", till exempel i Dynamite-klientbiblioteket:



     
     
    
     
        
     // få ett objekt från en  tabellklient  .  getItem  (  'användartabell'  )  .  setHashKey  (  'userId'  ,  'userA'  )  .  setRangeKey  (  'kolumn'  ,  '@'  )  .  exekvera  ()  .  then  (  function  (  data  )  {  // data.result: det resulterande objektet  }) 

Ett enkelt sätt att göra detta i JavaScript är att använda prototyparv och detta .



  
   
      
      
  

   
      
     
  

   
      
     
  

   
    
      
    
     
  



 
  
  
   // exempel från https://schier.co/blog/2013/11/14/method-chaining-in-javascript.html  class  Kitten  {  constructor  ()  {  this  .  name  =  'Garfield'  ;  detta  .  färg  =  'orange'  ;  }  setName  (  namn  )  {  detta  .  namn  =  namn  ;  returnera  detta  ;  }  setColor  (  color  )  {  this  .  färg  =  färg  ;  returnera  detta  ;  }  spara  ()  {  konsol  .  log  (  `sparar  ${  this  .  name  }  ,  ${  this  .  color  }  kattungen`  );  returnera  detta  ;  }  }  // använd den  nya  Kitten  ()  .  setName  (  'Salem'  )  .  setColor  (  'svart'  )  .  spara  (); 

Scala

Scala stöder en flytande syntax för både metodanrop och klassblandningar, med hjälp av egenskaper och nyckelordet med . Till exempel:

      
             

  
  
     
     
       

       

    
    
    
    


    
      
    
      
      
      
      
      
      
   


      

      
 klass  Färg  {  def  rgb  ():  Tuple3  [  Decimal  ]  }  objekt  Svart  utökar  Färg  {  override  def  rgb  ():  Tuple3  [  Decimal  ]  =  (  "0"  ,  "0"  ,  "0"  );  }  trait  GUIWindow  {  // Renderingsmetoder som returnerar detta för flytande ritning  def  set_pen_color  (  färg  :  Färg  ):  detta  .  skriv  def  move_to  (  pos  :  Position  ):  detta  .  skriv  def  line_to  (  pos  :  Position  ,  end_pos  :  Position  ):  detta  .  typ  def  render  ():  detta  .  typ  =  detta  // Rita ingenting, returnera bara detta, för att underordnade implementeringar ska kunna använda flytande  def  top_left  ():  Position  def  bottom_left  ():  Position  def  top_right  () :   Position  def  bottom_right  ():  Position  }  egenskap  WindowBorder  utökar  GUIWindow  {  def  render  ():  GUIWindow  =  {  super  .  rendera  ()  .  flytta_till  (  övre_vänster  ())  .  set_pen_color  (  svart  )  .  line_to  (  top_right  ())  .  line_to  (  bottom_right  ())  .  line_to  (  bottom_left  ())  .  line_to  (  top_left  ())  }  }  klass  SwingWindow  utökar  GUIWindow  {  ...  }  val  appWin  =  new  SwingWindow  ()  med  WindowBorder  appWin  .  rendera  () 

Raku

I Raku finns det många tillvägagångssätt, men en av de enklaste är att deklarera attribut som läs/skriv och använda det givna nyckelordet. Typkommentarerna är valfria, men den inbyggda gradvisa skrivningen gör det mycket säkrare att skriva direkt till offentliga attribut.

             0     

                              



   


 klass  Anställd  {  delmängd  Lön  av  Real  där  * >  ;  delmängd  NonEmptyString  av  Str  där  * ~~  /\S/  ;  # minst ett tecken utan mellanslag  har  NonEmptyString  $.name  är  rw  ;  har  NonEmptyString  $.surname  är  rw  ;  har  Lön  $.salary  är  rw  ;  method  gist  {  return  qq:to[END];  Namn: $.name  Efternamn: $.surname  Lön: $.salary  SLUT  } }  min  $employee  =  Anställd  .  ny  ();  given  $employee  { .  name  =  'Sally'  ; .   efternamn  =  'Rid'  ; .   lön  =  200  ; }   säg  $anställd  ;  # Output:  # Namn: Sally  # Efternamn: Ride  # Lön: 200 

PHP

I PHP kan man returnera det aktuella objektet genom att använda $this specialvariabel som representerar instansen. Returnera därför $this; kommer att få metoden att returnera instansen. Exemplet nedan definierar en klass Anställd och tre metoder för att ställa in dess namn, efternamn och lön. Varje returnerar instansen av Employee som tillåter kedja av metoder.

 

      
       
      

       
    
          

         
    

       
    
          

         
    

       
    
          

         
    

      
    
              
              
              

         
    



   
                
                
                


 




 klass  Anställd  {  privat  sträng  $namn  ;  privat  sträng  $efternamn  ;  privat  sträng  $lön  ;  public  function  setName  (  sträng  $name  )  {  $this  ->  name  =  $name  ;  returnera  $detta  ;  }  public  function  setSurname  (  sträng  $surname  )  {  $this  ->  efternamn  =  $efternamn  ;  returnera  $detta  ;  }  public  function  setSalary  (  sträng  $salary  )  {  $this  ->  salary  =  $salary  ;  returnera  $detta  ;  }  offentlig  funktion  __toString  ()  {  $employeeInfo  =  'Namn: '  .  $this  ->  namn  .  PHP_EOL  ;  $employeeInfo  .=  'Efternamn: '  .  $this  ->  efternamn  .  PHP_EOL  ;  $employeeInfo  .=  'Lön: '  .  $this  ->  lön  .  PHP_EOL  ;  returnera  $employeeInfo  ;  } }  #  Skapa en ny instans av klassen Employee, Tom Smith, med en lön på 100:  $employee  =  (  new  Employee  ())  ->  setName  (  'Tom'  )  ->  setSurname  (  'Smith'  )  ->  setSalary  (  '100'  );  # Visa värdet på Employee-instansen:  echo  $employee  ;  # Display:  # Namn: Tom  # Efternamn: Smith  # Lön: 100 

Pytonorm

I Python är att returnera själv i instansmetoden ett sätt att implementera det flytande mönstret.

Det avskräcks dock av språkets skapare, Guido van Rossum, och anses därför vara opytoniskt (inte idiomatiskt).

 
         
          

       
        
              
         

       
        
          
          klass  Dikt  :  def  __init__  (  själv  ,  titel  :  str  )  ->  Ingen  :  själv  .  title  =  title  def  indent  (  self  ,  spaces  :  int  ):  """Indrag dikten med det angivna antalet blanksteg."""  self  .  title  =  " "  *  mellanslag  +  själv  .  title  return  self  def  suffix  (  self  ,  author  :  str  ):  """Suffixera dikten med författarens namn."""  self  .  title  =  f  "  {  self  .  title  }  -  {  author  }  "  returnerar  själv 

 >>>  Dikt  (  "Road Not Resed"  )  .  strecksats  (  4  )  .  suffix  (  "Robert Frost"  )  .  titel  ' Road Not Traveled - Robert Frost' 

Snabb

I Swift 3.0+ är att återvända själv i funktionerna ett sätt att implementera det flytande mönstret.

  
        
        
        

    
         
          
         
    

    
         
          
         
    

    
         
          
         
    
 class  Person  {  var  firstname  :  String  =  ""  var  efternamn  :  String  =  ""  var  favoritCitat  :  String  =  ""  @  discardableResult  func  set  (  firstname  :  String  )  ->  Self  {  self  .  firstname  =  firstname  return  self  }  @  discardableResult  func  set  (  efternamn  :  String  )  ->  Self  {  self  .  efternamn  =  efternamn  returnera  själv  }  @  discardableResult  func  set  (  favoritCitat  :  String  )  ->  Self  {  self  .  favoritQuote  =  favoritCitat  returnerar  själv  }  } 
   
     
     
      låt  person  =  Person  ()  .  set  (  förnamn  :  "John"  )  .  set  (  efternamn  :  "Doe"  )  .  set  (  favoritCitat  :  "Jag gillar sköldpaddor"  ) 

Oföränderlighet

Det är möjligt att skapa oföränderliga flytande gränssnitt som använder kopiera-på-skriv- semantik. I denna variant av mönstret, istället för att modifiera interna egenskaper och returnera en referens till samma objekt, klonas objektet istället, med egenskaper ändrade på det klonade objektet, och det objektet returneras.

Fördelen med detta tillvägagångssätt är att gränssnittet kan användas för att skapa konfigurationer av objekt som kan forkastas från en viss punkt; Tillåta två eller flera objekt att dela en viss del av tillstånd och användas vidare utan att störa varandra.

JavaScript-exempel

Med hjälp av copy-on-write-semantik blir JavaScript-exemplet från ovan:

  
   
      
      
  

   
        
      
      
     
  

   
        
      
      
     
  

  



    
  

   
  

 
 klass  Kattunge  {  konstruktor  ()  {  detta  .  name  =  'Garfield'  ;  detta  .  färg  =  'orange'  ;  }  setName  (  name  )  {  const  copy  =  new  Kitten  ();  kopiera  .  färg  =  detta  .  färg  ;  kopiera  .  namn  =  namn  ;  returnera  kopia  ;  }  setColor  (  color  )  {  const  copy  =  new  Kitten  ();  kopiera  .  namn  =  detta  .  namn  ;  kopiera  .  färg  =  färg  ;  returnera  kopia  ;  }  // ...  }  // använd den  const  kattunge1  =  ny  kattunge  ()  .  setName  (  'Salem'  );  const  kattunge2  =  kattunge1  .  setColor  (  'svart'  );  konsol  .  log  (  kattunge1  ,  kattunge2  );  // -> Kattunge({ namn: 'Salem', färg: 'orange' }), Kattunge({ namn: 'Salem', färg: 'svart' }) 

Problem

Fel kan inte fångas upp vid kompilering

I maskinskrivna språk kommer användningen av en konstruktor som kräver alla parametrar att misslyckas vid kompileringstillfället, medan det flytande tillvägagångssättet bara kommer att kunna generera runtime -fel och missar alla typsäkerhetskontroller av moderna kompilatorer. Det strider också mot " felsnabb "-metoden för felskydd.

Felsökning och felrapportering

Enradiga kedjesatser kan vara svårare att felsöka eftersom felsökningsverktyg kanske inte kan ställa in brytpunkter inom kedjan. Att gå igenom en enkelradssats i en felsökning kan också vara mindre bekvämt.

 java  .  nio  .  ByteBuffer  .  fördela  (  10  ).  spola tillbaka  ().  gräns  (  100  ); 

Ett annat problem är att det kanske inte är klart vilket av metodanropen som orsakade ett undantag, särskilt om det finns flera anrop till samma metod. Dessa problem kan övervinnas genom att dela upp uttalandet i flera rader vilket bevarar läsbarheten samtidigt som användaren kan ställa in brytpunkter inom kedjan och enkelt gå igenom koden rad för rad:


    
    
     java  .  nio  .  ByteBuffer  .  fördela  (  10  )  .  spola tillbaka  ()  .  gräns  (  100  ); 

Vissa felsökare visar dock alltid den första raden i undantaget bakåtspårning, även om undantaget har kastats på vilken rad som helst.

Skogsavverkning

Att lägga till inloggning i mitten av en kedja av flytande samtal kan vara ett problem. Till exempel givet:

    ByteBuffer  buffer  =  ByteBuffer  .  fördela  (  10  ).  spola tillbaka  ().  gräns  (  100  ); 

För att logga bufferttillståndet efter rewind() -metodanropet är det nödvändigt att bryta de flytande anropen :

   
  0
 ByteBuffer  buffer  =  ByteBuffer  .  fördela  (  10  ).  spola tillbaka  ();  logga  .  debug  (  "Första byten efter bakåtspolning är "  +  buffert  .  get  (  ));  buffert  .  gräns  (  100  ); 

Detta kan lösas i språk som stöder tilläggsmetoder genom att definiera en ny tillägg för att linda in den önskade loggningsfunktionaliteten, till exempel i C# (med samma Java ByteBuffer-exempel som ovan):

  

             
    
           
        
         
     




    
    
          0 
     static  class  ByteBufferExtensions  {  public  static  ByteBuffer  Log  (  denna  ByteBuffer  buffer  ,  Log  log  ,  Action  <  ByteBuffer  >  getMessage  )  {  string  message  =  getMessage  (  buffert  );  logga  .  debug  (  meddelande  );  returbuffert  ;  _  }  }  // Användning:  ByteBuffer  .  Tilldela  (  10  )  .  Spola tillbaka  ()  .  Logg  (  log  ,  b  =>  "Första byte efter bakåtspolning är "  +  b  .  Hämta  (  )  )  .  Gräns  ​​(  100  ); 

Underklasser

Underklasser i starkt typade språk (C++, Java, C#, etc.) måste ofta åsidosätta alla metoder från sin superklass som deltar i ett flytande gränssnitt för att ändra returtyp. Till exempel:

  
         

   
            
         


     
      klass  A  {  public  A  doThis  ()  {  ...  }  }  klass  B  utökar  A  {  public  B  doThis  ()  {  super  .  gör detta  ();  returnera  detta  ;  }  // Måste ändra returtyp till B.  public  B  doThat  ()  {  ...  }  }  ...  A  a  =  new  B  ().  gör det  ().  gör detta  ();  // Detta skulle fungera även utan att åsidosätta A.doThis().  B  b  =  nytt  B  ().  gör detta  ().  gör det  ();  // Detta skulle misslyckas om A.doThis() inte åsidosattes. 

Språk som kan uttrycka F-bunden polymorfism kan använda det för att undvika denna svårighet. Till exempel:

     
	
	       
	
    
	
    
	       



     
               abstrakt  klass  AbstractA  <  T  utökar  SammanfattningA  <  T  >>  {  @SuppressWarnings  (  "unchecked"  )  public  T  doThis (  )  {  ...;  returnera  (  T  )  detta  ;  }  }  klass  A  förlänger  AbstractA  <  A  >  {}  klass  B  förlänger  AbstractA  <  B  >  {  public  B  doThat  ()  {  ...;  returnera  detta  ;  }  }  ...  B  b  =  nytt  B  ().  gör detta  ().  gör det  ();  // Arbetar!  A  a  =  nytt  A  ().  gör detta  ();  // Fungerar också. 

Observera att för att kunna skapa instanser av den överordnade klassen, var vi tvungna att dela upp den i två klasser — AbstractA och A , den senare utan innehåll (den skulle bara innehålla konstruktorer om de behövdes). Tillvägagångssättet kan enkelt utökas om vi vill ha underklasser (etc.) också:

       
	
	       

    

       
	
	       

    

     
            abstrakt  klass  AbstractB  <  T  utökar  AbstractB  <  T  >>  utökar  AbstractA  <  T  >  {  @SuppressWarnings  (  "unchecked"  )  public  T  doThat  ()  {  ...;  returnera  (  T  )  detta  ;  }  }  klass  B  förlänger  AbstractB  <  B  >  {}  abstrakt  klass  AbstractC  <  T  förlänger  AbstractC  <  T  >>  utökar  AbstractB  <  T  >  {  @SuppressWarnings  (  "unchecked"  )  public  T  foo  ()  {  ...;  returnera  (  T  )  detta  ;  }  }  klass  C  utökar  AbstractC  <  C  >  {}  ...  C  c  =  nytt  C  ().  gör detta  ().  gör det  ().  foo  ();  // Arbetar!  B  b  =  nytt  B  ().  gör detta  ().  gör det  ();  // Fungerar fortfarande. 

I ett beroende skrivet språk, t.ex. Scala, kan metoder också uttryckligen definieras som att de alltid returnerar detta och kan därför endast definieras en gång för underklasser för att dra nytta av det flytande gränssnittet:

  
           

   
    
          


      
       klass  A  {  def  doThis  ():  detta  .  typ  =  {  ...  }  // returnerar detta, och alltid detta.  }  klass  B  utökar  A  {  // Ingen åsidosättning behövs!  def  doThat  ():  detta  .  typ  =  {  ...  }  }  ...  val  a  :  A  =  nytt  B  ().  gör det  ().  gör detta  ();  // Kedjning fungerar i båda riktningarna.  val  b  :  B  =  nytt  B  ().  gör detta  ().  gör det  ();  // Och båda metodkedjorna resulterar i ett B! 

Se även

externa länkar