Myślę, że może się przydać mój post na blogu o nieszczelnych abstrakcjach. Oto odpowiednie tło:
Abstrakcja jest mechanizmem, który pomaga wziąć to, co wspólne dla zestawu powiązanych fragmentów programu, usunąć ich różnice i umożliwić programistom bezpośrednią pracę z konstrukcją reprezentującą tę abstrakcyjną koncepcję. Ten nowy konstrukt (praktycznie) zawsze ma parametryzację : sposób na dostosowanie sposobu używania konstruktora do konkretnych potrzeb.
Na przykład List
klasa może wyodrębnić szczegóły implementacji listy połączonej - gdzie zamiast myśleć o kategoriach manipulacji next
i previous
wskaźników, możesz myśleć na poziomie dodawania lub usuwania wartości do sekwencji. Abstrakcja jest niezbędnym narzędziem do tworzenia użytecznych, bogatych, a czasem złożonych funkcji na podstawie znacznie mniejszego zestawu bardziej prymitywnych koncepcji.
Abstrakcja wiąże się z enkapsulacją i modułowością, a te pojęcia są często źle rozumiane.
W tym List
przykładzie można zastosować enkapsulację, aby ukryć szczegóły implementacji listy połączonej; na przykład w języku zorientowanym obiektowo można ustawić prywatne wskaźniki next
i previous
wskaźniki, w których tylko implementacja listy ma dostęp do tych pól.
Kapsułkowanie nie wystarcza do abstrakcji, ponieważ niekoniecznie oznacza, że masz nową lub inną koncepcję konstruktów. Gdyby wszystko, co List
zrobiła klasa, dawało ci metody dostępu do stylu getNext
„/” setNext
, zawierałoby od ciebie szczegóły implementacji (np. Czy nazwałeś pole prev
„czy previous
”? Jaki był jego typ statyczny?), Ale to miałby bardzo niski stopień abstrakcji.
Modułowość dotyczy ukrywania informacji : stabilne właściwości są określone w interfejsie, a moduł implementuje ten interfejs, zachowując wszystkie szczegóły implementacji w module. Modułowość pomaga programistom radzić sobie ze zmianami, ponieważ inne moduły zależą tylko od stabilnego interfejsu.
Ukrywanie informacji jest wspomagane przez enkapsulację (dzięki czemu kod nie zależy od niestabilnych szczegółów implementacji), ale enkapsulacja nie jest konieczna dla modułowości. Na przykład, można wdrożyć List
struktury w C, odsłaniając „ next
” i „ prev
wskaźniki” na świecie, ale także interfejs, zawierający initList()
, addToList()
orazremoveFromList()
Funkcje. Pod warunkiem, że przestrzegane są reguły interfejsu, można zagwarantować, że pewne właściwości będą zawsze obowiązywać, na przykład upewnić się, że struktura danych jest zawsze w poprawnym stanie. [Klasyczny artykuł Parnasa na temat modułowości, na przykład, został napisany z przykładem w asemblerze. Interfejs jest umową i formą komunikacji na temat projektu, niekoniecznie musi być sprawdzany mechanicznie, chociaż na tym dziś polegamy.]
Chociaż terminy takie jak abstrakcyjne, modułowe i enkapsulowane są używane jako pozytywne opisy projektu, ważne jest, aby zdawać sobie sprawę, że obecność którejkolwiek z tych cech nie zapewnia automatycznie dobrego projektu:
Jeśli algorytm n ^ 3 jest „ładnie zamknięty”, nadal będzie działał gorzej niż ulepszony algorytm n log n.
Jeśli interfejs zostanie przypisany do konkretnego systemu operacyjnego, żadna z korzyści modułowej konstrukcji nie zostanie zrealizowana, gdy powiedzmy, że gra wideo musi zostać przeniesiona z systemu Windows na iPada.
Jeśli utworzona abstrakcja ujawni zbyt wiele nieistotnych szczegółów, nie uda jej się stworzyć nowego konstruktu z własnymi operacjami: Będzie to po prostu inna nazwa tego samego.