Zasada jednolitego dostępu

jednolitego dostępu w programowaniu komputerowym została przedstawiona przez Bertranda Meyera (pierwotnie w Object-Oriented Software Construction ). Stwierdza, że ​​​​„Wszystkie usługi oferowane przez moduł powinny być dostępne za pośrednictwem jednolitej notacji, która nie zdradza, czy są one realizowane poprzez przechowywanie, czy obliczenia”. Zasada ta dotyczy ogólnie składni obiektowych języków programowania . W prostszej formie stwierdza, że ​​nie powinno być różnic składniowych między pracą z atrybutem , wstępnie obliczoną właściwością lub metodą / zapytaniem obiektu.

Podczas gdy większość przykładów skupia się na „odczytywanym” aspekcie zasady (tj. pobieraniu wartości), Meyer pokazuje, że implikacje „zapisu” (tj. modyfikacja wartości) zasady są trudniejsze do omówienia w swoim comiesięcznym felietonie na Oficjalna strona języka programowania Eiffla .

Wyjaśnienie

Problem, którym zajmuje się Meyer, dotyczy utrzymania dużych projektów oprogramowania lub bibliotek oprogramowania. Czasami podczas opracowywania lub utrzymywania oprogramowania konieczna jest, po wprowadzeniu dużej ilości kodu, zmiana klasy lub obiektu w sposób, który przekształca to, co było po prostu dostępem do atrybutu, w wywołanie metody. Języki programowania często używają innej składni dostępu do atrybutów i wywoływania metody (np. obiekt.coś kontra obiekt.coś() ). Zmiana składni wymagałaby, w popularnych wówczas językach programowania, zmiany kodu źródłowego we wszystkich miejscach, w których zastosowano atrybut. Może to wymagać zmiany kodu źródłowego w wielu różnych lokalizacjach w bardzo dużej ilości kodu źródłowego. Lub, co gorsza, jeśli zmiana dotyczy biblioteki obiektów używanej przez setki klientów, każdy z tych klientów musiałby znaleźć i zmienić wszystkie miejsca, w których atrybut był używany we własnym kodzie, i ponownie skompilować swoje programy.

Przejście w odwrotną stronę (od metody do prostego atrybutu) naprawdę nie stanowiło problemu, ponieważ zawsze można po prostu zachować funkcję i sprawić, by po prostu zwracała wartość atrybutu.

Meyer dostrzegł potrzebę pisania kodu przez programistów w taki sposób, aby zminimalizować lub wyeliminować kaskadowe zmiany w kodzie, które wynikają ze zmian, które przekształcają atrybut obiektu w wywołanie metody lub odwrotnie. W tym celu opracował zasadę jednolitego dostępu.

Wiele języków programowania nie obsługuje ściśle UAP, ale obsługuje jego formy. Właściwości, które są dostępne w wielu językach programowania, rozwiązują problem, który Meyer rozwiązywał w swoim UAP w inny sposób. Zamiast zapewniać pojedynczą, jednolitą notację, właściwości umożliwiają wywołanie metody obiektu przy użyciu tej samej notacji, która jest używana do uzyskiwania dostępu do atrybutów. Oddzielna składnia wywołania metody jest nadal dostępna.

Przykład UAP

Jeśli język używa składni wywołania metody, może wyglądać mniej więcej tak.

// Załóżmy, że print wyświetla zmienną przekazaną do niej, z nawiasami lub bez // Ustaw atrybut Foo 'bar' na wartość 5. Foo.bar(5) print Foo.bar()

Po wykonaniu powinno wyświetlić się:

5

To, czy Foo.bar(5) wywołuje funkcję, czy po prostu ustawia atrybut, jest ukryte przed wywołującym. Podobnie to, czy funkcja Foo.bar() po prostu pobiera wartość atrybutu, czy też wywołuje funkcję w celu obliczenia zwróconej wartości, jest szczegółem implementacji ukrytym przed wywołującym.

Jeśli język używa składni atrybutów, składnia może wyglądać tak.

Foo.bar = 5 drukuj Foo.bar

Ponownie, niezależnie od tego, czy metoda jest wywoływana, czy też wartość jest po prostu przypisana do atrybutu, jest ukrywana przed wywołującą metodą.

Problemy

Jednak sam UAP może powodować problemy, jeśli jest używany w miejscach, w których różnice między metodami dostępu nie są pomijalne, na przykład gdy zwracana wartość jest kosztowna do obliczenia lub wyzwala operacje pamięci podręcznej.

Przykłady językowe

Pyton

Właściwości Pythona mogą być używane, aby umożliwić wywołanie metody z taką samą składnią jak dostęp do atrybutu. Podczas gdy UAP Meyera miałby jedną notację zarówno dla dostępu do atrybutów, jak i wywołania metody (składnia wywołania metody), język obsługujący właściwości nadal obsługuje oddzielne notacje dla dostępu do atrybutów i metod. Właściwości pozwalają na użycie notacji atrybutu, ale ukrywają fakt, że metoda jest wywoływana zamiast zwykłego pobierania lub ustawiania wartości.

W związku z tym Python pozostawia opcję przestrzegania UAP indywidualnemu programiście. Wbudowana @property zapewnia prosty sposób dekorowania dowolnej metody w składni dostępu do atrybutów, eliminując w ten sposób różnice składniowe między wywołaniami metod a dostępami do atrybutów.

W Pythonie możemy mieć kod uzyskujący dostęp do obiektu Egg , który można zdefiniować w taki sposób, że waga i kolor są prostymi atrybutami, jak w poniższym








 
         
          
          

      """  >>> jajko = Jajko(4.0, "biały")  >>> jajko.kolor = "zielony"  >>> print(jajko)  Jajo(4.0, zielony)  """  klasa  Jajo  :  def  __init__  (  self  ,  waga  ,  kolor  )  ->  Brak  :  self  .  waga  =  waga  siebie  .  color  =  color  def  __str__  (  self  )   
          ->  str  :  return  f  "  {  __klasa__  .  __nazwa__  }  (  {  własny  .  waga  }  ,  {  własny  .  kolor  }  )" 

Lub obiekt Egg mógłby używać właściwości i zamiast tego wywoływać metody pobierające i ustawiające


 
           
          
          
        
    
       
         # ...(snip)...  class  Egg  :  def  __init__  (  self  ,  weight_oz  :  float  ,  color_name  :  float  )  ->  Brak  :  self  .  waga  =  waga_oz  siebie  .  color  =  color_name  @property  def  color  (  self  )  ->  str  :  '''Kolor jajka''' 
         

    
         
             

    
       
         powrót  do_color_str  (  self  .  _color_rgb  )  @kolor  .  setter  def  color  (  self  ,  color_name  :  str  )  ->  Brak  :  self  .  _color_rgb  =  to_rgb  (  nazwa_koloru  )  @property  def  weight  (  self  )  ->  float  :  '''Waga w uncjach''' 
           

    
         
            

     zwrócić  siebie  .  _waga_gram  /  29,3  @waga  .  setter  def  weight  (  self  ,  weight_oz  :  float  )  ->  Brak  :  self  .  _waga_gram  =  29,3  *  waga_oz  # ...(odcinek)... 
Wycięte kody są następujące:
 



    
    
         
     
         
    
  import  webcolors  # class Egg:  def  to_color_str  (  rgb  :  webcolors  .  IntegerRGB  )  ->  str  :  try  :  return  webcolors  .  rgb_to_name  (  rgb  )  z wyjątkiem  ValueError  :  zwraca  kolory internetowe  .  rgb_to_hex  (  rgb  )  def  to_rgb  (  nazwa_koloru  :    
    
         
     
         


   
     
     str  )  ->  kolory internetowe  .  IntegerRGB  :  try  :  zwraca  kolory internetowe  .  name_to_rgb  (  nazwa_koloru  )  z wyjątkiem  ValueError  :  zwraca  kolory internetowe  .  hex_to_rgb  (  nazwa_koloru  )  if  __name__  ==  "__main__"  :  import  doctest  doctest  .  mod testowy  () 

Niezależnie od tego, w jaki sposób zdefiniowano Egg , kod wywołujący może pozostać taki sam. Implementacja Egg może przełączać się z jednej formy do drugiej bez wpływu na kod korzystający z klasy Egg. Języki, które implementują UAP, również mają tę właściwość.

Rubin

Rozważ następujące

  
   
  y  =  Jajko  .  nowy  (  „zielony”  )  y  .  color  =  "Biały"  stawia  y  .  kolor 

Teraz klasę Egg można zdefiniować w następujący sposób

 
   
   
      
  
 class  Egg  attr_accessor  :color  def  initialize  (  color  )  @color  =  color  end  end 

Powyższy początkowy segment kodu działałby dobrze, gdyby jajko zostało zdefiniowane jako takie. Klasę Egg można również zdefiniować jak poniżej, gdzie kolor jest metodą. Kod wywołujący nadal działałby, niezmieniony, gdyby Egg miał być zdefiniowany w następujący sposób.

 
  
   
      
  

   
     
  
 
    
       
  

  
   
     
  

   class  Egg  def  initialize  (  kolor  )  @rgb_color  =  to_rgb  (  kolor  )  end  def  color  to_color_name  (  @rgb_color  )  end  def  color=  (  color  )  @rgb_color  =  to_rgb  (  color  )  end  private  def  to_rgb  (  color_name  )  .....  end  def  
     
  
 to_color_name  (  kolor  )  ....  koniec  koniec 

Zwróć uwagę, że chociaż kolor wygląda jak atrybut w jednym przypadku, a para metod w następnym, interfejs do klasy pozostaje taki sam. Osoba utrzymująca klasę Egg może przechodzić z jednej formy do drugiej bez obawy o złamanie kodu dzwoniącego. Ruby podąża za poprawionym UAP, attr_accessor :color działa tylko jako cukier składniowy do generowania metod akcesora/settera dla color . W języku Ruby nie ma możliwości pobrania zmiennej instancji z obiektu bez wywołania na niej metody.

Ściśle mówiąc, Ruby nie podąża za oryginalnym UAP Meyera, ponieważ składnia dostępu do atrybutu różni się od składni wywoływania metody. Ale tutaj dostęp do atrybutu zawsze będzie faktycznie odbywał się za pośrednictwem funkcji, która często jest generowana automatycznie. Tak więc w istocie każdy rodzaj dostępu wywołuje funkcję, a język jest zgodny ze zmienioną zasadą jednolitego dostępu Meyera.

C#

Język C# obsługuje właściwości klas , które umożliwiają definiowanie operacji pobierania i ustawiania ( pobierających i ustawiających ) dla zmiennej składowej. Składnia uzyskiwania dostępu lub modyfikowania właściwości jest taka sama, jak uzyskiwania dostępu do dowolnej innej zmiennej składowej klasy, ale rzeczywista implementacja tego może być zdefiniowana jako prosty dostęp do odczytu/zapisu lub jako kod funkcjonalny.

  

      

    
      
    
            
            
    

    
      
    
                 
                 
    
 klasa  publiczna  Foo  {  prywatny  ciąg  _nazwa  ;  // Właściwość  public  int  Rozmiar  {  get  ;  // Getter  ustawia  ;  // Setter  }  // Właściwość  public  string  Nazwa  {  get  {  return  _name  ;  }  // Getter  set  {  _name  =  value  ;  }  // Seter  }  } 

W powyższym przykładzie klasa Foo zawiera dwie właściwości, Size i Name . Właściwość Size jest liczbą całkowitą, którą można odczytać (pobrać) i zapisać (ustawić) . Podobnie właściwość Name jest ciągiem znaków, który również można odczytywać i modyfikować, ale jej wartość jest przechowywana w oddzielnej (prywatnej) zmiennej klasy _name .

Pominięcie operacji set w definicji właściwości powoduje, że właściwość jest tylko do odczytu, natomiast pominięcie operacji get powoduje, że jest ona tylko do zapisu.

Użycie właściwości wykorzystuje UAP, jak pokazano w poniższym kodzie.

         
    
            
           
           
         
     public  Foo  CreateFoo  (  rozmiar  int  ,  nazwa  ciągu  )  {  var  foo  =  new  Foo  ();  fuj  .  rozmiar  =  rozmiar  ;  // Ustawienie właściwości  foo  .  imię  =  imię  ;  // Metoda ustawiająca właściwość  return  foo  ;  } 

C++

C++ nie ma ani UAP, ani właściwości, gdy obiekt jest zmieniany w taki sposób, że atrybut (kolor) staje się parą funkcji ( getA, setA ). Każde miejsce, które używa instancji obiektu i ustawia lub pobiera wartość atrybutu ( x = obj.color lub obj.color = x ) musi zostać zmienione, aby wywołać jedną z funkcji. ( x = obj.getColor() lub obj.setColor(x) ). Używanie szablonów i przeciążanie operatorów , możliwe jest sfałszowanie właściwości, ale jest to bardziej złożone niż w językach, które bezpośrednio obsługują właściwości. To komplikuje konserwację programów C++. Rozproszone biblioteki obiektów C++ muszą uważać na sposób, w jaki zapewniają dostęp do danych członkowskich.

JavaScript

JavaScript obsługuje obliczane właściwości od 2009 roku.