Programování

Zapouzdření není skrývání informací

Slova jsou kluzká. Jako Humpty Dumpty vyhlášený u Lewise Carrolla Přes zrcadlo, „Když použiji slovo, znamená to přesně to, co jsem si vybral, že to znamená - ani více, ani méně.“ Určitě běžné používání slov zapouzdření a skrývání informací Zdá se, že se řídí touto logikou. Autoři zřídka rozlišují mezi těmito dvěma a často přímo tvrdí, že jsou stejní.

Je to tak? Není pro mě. Kdyby to byla jen otázka slov, nenapsal bych k tomu další slovo. Za těmito termíny jsou ale dva odlišné koncepty, koncepty vytvořené samostatně a nejlépe pochopitelné samostatně.

Zapouzdření označuje spojení dat s metodami, které na těchto datech pracují. Tato definice je často nesprávně vykládána, což znamená, že data jsou nějak skrytá. V Javě můžete mít zapouzdřená data, která nejsou vůbec skrytá.

Skrytí dat však není úplným rozsahem skrývání informací. David Parnas poprvé představil koncept skrývání informací kolem roku 1972. Tvrdil, že primární kritéria pro modularizaci systému by se měla týkat skrývání kritických konstrukčních rozhodnutí. Zdůraznil, že se skrývá „obtížná rozhodnutí o návrhu nebo rozhodnutí o návrhu, která se pravděpodobně změní“. Skrytí informací tímto způsobem izoluje klienty od vyžadování důkladné znalosti návrhu pro použití modulu a od účinků změny těchto rozhodnutí.

V tomto článku zkoumám rozdíl mezi zapouzdřením a skrýváním informací při vývoji ukázkového kódu. Diskuse ukazuje, jak Java usnadňuje zapouzdření a zkoumá negativní důsledky zapouzdření bez skrývání dat. Příklady také ukazují, jak vylepšit design třídy pomocí principu skrývání informací.

Poziční třída

S rostoucím povědomím o obrovském potenciálu bezdrátového internetu mnoho učenců očekává, že služby založené na poloze poskytnou příležitost pro první bezdrátovou aplikaci zabijáka. Pro ukázkový kód tohoto článku jsem vybral třídu představující geografické umístění bodu na zemském povrchu. Jako entita domény je třída pojmenovaná Pozice, představuje informace o globálním pozičním systému (GPS). První sestřih ve třídě vypadá stejně jednoduše jako:

public class Pozice {public double latitude; veřejná dvojitá zeměpisná délka; } 

Třída obsahuje dvě datové položky: GPS zeměpisná šířka a zeměpisná délka. V současnosti, Pozice není nic jiného než malá taška dat. Nicméně Pozice je třída a Pozice objekty mohou být vytvořeny pomocí třídy. Chcete-li tyto objekty využít, třída PositionUtility obsahuje metody pro výpočet vzdálenosti a směru - tedy směru - mezi zadanými Pozice objekty:

public class PositionUtility {public static double distance (Pozice pozice1, Pozice pozice2) {// Vypočítá a vrátí vzdálenost mezi zadanými pozicemi. } veřejné statické dvojité záhlaví (Pozice pozice1, Pozice pozice2) {// Vypočítá a vrátí nadpis z pozice1 do pozice2. }} 

Skutečný implementační kód pro výpočty vzdálenosti a kurzu vynechávám.

Následující kód představuje typické použití Pozice a PositionUtility:

// Vytvořit pozici představující můj dům Pozice myHouse = nová pozice (); myHouse.latitude = 36.538611; myHouse.longitude = -121,797500; // Vytvoření pozice představující místní kavárnu Position coffeeShop = new Position (); coffeeShop.latitude = 36.539722; coffeeShop.longitude = -121,907222; // Pomocí nástroje PositionUtility vypočítáte vzdálenost a směr z mého domu // do místní kavárny. dvojitá vzdálenost = PositionUtility.distance (myHouse, coffeeShop); dvojitý nadpis = PositionUtility.heading (myHouse, coffeeShop); // Tisk výsledků System.out.println ("Z mého domu v (" + myHouse.latitude + "," + myHouse.longitude + ") do kavárny v (" + coffeeShop.latitude + "," + coffeeShop. zeměpisná délka + ") je vzdálenost" + vzdálenost + "při směru" + směru + "stupňů."); 

Kód generuje výstup níže, což naznačuje, že kavárna je přímo na západ (270,8 stupňů) od mého domu ve vzdálenosti 6,09. Pozdější diskuse se zabývá nedostatkem jednotek vzdálenosti.

 ================================================== ================== Z mého domu v (36.538611, -121,7975) do kavárny v (36.539722, -121,907222) je vzdálenost 6,0873776351893385 v okruhu 270,7547022304523 stupňů. ================================================== ================== 

Pozice, PositionUtilitya jejich použití kódu je trochu znepokojující a rozhodně není příliš objektově orientované. Ale jak to může být? Java je objektově orientovaný jazyk a kód používá objekty!

Ačkoli kód může používat objekty Java, dělá to způsobem, který připomíná minulou éru: obslužné funkce fungující na datových strukturách. Vítejte v roce 1972! Když se prezident Nixon schoulil nad tajnými magnetofonovými nahrávkami, počítačoví profesionálové kódující v procedurálním jazyce Fortran vzrušeně používali právě tímto způsobem novou Mezinárodní knihovnu matematiky a statistiky (IMSL). Úložiště kódů, jako je IMSL, byla plná funkcí pro numerické výpočty. Uživatelé předávali data těmto funkcím v dlouhých seznamech parametrů, které občas zahrnovaly nejen vstupní, ale i výstupní datové struktury. (IMSL se v průběhu let neustále vyvíjel a vývojářům prostředí Java je nyní k dispozici verze.)

V současném designu Pozice je jednoduchá datová struktura a PositionUtility je úložiště knihovních funkcí ve stylu IMSL, které funguje Pozice data. Jak ukazuje výše uvedený příklad, moderní objektově orientované jazyky nutně nevylučují použití zastaralých procedurálních technik.

Sdružování dat a metod

Kód lze snadno vylepšit. Proč pro začátek umisťovat data a funkce, které na těchto datech pracují, do samostatných modulů? Třídy Java umožňují spojovat data a metody dohromady:

public class Position {public double distance (Position position) {// Vypočítá a vrátí vzdálenost z tohoto objektu do zadané // polohy. } veřejné dvojité záhlaví (Pozice pozice) {// Vypočítá a vrátí záhlaví z tohoto objektu na zadanou // pozici. } veřejná dvojitá zeměpisná šířka; veřejná dvojitá zeměpisná délka; } 

Uvedení polohových datových položek a implementačního kódu pro výpočet vzdálenosti a kurzu do stejné třídy odstraňuje potřebu samostatného PositionUtility třída. Nyní Pozice se začíná podobat skutečné objektově orientované třídě. Následující kód používá tuto novou verzi, která spojuje data a metody dohromady:

Pozice myHouse = nová pozice (); myHouse.latitude = 36.538611; myHouse.longitude = -121,797500; Pozice coffeeShop = nová pozice (); coffeeShop.latitude = 36.539722; coffeeShop.longitude = -121,907222; double distance = myHouse.distance (coffeeShop); dvojitý nadpis = myHouse.heading (coffeeShop); System.out.println ("From my house at (" + myHouse.latitude + "," + myHouse.longitude + ") to the coffee shop at (" + coffeeShop.latitude + "," + coffeeShop.longitude + ") je vzdálenost „+ vzdálenost +“ při směru „+ směrování +„ stupňů. “); 

Výstup je identický jako dříve, a co je důležitější, výše uvedený kód vypadá přirozeněji. Předchozí verze prošla dvěma Pozice objekty k funkci v samostatné třídě nástrojů pro výpočet vzdálenosti a kurzu. V tomto kódu výpočet záhlaví s voláním metody util.heading (myHouse, coffeeShop) jasně neukazovaly směr výpočtu. Vývojář si musí pamatovat, že obslužná funkce vypočítá záhlaví od prvního parametru k druhému.

Ve srovnání výše uvedený kód používá příkaz myHouse.heading (coffeeShop) k výpočtu stejného záhlaví. Sémantika volání jasně naznačuje, že směr postupuje z mého domu do kavárny. Převod funkce dvou argumentů nadpis (Pozice, Pozice) na funkci s jedním argumentem position.heading (Pozice) je známý jako kari funkce. Currying efektivně specializuje funkci na svůj první argument, což vede k jasnější sémantice.

Umístění metod s využitím Pozice data třídy v Pozice třída sama dělá currying funkcí vzdálenost a nadpis možný. Změna struktury volání funkcí tímto způsobem je významnou výhodou oproti procedurálním jazykům. Třída Pozice nyní představuje abstraktní datový typ, který zapouzdřuje data a algoritmy, které na těchto datech pracují. Jako typ definovaný uživatelem Pozice objekty jsou také občany první třídy, kteří využívají všech výhod systému jazykových typů Java.

Jazykovým zařízením, které sdružuje data s operacemi, které s těmito daty jsou, je zapouzdření. Zapouzdření nezaručuje ochranu dat ani skrytí informací. Zapouzdření nezaručuje ani soudržný design třídy. K dosažení těchto atributů návrhu kvality je zapotřebí technik nad rámec zapouzdření poskytovaného jazykem. Jak je aktuálně implementováno, třída Pozice neobsahuje nadbytečné nebo nesouvisející údaje a metody, ale Pozice vystavuje obojí zeměpisná šířka a zeměpisná délka v surové formě. To umožňuje každému klientovi třídy Pozice přímo změnit buď interní datovou položku bez jakéhokoli zásahu Pozice. Je zřejmé, že zapouzdření nestačí.

Obranné programování

Abych dále prozkoumal důsledky odhalení interních datových položek, předpokládám, že se rozhodnu přidat trochu obranného programování Pozice omezením zeměpisné šířky a délky na rozsahy určené GPS. Zeměpisná šířka spadá do rozsahu [-90, 90] a zeměpisná délka v rozsahu (-180, 180). Expozice datových položek zeměpisná šířka a zeměpisná délka v PoziceDíky současné implementaci je toto obranné programování nemožné.

Vytváření atributů zeměpisná šířka a délka soukromé datové členy třídy Pozice a přidání jednoduchých metod přístupu a mutátoru, také běžně nazývaných getry a setry, poskytuje jednoduchý prostředek k odhalení položek surových dat. V níže uvedeném příkladu kódu metody setteru vhodně kontrolují vnitřní hodnoty zeměpisná šířka a zeměpisná délka. Spíše než hodit výjimku, určuji provedení modulo aritmetiky na vstupních hodnotách, aby se interní hodnoty udržovaly ve stanovených rozmezích. Například pokus o nastavení zeměpisné šířky na 181,0 má za následek interní nastavení -179,0 pro zeměpisná šířka.

Následující kód přidává metody getter a setter pro přístup k soukromým datovým členům zeměpisná šířka a zeměpisná délka:

veřejná třída Pozice {veřejná pozice (dvojitá zeměpisná šířka, dvojitá zeměpisná délka) {setLatitude (zeměpisná šířka); setLongitude (longitude); } public void setLatitude (double latitude) {// Zajistěte -90 <= zeměpisná šířka <= 90 pomocí aritmetiky modulo. // Kód není zobrazen. // Potom nastavíme proměnnou instance. this.latitude = zeměpisná šířka; } public void setLongitude (double longitude) {// Zajistěte -180 <zeměpisná délka <= 180 pomocí aritmetiky modulo. // Kód není zobrazen. // Potom nastavíme proměnnou instance. this.longitude = longitude; } public double getLatitude () {return latitude; } public double getLongitude () {return longitude; } public double distance (Position position) {// Vypočítá a vrátí vzdálenost z tohoto objektu do zadané // polohy. // Kód není zobrazen. } veřejné dvojité záhlaví (Pozice pozice) {// Vypočítá a vrátí záhlaví z tohoto objektu na zadanou // pozici. } soukromá dvojitá zeměpisná šířka; soukromá dvojitá zeměpisná délka; } 

Pomocí výše uvedené verze Pozice vyžaduje pouze drobné změny. Jako první změnu, protože výše uvedený kód určuje konstruktor, který trvá dva dvojnásobek argumenty, výchozí konstruktor již není k dispozici. Následující příklad používá nový konstruktor i nové metody getru. Výstup zůstává stejný jako v prvním příkladu.

Pozice myHouse = nová pozice (36.538611, -121,797500); Pozice coffeeShop = nová pozice (36.539722, -121,907222); double distance = myHouse.distance (coffeeShop); dvojitý nadpis = myHouse.heading (coffeeShop); System.out.println ("From my house at (" + myHouse.getLatitude () + "," + myHouse.getLongitude () + ") to the coffee shop at (" + coffeeShop.getLatitude () + "," + coffeeShop.getLongitude () + ") je vzdálenost" + vzdálenost + "v záhlaví" + záhlaví + "stupňů."); 

Volba omezit přijatelné hodnoty zeměpisná šířka a zeměpisná délka přes metody setteru je přísně rozhodnutí o návrhu. Zapouzdření nehraje žádnou roli. To znamená, že zapouzdření, jak se projevuje v jazyce Java, nezaručuje ochranu interních dat. Jako vývojář můžete odhalit interní prvky vaší třídy. Přesto byste měli omezit přístup a úpravy interních datových položek pomocí metod getter a setter.

Izolační změna potenciálu

Ochrana interních dat je pouze jedním z mnoha problémů, které podporují rozhodování o designu nad zapouzdřením jazyka. Izolace ke změně je další. Úpravy vnitřní struktury třídy by neměly, pokud je to možné, ovlivnit třídy klientů.

Například jsem si dříve všiml, že výpočet vzdálenosti ve třídě Pozice neoznačoval jednotky. Aby byla užitečná, údajná vzdálenost 6,09 od mého domu k kavárně zjevně potřebuje měrnou jednotku. Možná znám směr, kterým se mám ubírat, ale nevím, jestli jít 6,09 metru, jet 6,09 mil nebo letět 6,09 tisíc kilometrů.

$config[zx-auto] not found$config[zx-overlay] not found