Programování

Proč metody getter a setter jsou zlé

Neměl jsem v úmyslu zahájit sérii „je zlo“, ale několik čtenářů mě požádalo, abych vysvětlil, proč jsem se zmínil, že byste se měli vyhnout metodám get / set ve sloupci z minulého měsíce „Why extends Is Evil“.

Ačkoli metody getter / setter jsou v Javě běžné, nejsou nijak zvlášť objektově orientované (OO). Ve skutečnosti mohou poškodit udržovatelnost vašeho kódu. Navíc přítomnost mnoha metod getter a setter je červeným příznakem, že program nemusí být nutně dobře navržen z pohledu OO.

Tento článek vysvětluje, proč byste neměli používat getry a settery (a kdy je můžete použít), a navrhuje metodologii designu, která vám pomůže vymanit se z mentality getter / setter.

O povaze designu

Než se pustím do jiného sloupce souvisejícího s designem (s provokativním názvem, neméně), chci objasnit několik věcí.

Byl jsem ohromen některými komentáři čtenářů, které vyplynuly ze sloupce z minulého měsíce „Why extends Is Evil“ (viz Talkback na poslední stránce článku). Někteří lidé věřili, že jsem tvrdil, že objektová orientace je špatná jednoduše proto, že rozšiřuje má problémy, jako by si oba pojmy byly rovnocenné. To rozhodně není to, co já myslel Řekl jsem, tak mi dovolte objasnit některé meta-problémy.

Tento sloupec a článek z minulého měsíce jsou o designu. Design je od přírody řada kompromisů. Každá volba má dobrou i špatnou stránku a vy se rozhodujete v kontextu celkových kritérií definovaných nutností. Dobré a špatné však nejsou absolutní. Dobré rozhodnutí v jednom kontextu může být špatné v jiném.

Pokud nerozumíte oběma stranám problému, nemůžete udělat inteligentní volbu; ve skutečnosti, pokud nerozumíte všem důsledkům svých činů, vůbec to nenavrhujete. Klopýtáte ve tmě. Není náhodou, že každá kapitola v Gangu čtyř Designové vzory Kniha obsahuje sekci „Důsledky“, která popisuje, kdy a proč je použití vzoru nevhodné.

Tvrdit, že některá jazyková funkce nebo běžný programovací idiom (například přístupoví uživatelé) mají problémy, není totéž jako říkat, že byste je za žádných okolností nikdy neměli používat. A to, že se funkce nebo idiom běžně používá, ještě neznamená, že vás by měl použijte to buď. Neinformovaní programátoři píší mnoho programů a pouhé použití u Sun Microsystems nebo Microsoftu kouzelně nezlepší něčí programovací ani designové schopnosti. Balíčky Java obsahují spoustu skvělého kódu. Ale existují i ​​části tohoto kódu, jsem si jistý, že autoři jsou v rozpacích přiznat, že napsali.

Stejně tak marketingové nebo politické pobídky často tlačí designové idiomy. Programátoři někdy dělají špatná rozhodnutí, ale společnosti chtějí propagovat, co technologie dokáže, takže de-zdůrazňují, že způsob, jakým to děláte, je méně než ideální. Využívají špatnou situaci co nejlépe. V důsledku toho jednáte nezodpovědně, když si osvojíte jakoukoli programovací praxi jednoduše proto, že „takhle máte dělat věci“. Mnoho neúspěšných projektů Enterprise JavaBeans (EJB) dokazuje tento princip. Technologie založená na EJB je skvělá technologie při správném použití, ale při nevhodném použití může společnost doslova zničit.

Chci říct, že byste neměli programovat slepě. Musíte pochopit, jakou katastrofu může funkce nebo idiom způsobit. Přitom jste v mnohem lepší pozici, abyste se mohli rozhodnout, zda byste měli použít tuto funkci nebo idiom. Vaše rozhodnutí by měla být informovaná a pragmatická. Účelem těchto článků je pomoci vám přistupovat k programování s otevřenýma očima.

Abstrakce dat

Základní zásadou systémů OO je, že objekt by neměl vystavovat žádné podrobnosti implementace. Tímto způsobem můžete změnit implementaci beze změny kódu, který používá objekt. Z toho vyplývá, že v systémech OO byste se měli vyhnout funkcím getter a setter, protože většinou poskytují přístup k podrobnostem implementace.

Chcete-li zjistit proč, zvažte, že na a. Může být 1 000 volání getX () metoda ve vašem programu a každé volání předpokládá, že návratová hodnota je určitého typu. Můžete uložit getX ()Například návratová hodnota v místní proměnné a tento typ proměnné musí odpovídat typu návratové hodnoty. Pokud potřebujete změnit způsob implementace objektu takovým způsobem, že se změní typ X, máte velké potíže.

Pokud X byl int, ale teď musí být dlouho, dostanete 1 000 chyb při kompilaci. Pokud problém nesprávně vyřešíte sesláním návratové hodnoty na int, kód se zkompiluje čistě, ale nebude fungovat. (Návratová hodnota může být zkrácena.) Chcete-li kompenzovat změnu, musíte upravit kód obklopující každé z těchto 1 000 volání. Určitě nechci dělat tolik práce.

Jedním ze základních principů OO systémů je abstrakce dat. Měli byste úplně skrýt způsob, jakým objekt implementuje obslužnou rutinu zprávy, od zbytku programu. To je jeden z důvodů, proč by všechny vaše proměnné instance (nekonstantní pole třídy) měly být soukromé.

Pokud vytvoříte proměnnou instance veřejnost, pak nemůžete změnit pole, protože se třída v průběhu času vyvíjí, protože byste porušili externí kód, který pole používá. Nechcete prohledávat 1000 použití třídy jednoduše proto, že tuto třídu změníte.

Tento princip skrývání implementace vede k dobrému testu kvality systému OO: Můžete provést rozsáhlé změny v definici třídy - dokonce vyhodit celou věc a nahradit ji úplně jinou implementací - aniž by to mělo dopad na jakýkoli kód, který to používá předměty třídy? Tento druh modularizace je ústředním předpokladem orientace objektu a výrazně usnadňuje údržbu. Bez skrytí implementace nemá smysl používat další funkce OO.

Metody getter a setter (známé také jako přístupové objekty) jsou nebezpečné ze stejného důvodu veřejnost pole jsou nebezpečná: Poskytují externí přístup k podrobnostem implementace. Co když potřebujete změnit typ přístupného pole? Musíte také změnit návratový typ přístupového objektu. Tuto návratovou hodnotu použijete na mnoha místech, takže musíte také změnit celý tento kód. Chci omezit účinky změny na definici jedné třídy. Nechci, aby se vlnily do celého programu.

Vzhledem k tomu, že přistupující osoby porušují princip zapouzdření, můžete rozumně argumentovat, že systém, který přístupové nástroje těžce nebo nevhodně používá, jednoduše není objektově orientovaný. Pokud projdete procesem návrhu, na rozdíl od pouhého kódování, ve svém programu téměř nenajdete přistupující osoby. Tento proces je důležitý. K této otázce mám na konci článku více co říci.

Nedostatek metod getter / setter neznamená, že některá data neprotékají systémem. Nejlepší je nicméně co nejvíce minimalizovat pohyb dat. Moje zkušenost je, že udržovatelnost je nepřímo úměrná množství dat, která se pohybují mezi objekty. I když možná ještě nevidíte, jak je to možné, můžete většinu tohoto pohybu dat vlastně eliminovat.

Pečlivým navrhováním a zaměřením na to, co musíte udělat, spíše než na to, jak to děláte, eliminujete drtivou většinu metod getter / setter ve vašem programu. Nepožadujte informace, které potřebujete k provedení práce; požádejte objekt, který má informace, aby za vás udělal práci. Většina přistupujících uživatelů si najde cestu do kódu, protože návrháři nemysleli na dynamický model: runtime objekty a zprávy, které si navzájem posílají, aby provedli práci. Začínají (nesprávně) návrhem hierarchie tříd a poté se pokoušejí tyto třídy zavést do dynamického modelu. Tento přístup nikdy nefunguje. Chcete-li vytvořit statický model, musíte zjistit vztahy mezi třídami a tyto vztahy přesně odpovídají toku zpráv. Přidružení existuje mezi dvěma třídami pouze tehdy, když objekty jedné třídy odesílají zprávy objektům druhé. Hlavním účelem statického modelu je zachytit tyto informace o přidružení při dynamickém modelování.

Bez jasně definovaného dynamického modelu pouze hádáte, jak budete používat objekty třídy. V důsledku toho se metody přístupových modulů často dostanou do modelu, protože musíte poskytnout co nejvíce přístupu, protože nemůžete předpovědět, zda jej budete potřebovat nebo ne. Tento druh strategie podle hádání je v nejlepším případě neefektivní. Ztrácíte čas psaním zbytečných metod (nebo přidáváním zbytečných schopností do tříd).

Přistupující osoby také končí v designech silou zvyku. Když procedurální programátoři přijmou Javu, mají tendenci začít budováním známého kódu. Procedurální jazyky nemají třídy, ale mají C. struktur (myslím: třída bez metod). Zdá se tedy přirozené napodobovat a struktur budováním definic tříd prakticky bez metod a nic jiného než veřejnost pole. Tito procedurální programátoři někde čtou, že pole by měla být soukromé, ale proto dělají pole soukromé a zásobování veřejnost přístupové metody. Veřejný přístup však jen zkomplikovali. Určitě neurobili systém objektově orientovaným.

Nakreslete se

Jedním z důsledků zapouzdření celého pole je konstrukce uživatelského rozhraní (UI). Pokud nemůžete použít přístupové objekty, nemůžete volat třídu Tvůrce uživatelského rozhraní a getAttribute () metoda. Místo toho mají třídy prvky jako Nakresli se(...) metody.

A getIdentity () metoda může samozřejmě také fungovat, za předpokladu, že vrátí objekt, který implementuje Identita rozhraní. Toto rozhraní musí obsahovat a Nakresli se() (nebo dej miJComponent-that-představuje-vaši-identitu). Ačkoli getIdentity začíná na „get“, není to přístupový objekt, protože nevrací pouze pole. Vrátí složitý objekt, který má rozumné chování. I když mám Identita objekt, stále netuším, jak je interně reprezentována identita.

Samozřejmě, a Nakresli se() strategie znamená, že jsem (lapal po dechu) vložil kód UI do obchodní logiky. Zvažte, co se stane, když se změní požadavky uživatelského rozhraní. Řekněme, že chci tento atribut představovat úplně jiným způsobem. Dnes je „identita“ jméno; zítra je to jméno a identifikační číslo; den poté je to jméno, identifikační číslo a obrázek. Omezím rozsah těchto změn na jedno místo v kódu. Pokud mám dárekJComponent-that-představuje-třídu vaší identity, pak jsem izoloval způsob, jakým jsou identity reprezentovány od zbytku systému.

Mějte na paměti, že jsem do obchodní logiky vlastně nevložil žádný kód uživatelského rozhraní. Napsal jsem vrstvu uživatelského rozhraní z hlediska AWT (Abstract Window Toolkit) nebo Swing, což jsou obě abstrakční vrstvy. Skutečný kód uživatelského rozhraní je v implementaci AWT / Swing. To je celý smysl abstrakční vrstvy - izolovat vaši obchodní logiku od mechaniky subsystému. Mohu snadno portovat do jiného grafického prostředí bez změny kódu, takže jediným problémem je trochu nepořádek. Tento nepořádek můžete snadno odstranit přesunutím veškerého kódu uživatelského rozhraní do vnitřní třídy (nebo pomocí návrhového vzoru Façade).

JavaBeans

Můžete namítnout, že řeknete: „Ale co JavaBeans?“ A co oni? Určitě můžete stavět JavaBeans bez getrů a setterů. The BeanCustomizer, BeanInfo, a BeanDescriptor třídy existují přesně pro tento účel. Návrháři specifikací JavaBean vrhli do obrazu idiom getr / setr, protože si mysleli, že by to byl snadný způsob, jak rychle vyrobit fazole - něco, co můžete dělat, když se učíte, jak to udělat správně. Bohužel to nikdo neudělal.

Accessors were created only as a way to tag certain properties so a UI-builder program or equivalent could identify them. Tyto metody byste neměli nazývat sami. Existují pro použití automatizovaného nástroje. Tento nástroj používá introspekční API v Třída třídy najít metody a extrapolovat existenci určitých vlastností z názvů metod. V praxi tento idiom založený na introspekci nefungoval. Díky tomu je kód nesmírně komplikovaný a procedurální. Programátoři, kteří nerozumí abstrakci dat, skutečně volají přistupující subjekty a v důsledku toho je kód méně udržovatelný. Z tohoto důvodu bude funkce metadat začleněna do prostředí Java 1.5 (bude k dispozici v polovině roku 2004). Takže místo:

soukromé int vlastnictví; public int getProperty () {návrat vlastnost; } public void setProperty (int hodnota} {vlastnost = hodnota;} 

Budete moci použít něco jako:

private @property int property; 

Nástroj pro konstrukci uživatelského rozhraní nebo ekvivalent použije introspekční API k vyhledání vlastností, místo aby zkoumal názvy metod a odvodil existenci vlastnosti z názvu. Žádný runtime accessor proto nepoškodí váš kód.

Kdy je přístupový bod v pořádku?

Nejprve, jak jsem již zmínil dříve, je v pořádku, aby metoda vrátila objekt z hlediska rozhraní, které objekt implementuje, protože toto rozhraní vás izoluje od změn implementační třídy. Tento druh metody (který vrací odkaz na rozhraní) není ve skutečnosti „getter“ ve smyslu metody, která pouze poskytuje přístup k poli. Pokud změníte interní implementaci poskytovatele, stačí změnit definici vráceného objektu, aby se změny přizpůsobily. Stále chráníte externí kód, který používá objekt prostřednictvím jeho rozhraní.