Java - rozhraní, dědičnost a abstraktní třídy

iwtu

Re:Java - rozhraní, dědičnost a abstraktní třídy
« Odpověď #30 kdy: 16. 07. 2013, 14:12:24 »
Programuj a ak trochu premyslas, prides na to. Mne tie umele priklady nikdy nesedeli, vacsinou tych znalosti som ziskal v praxi premyslanim, citom a potom diskusiou s nejakym profikom. Programuj, ako najlepsie viac a casom na to prides.


Martin

Re:Java - rozhraní, dědičnost a abstraktní třídy
« Odpověď #31 kdy: 16. 07. 2013, 14:28:53 »
Dobře, všem vám moc díky za odpovědi, myslím, že už to je pro mě trošku jasnější, ale kdyby náhodou měl někdo ještě nějaké pěkné vysvětlení, nebudu se mu bránit, budu jedině rád když ho sem někam napíšete :).

perceptron

Re:Java - rozhraní, dědičnost a abstraktní třídy
« Odpověď #32 kdy: 16. 07. 2013, 15:01:28 »
pri navrhu triedy casto zacnem navrhovanim interfejsu, kde poviem, CO ma trieda robit, teda ake metody ma poskytovat (ono je to dobre sparovane s pisanim unit testov, ale tym vas teraz trapit nebudem).

potom vytvorim triedu, ktora implementuje ten interfejs a obsahuje kod a instancne premenne, ktore zabezpecia, ze metody budu robit to, co maju

znalci urcite plestia oci, ale casom som samozrejme ziskal cit, ked viem, ze nie vzdy potrebujem zbytocny interfejs a triedu, ale staci mi len trieda.

abstraktne metody pouzivam vtedy, ak vyrobim abstraktnu triedu, kde niektore metody su naimplementovane a ostatne ponechane na podtriedy -- pekny priklad je, ked mate algoritmus o troch krokoch, uvod a zaver su furt rovnake, ale stred sa meni na zaklade rozlicnych okolnosti. uvod a zaver uvediete do dvoch abstraktnych metod abstraktnej triedy a "stred" bude abstraktna metoda, pricom kazda podtrieda si ten premenlivy stred implementuje po svojom. toto su vsak uz pokrocilejsie veci.

s dedicnostou treba dat velky pozor: sice ucebnice dokola tvrdia, ze dedicnost je IS-A (pes JE zvieratom, teda Pes extends Zviera), ale existuje kopa pripadov, ked to v OOP jednoducho nefunguje. (klasicky priklad je kruznica a elipsa, kde sice kruznica IS-A elipsa, ale navrhnut to tak v OOP je fail)

+ co vravi duro

hmm

Re:Java - rozhraní, dědičnost a abstraktní třídy
« Odpověď #33 kdy: 16. 07. 2013, 16:23:10 »
klasicky priklad je kruznica a elipsa, kde sice kruznica IS-A elipsa, ale navrhnut to tak v OOP je fail

preco?

podlesh

Re:Java - rozhraní, dědičnost a abstraktní třídy
« Odpověď #34 kdy: 16. 07. 2013, 16:34:52 »
Základní chyba je pokoušet se objekty (resp. třídy) v programu modelovat na základě popisu pojmů v reálném světě.
Jako vysvětlení příkladů v učebnici ano, ale v reálném programu to málokdy dává dobré výsledky.


Natix

Re:Java - rozhraní, dědičnost a abstraktní třídy
« Odpověď #35 kdy: 16. 07. 2013, 16:59:54 »
klasicky priklad je kruznica a elipsa, kde sice kruznica IS-A elipsa, ale navrhnut to tak v OOP je fail

preco?

Problém kružnice a elipsy (případně čtverce a obdélníka) nastává pouze v případě, že budeš obě třídy modelovat jako mutable. Immutable třídy tímhle problémem netrpí, jenže immutabilita se stále ještě moc nenosí, že.

Nadefinujme si elipsu jako jednoduchou mutable beanu:

Kód: [Vybrat]
class Ellipsis {
  private int x;
  private int y;

  Ellipsis(int x, int y) {
    this.x = x;
    this.y = y;
  }

  int getX() {
    return x;
  }
  void setX(int x) {
    this.x = x;
  }

  int getY() {
    return y;
  }
  void setY() {
    this.y = y;
  }
}

Zatím žádný problém, že? Tak se podívejme na kružnici:

Kód: [Vybrat]
class Circle extends Ellipsis {
   private int radius;

  Circle(int radius) {
    this.radius = radius;
  }

  int getRadius() {
    return radius;
  }

  // nu a teď je potřeba něco udělat s metodami zděděnými z elipsy
  // nejdřív gettery, s tím není problém

  int getX() {
    return radius;
  }
   
  int getY() {
    return radius;
  }

  // no a co se settery?
  // jakákoliv implementace buď poruší kontrakt kružnice nebo kontrakt elipsy

  void setX(int x) { ??? }

  void setY(int y) { ??? }
}


perceptron

Re:Java - rozhraní, dědičnost a abstraktní třídy
« Odpověď #36 kdy: 16. 07. 2013, 18:29:21 »
je to presne ako vravi predrecnik: ked mame elipsu, ta sa da natahovat v oboch osiach (= horizontalne i vertikalne cez setWidth() resp setHeight()). Zmena sirky neovplyvni zmenu vysky a naopak. Ta posledna veta definuje kontrakt, co je akasi "spolocenska zmluva", ktora sa dohodne medzi pouzivatelom triedy a samotnou triedou. Ak je kontrakt dodrzany, metoda dava spravne a ocakavane vysledky. Pre prekryte metody plati, ze musia dodrzat kontrakt rodicovskej metody (inak by to vracalo bludy).

A teraz zoberme kruznicu, co dedi od elipsy. Musi prekryt metody setWidth() aj setHeight(). Mame dve moznosti:
* bud so zmenou sirky sa musi menit aj vyska. To je porusenie kontraktu.
* alebo sa so zmenou sirky vyska nemeni, co sice dodrzi kontrakt rodicovskej metody, ale tym padom mame kruznicu, co zrazu prestane byt kruznicou ("zelipsovatie"), co je logicky blud (nechcete mat objekt typu Kruznica, ktory ma rozlicne polomery...)

jeden z principov prekryvania (override / dedenia) metod, je:
* predpodmienky (tvrdenia, ktore musia platit pred spustenim metody) nemozno v oddedenej metode pritvrdit. Ak pre elipsu plati, ze polosi mozu byt rozne, pre kruznicu nesmieme pred zmenou sirky vyzadovat, ze polosi (= polomer) musia byt rovnake
* postpodmienky (tvrdenia, ktore musia platit po dobehnuti metody) nemozno zoslabit. Ak pre elipsu plati, ze po zmene sirky sa vyska zachova, nemozeme v kruznici zrazu povedat, ze po zmene sirky sa moze zmenit vyska.
* invarianty (tvrdenia, ktore su nemenne pocas behu metody) sa nezmenia -- toto tu nema zmysel.

Cele to suvisi s Liskovovej substitucnym principom: co volne povedane hovori, ze ked mame v premennej typu elipsa nejaky objekt a nahradime ho napr. kruznicou, spravanie sa nezmeni. Je vidiet, ze to nie je pravda, lebo spravanie kruznice a elipsy su ine (staci si to predstavit na priklade vektoroveho editora a la Illustrator/Inkscape/Corel) a preto nema tam byt hierarchia dedicnosti, aj ked matematicka definicia hovori inak.

a ako hovori tiez predrecnik, cele je to o tom, ze trieda je navrhnuta ako premenliva (teda hodnoty jej instancnych premennych sa mozu menit), ale klasicky javoidny dizajn tried velmi neuvazuje o situaciach nemenlivych tried.

Radek Miček

Re:Java - rozhraní, dědičnost a abstraktní třídy
« Odpověď #37 kdy: 16. 07. 2013, 18:47:55 »
V jazycích s reflexí, kde mohu zjistit jednoznačný identifikátor třídy pro každou hodnotu, nemá neabstraktní třída žádný podtyp kromě sama sebe.

Svetozar Bludna

Re:Java - rozhraní, dědičnost a abstraktní třídy
« Odpověď #38 kdy: 17. 07. 2013, 00:34:05 »
klasicky priklad je kruznica a elipsa, kde sice kruznica IS-A elipsa, ale navrhnut to tak v OOP je fail

Ale p*ču! Na tento přístup přesně sedí pořekadlo vylít s vaničkou i nemluvně. Tímhle způsobem akorát uměle vytváříte problémy a omezení, které ve skutečnosti vůbec neexistují. Komplikujete si tím život a žádnou výhodu to nepřináší - kromě toho, že máte pocit, že je to tak správnější. Mimochodem - odvození elipsy z kružnice by ve vašem pojetí OOP bylo sice naprosto čisté, ale prakticky je to úplně k ničemu.

eMko

  • ****
  • 456
    • Zobrazit profil
    • E-mail
Re:Java - rozhraní, dědičnost a abstraktní třídy
« Odpověď #39 kdy: 17. 07. 2013, 06:49:45 »
V jazycích s reflexí, kde mohu zjistit jednoznačný identifikátor třídy pro každou hodnotu, nemá neabstraktní třída žádný podtyp kromě sama sebe.

Reflexe je pomalá (alespoň v Javě a C#).

Teda sorry. Pomalejší než pomalá. Udělej to 1x, nepoznáš rozdíl. Udělej to 1000x (což na opravdových projektech není nereálná hodnota) a nebudeš se stačit divit.

s dedicnostou treba dat velky pozor: sice ucebnice dokola tvrdia, ze dedicnost je IS-A (pes JE zvieratom, teda Pes extends Zviera), ale existuje kopa pripadov, ked to v OOP jednoducho nefunguje. (klasicky priklad je kruznica a elipsa, kde sice kruznica IS-A elipsa, ale navrhnut to tak v OOP je fail)

Nemusí být, záleží na tom, co "modeluješ".

perceptron

Re:Java - rozhraní, dědičnost a abstraktní třídy
« Odpověď #40 kdy: 17. 07. 2013, 11:14:48 »
Citace
Ale p*ču!
mozete to elaborovat?

---------
reflexia nie je nejake vykonnostne megaterno, ale v jave 5 sa dramaticky zvysila oproti prastarym verziam. ale to je tak vsetko, nejeden projekt na tom stoji a pada - taky spring je plny reflexie. a to nehovorim o dalsich zverinach, ako manipulacia bajtkodu za behu (vid serial pana Tisnovskeho), co je tiez domena springu a hibernate.

nejak by som to nehrotil.

Kolemjdoucí

Re:Java - rozhraní, dědičnost a abstraktní třídy
« Odpověď #41 kdy: 17. 07. 2013, 11:35:58 »
ako manipulacia bajtkodu za behu (vid serial pana Tisnovskeho), co je tiez domena springu a hibernate.

V baště Javy Springu a Hibernate se manipuluje s bytecode ? :o Překvapit mě to nemůže, jenom bych se v takovém případě smíchy počůral ;D

Makovec

Re:Java - rozhraní, dědičnost a abstraktní třídy
« Odpověď #42 kdy: 17. 07. 2013, 11:47:10 »
A proč ne takto?

Kód: [Vybrat]
class Circle extends Ellipsis {

  void setX(int x) { this.radius = x }

  void setY(int y) { this.radius = y }
}

Natix

Re:Java - rozhraní, dědičnost a abstraktní třídy
« Odpověď #43 kdy: 17. 07. 2013, 12:31:34 »
A proč ne takto?

Kód: [Vybrat]
class Circle extends Ellipsis {

  void setX(int x) { this.radius = x }

  void setY(int y) { this.radius = y }
}

Protože v tomhle případě nemůžu kružnici použít jako elipsu.

Kód: [Vybrat]
Ellipsis e = new Circle(5);
e.getX(); // 5
e.getY(); // 5

e.setX(10);
e.getX(); // 10
e.getY(); // 10 !!!

Poslední řádka porušuje kontrakt pro elipsu. Pokud změním velikost jedné poloosy, tak to druhou poloosu nesmí ovlivnit, což se ovšem tady stalo.

Tím pádem takováto kružnice je sice podtřída elipsy, ale už ne její podtyp, což je dost nepříjemné. V zásadě jde o porušení LSP.

noef

  • *****
  • 897
    • Zobrazit profil
    • E-mail
Re:Java - rozhraní, dědičnost a abstraktní třídy
« Odpověď #44 kdy: 17. 07. 2013, 13:22:53 »
ako manipulacia bajtkodu za behu (vid serial pana Tisnovskeho), co je tiez domena springu a hibernate.

V baště Javy Springu a Hibernate se manipuluje s bytecode ? :o Překvapit mě to nemůže, jenom bych se v takovém případě smíchy počůral ;D

resit to nativne = transformovat bytecode je nejrychlejsi (pri behu), je to opravdu takove prekvapeni? (navic si ani nejsem jistej, jestli by jen pomoci reflexe vse slo resit.)

https://github.com/hibernate/hibernate-orm/blob/master/libraries.gradle#L54
https://github.com/hibernate/hibernate-orm/blob/master/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/Enhancer.java

V jazycích s reflexí, kde mohu zjistit jednoznačný identifikátor třídy pro každou hodnotu, nemá neabstraktní třída žádný podtyp kromě sama sebe.
Reflexe je pomalá (alespoň v Javě a C#).

Teda sorry. Pomalejší než pomalá. Udělej to 1x, nepoznáš rozdíl. Udělej to 1000x (což na opravdových projektech není nereálná hodnota) a nebudeš se stačit divit.

neni uplne pravda (alespon v pripade javy). pokud se vhodne pouziva MethodHandle tak je stejne rychla jako nativni kod.