14. listopadu 2009

Definice rozhraní dohromady se třídami

Nedávno jsem procházel cizí kód a narazil jsem na definici rozhraní dohromady s definicí použitých tříd, viz následující příklad (příklad je uměle vytvořen):


public interface Connector {

Status getStatus();


/**
* Connection status.
*/
public static class Status {
private int code;


/**
* Constructor
*
* @param code Connection status code
*/
public Status(int code) {
this.code = code;
}


public int getCode() {
return this.code;
}
}
}

Mě by tato konstrukce nikdy nenapadla, proto mě to hned praštilo. Čím více jsem ale o tom přemýšlel a rozebíral s autorem, tak jsem vlastně zjišťoval, že moc argumentů, proč by to tak nemělo být, vlastně nemám.

Zde jsou mé dva argumenty, proč se mi to "nezdá":
  • První argument asi moc neobstojí, ale pro mě je celkem důležitý - rozhraní se definují pomocí rozhraní, třídy pomocí tříd. Proč to tedy míchat dohromady? Autor mi na to řekl, že chtěl zdůraznit spojitost mezi rozhraním a použitými třídami. Tomu moc nerozumím, protože všechny třídy použité v rozhraní jsou automaticky součástí API. Možná měl na mysli to, že kdykoliv budu pracovat se třídou Status, tak budu muset používat prefix rozhraní, tedy Connector.Status a tím tedy vždy bude zřejmá příslušnost třídy k danému rozhraní.

  • Všechny mé další výtky směřují na možné úpravy, rozšíření v budoucnu - to je jeden z důležitých aspektů kvalitního API, aby bylo možné zachovat zpětnou kompatibilitu a přitom mít možnost dané API rozšiřovat. Co když budu chtít třídu Status použít v budoucnu i v jiném rozhraní? Co když budu chtít vytvořit třídu StatusExt jako rozšíření třídy Status?

Přijde mi tedy, že kvůli minimální "výhodě" mohu mít v budoucnu problémy.

Spousta lidí má představu, co to asi tak API je, ale už jen pár lidí si dokáže představit, jak je těžké vytvořit kvalitní API. Já sám se v této oblasti hodně učím a to zejména z knížky "Practical API Design" od Jaroslava Tulacha. Ale je hlavně potřeba praxe a mít vůbec možnost taková API navrhovat a rozvíjet, což se ve standardních webových aplikacích moc často neděje.

Proto by mě moc zajímal názor ostatních - máte nějaký další argument na mojí podporu? Nebo se vám zdá, že to ani nemá cenu řešit, a že je vše v pořádku?

3 komentáře:

Tomas řekl(a)...

Nejen že by mě podobná konstrukce nenapadla, ale ani můj obvyklý programovací jazyk ji nepovolí. Trochu mi to připomíná abstract factory a tam by samozřejmě podobná konstukce úplně ztrácela smysl.

Tomáš Piňos řekl(a)...

Je to dobrý příklad problému, na který má většina programátorů jasný názor, ale pro jeho vysvětlení se obtížně hledají argumenty.

Jsou lidé, kteří to propagují jako vzor. Autor třídu uvnitř rozhranní použivá tam, kde třída nemá bez rozhranní význam a nemůže bez něj ani existovat. Výhodou je pro něj nižší "namespace pollution" a "fewer source files".

Naopak třeba Joshua Bloch v Effective Java zdůrazňuje, že rozhranní má být využíváno k definici typu a ničeho jiného.

Já bych definici třídy v rozhranní nepoužil hlavně proto, že to pro mě zhoršuje čitelnost kódu a taky uvaluje zbytečné omezení na tu třídu. Pokud chci zdůraznit těsnou vazbu mezi rozhranním a nějakou třidou, dám je do stejné package nebo použiju vhodné pojmenování (místo Status bych tady použil rekněme ConnectorStatus).

Napadá mě analogie s UML class diagramy a rozdíly mezi agregací a kompozicí. Spousta lidí se ráda utápí v diskuzích o rozdílu mezi těmito asociacemi, i když je to většinou pro danou doménu zavádějící a nedůležité. Já proto raději vždy používám obyčejnou asociaci se správně popsanými multiplicitami.

Abych se moc nevzdálil - přijde mi důležitě sdělit klientovi rozhranní dost, ale ne příliš. A umístění definice třídy do rozhranní už podle mě je příliš.

Vitek Tajzich řekl(a)...

Osobne se mi zda tento zpusob jako anti-pattern.

Kazdy asi mame nejakou zakladni implementaci sveho rozhranni a tu pouzivame, ale to neznamena, ze ji musim definovat spolecne s rozhrannim.

Nejde spise o "lenost" ? ;-)