Omezená dědičnost (je něco lepšího než OOP?)

Kit

Re:Omezená dědičnost (je něco lepšího než OOP?)
« Odpověď #45 kdy: 31. 08. 2015, 18:36:45 »
Dalšími omezeními jde předejít dalším problémům. Pro C++ existují například BPravidla:

  • no virtual methods or virtual inheritance
  • no visible members or methods in any public data structure (that is, in any class declared in an .h file)
  • no mutations to public data structures
    • a strict form: no assignments or mutations whatsoever
    • a less strict form: no function may alter, directly or indirectly, any data it receives as arguments
       

Ještě bych prosil zakázat gettery a settery ... :)


Radek Miček

Re:Omezená dědičnost (je něco lepšího než OOP?)
« Odpověď #46 kdy: 31. 08. 2015, 18:38:24 »
Dalšími omezeními jde předejít dalším problémům. Pro C++ existují například BPravidla:

  • no virtual methods or virtual inheritance
  • no visible members or methods in any public data structure (that is, in any class declared in an .h file)
  • no mutations to public data structures
    • a strict form: no assignments or mutations whatsoever
    • a less strict form: no function may alter, directly or indirectly, any data it receives as arguments
       
       
Tak tohle mi už přijde jako úlet. Zakáže polovinu C++ a pak na to ručně bez podpory překladače roubuje Haskell. C++ už je dostatečný bordel i tak. Pokud chci letadlo, tak přece nebudu šroubovat křídla na traktor. :o

Souhlasím. Ostatně i autor to poznamenává v závěru:

BRules are at odds with the practice if not the very mentality of OOP. This begs the question: Is OOP indeed conducive to software development?


Nicméně BPravidla údajně garantují (pro C++ 98), že podtřída je podtyp (zřejmě se nepředpokládá použití reflexe) - tj. máte rozhodnutelná pravidla, jenž implikují platnost LSP.

JSH

Re:Omezená dědičnost (je něco lepšího než OOP?)
« Odpověď #47 kdy: 31. 08. 2015, 18:50:58 »
Nicméně BPravidla údajně garantují (pro C++ 98), že podtřída je podtyp (zřejmě se nepředpokládá použití reflexe) - tj. máte rozhodnutelná pravidla, jenž implikují platnost LSP.
Nemám nějaký formální důkaz ale na tohle by snad stačilo vynutit immutable data podle třetího bodu. Zakázal virtuální metody, ale pak si je tam stejně implementuje ručně (příklad s tvary). Zakázal členské metody ale pak je tam stejně implementuje ručně akorát bez tečky.

Radek Miček

Re:Omezená dědičnost (je něco lepšího než OOP?)
« Odpověď #48 kdy: 31. 08. 2015, 19:07:46 »
Nicméně BPravidla údajně garantují (pro C++ 98), že podtřída je podtyp (zřejmě se nepředpokládá použití reflexe) - tj. máte rozhodnutelná pravidla, jenž implikují platnost LSP.
Nemám nějaký formální důkaz ale na tohle by snad stačilo vynutit immutable data podle třetího bodu.

To bohužel nestačí. Můžete vzít například třídu T s jednou virtuální metodou, jejíž chování je definováno tak, že vždy vrátí konstantu 1. Když pak vezmete podtřídu S, kde tato metoda vrací konstantu 2, tak to už není podtyp. O objektech typu T snadno dokážete, že daná metoda vrátí 1. Tuto vlastno nejde dokázat pro objekty typu S, tj. podle LSP S není podtyp T (podle definice LSP z A Behavioral Notion of Subtyping, hned v úvodu na druhé stránce - Subtype Requirement).

Obecně rozhodovat LSP je ostře těžší než rozhodovat HP.

Ivan Nový

Re:Omezená dědičnost (je něco lepšího než OOP?)
« Odpověď #49 kdy: 31. 08. 2015, 19:45:47 »
Čtverec ani obdélník jako objekt nepotřebujete, stačí kosodélník definovaný strana a, strana b, úhel alfa, obdélník je Kosodélník(a, b, 90), čtverec je Kosodélník(a, a, 90), výpočet obsahu, obvodu je univerzální. V programu není důvod využívat speciálních vlastností obdélníka, nebo čtverce.


gamer

Re:Omezená dědičnost (je něco lepšího než OOP?)
« Odpověď #50 kdy: 31. 08. 2015, 20:05:25 »
Čtverec ani obdélník jako objekt nepotřebujete, stačí kosodélník definovaný strana a, strana b, úhel alfa, obdélník je Kosodélník(a, b, 90), čtverec je Kosodélník(a, a, 90), výpočet obsahu, obvodu je univerzální.
S touto logikou nepotřebuju ani kosodelník, vystačím si na všechno s polygonem.

V programu není důvod využívat speciálních vlastností obdélníka, nebo čtverce.
Samozřejmě že je důvod, např. ve hrách se kolize objektů řeší nejprve na úrovni axis aligned bounding boxu objektu (obdelník pro 2D, kvádr pro 3D), je to mnohem efektivnější než to počítat pro obecné polygony.

JSH

Re:Omezená dědičnost (je něco lepšího než OOP?)
« Odpověď #51 kdy: 31. 08. 2015, 20:10:40 »
To bohužel nestačí. Můžete vzít například třídu T s jednou virtuální metodou, jejíž chování je definováno tak, že vždy vrátí konstantu 1. Když pak vezmete podtřídu S, kde tato metoda vrací konstantu 2, tak to už není podtyp. O objektech typu T snadno dokážete, že daná metoda vrátí 1. Tuto vlastno nejde dokázat pro objekty typu S, tj. podle LSP S není podtyp T (podle definice LSP z A Behavioral Notion of Subtyping, hned v úvodu na druhé stránce - Subtype Requirement).
Pravda, tohle není podtyp. Tomuhle ale nezabrání ani ty BPravidla. Ve Shapes-no-oop.cc implementuje vlastní virtuální funkce, které ty pravidla neporušují a přitom s nima jde tohle udělat taky. Zakázal sice klíčové slovo virtual ale implementuje tam vlastní VFT pomocí ukazatelů na funkce.
Vlastně si nedokážu představit, jakým způsobem by se ty podtypy daly zaručit. Na to aby to nebyl podtyp stačí jakýkoliv bug v implementaci vlastností.

v

Re:Omezená dědičnost (je něco lepšího než OOP?)
« Odpověď #52 kdy: 31. 08. 2015, 20:42:13 »
opět se potřebuju vyznat k obdivu k Haskellu
definoval bych si datové typy, které přesně popisuje danou entitu (data Čtverec = Čtverec Integer etc.) a pak bych si definoval operace společné pro různé obrazce pomoc typových tříd (type class), jenom data a výpočty, žádné zbytečné abstrakce

v

Re:Omezená dědičnost (je něco lepšího než OOP?)
« Odpověď #53 kdy: 31. 08. 2015, 20:43:55 »
nebo sum type :)

Radek Miček

Re:Omezená dědičnost (je něco lepšího než OOP?)
« Odpověď #54 kdy: 31. 08. 2015, 22:50:16 »
To bohužel nestačí. Můžete vzít například třídu T s jednou virtuální metodou, jejíž chování je definováno tak, že vždy vrátí konstantu 1. Když pak vezmete podtřídu S, kde tato metoda vrací konstantu 2, tak to už není podtyp. O objektech typu T snadno dokážete, že daná metoda vrátí 1. Tuto vlastno nejde dokázat pro objekty typu S, tj. podle LSP S není podtyp T (podle definice LSP z A Behavioral Notion of Subtyping, hned v úvodu na druhé stránce - Subtype Requirement).
Pravda, tohle není podtyp. Tomuhle ale nezabrání ani ty BPravidla. Ve Shapes-no-oop.cc implementuje vlastní virtuální funkce, které ty pravidla neporušují a přitom s nima jde tohle udělat taky.

AFAIK tohle, co jsem napsal, se s BPravidly udělat nedá. Důvod je, že původně virtuální metoda z T, ale s BPravidly funkce mimo T nevrací vždy 1 - její činnost je totiž definována funkcí, která se předá konstruktoru T (podobně jako u odkazovaného Shape, kde je činnost metod definována funkcemi z konstruktoru). Jinak řečeno pro každou instanci S můžete vyrobit instanci T, která se chová stejně (funkce co konstruktor S předával konstruktoru T přímo předáte konstruktoru T, čímž získáte požadovanou instanci T).

JSH

Re:Omezená dědičnost (je něco lepšího než OOP?)
« Odpověď #55 kdy: 01. 09. 2015, 00:21:18 »
AFAIK tohle, co jsem napsal, se s BPravidly udělat nedá. Důvod je, že původně virtuální metoda z T, ale s BPravidly funkce mimo T nevrací vždy 1 - její činnost je totiž definována funkcí, která se předá konstruktoru T (podobně jako u odkazovaného Shape, kde je činnost metod definována funkcemi z konstruktoru). Jinak řečeno pro každou instanci S můžete vyrobit instanci T, která se chová stejně (funkce co konstruktor S předával konstruktoru T přímo předáte konstruktoru T, čímž získáte požadovanou instanci T).
Bojím se že nechápu. Vidím tam jen dva rozdíly. Jestli je VFT uložená přímo v objektu nebo nepřímo přes ukazatel. A druhý je jestli ty ukazatele inicializuju v konstruktoru já nebo překladač. Jestli tu funkci zadrátuju do konstruktoru já nebo překladač vidím jenom kosmetický rozdíl. V tom, co si do těch funkcí nacpu, mě ani v jednom případě překladač nijak neomezuje.
V tom odkazovaném případě je ekvivalent jedné abstraktní třídy a jedné vrstvy odvozených. Nenapadá mě ale jak by mi BP zabránily udělat několik vrstev odvozených typů a např. nějak divoce ifovat v konstruktorech. V obou případech dokážu ty virtuální funkce inicializovat podle libosti a ani jedno pravidlo mi v tom nebrání. I ty tovární metody "make" můžou dělat skoro cokoliv.

Radek Miček

Re:Omezená dědičnost (je něco lepšího než OOP?)
« Odpověď #56 kdy: 01. 09. 2015, 08:41:16 »
AFAIK tohle, co jsem napsal, se s BPravidly udělat nedá. Důvod je, že původně virtuální metoda z T, ale s BPravidly funkce mimo T nevrací vždy 1 - její činnost je totiž definována funkcí, která se předá konstruktoru T (podobně jako u odkazovaného Shape, kde je činnost metod definována funkcemi z konstruktoru). Jinak řečeno pro každou instanci S můžete vyrobit instanci T, která se chová stejně (funkce co konstruktor S předával konstruktoru T přímo předáte konstruktoru T, čímž získáte požadovanou instanci T).
Bojím se že nechápu. Vidím tam jen dva rozdíly. Jestli je VFT uložená přímo v objektu nebo nepřímo přes ukazatel. A druhý je jestli ty ukazatele inicializuju v konstruktoru já nebo překladač. Jestli tu funkci zadrátuju do konstruktoru já nebo překladač vidím jenom kosmetický rozdíl. V tom, co si do těch funkcí nacpu, mě ani v jednom případě překladač nijak neomezuje.
V tom odkazovaném případě je ekvivalent jedné abstraktní třídy a jedné vrstvy odvozených. Nenapadá mě ale jak by mi BP zabránily udělat několik vrstev odvozených typů a např. nějak divoce ifovat v konstruktorech. V obou případech dokážu ty virtuální funkce inicializovat podle libosti a ani jedno pravidlo mi v tom nebrání. I ty tovární metody "make" můžou dělat skoro cokoliv.

Bez BPravidel můžete v nadtřídě vytvořit virtuální metodu M s vlastností P. V podtřídě pak můžete tuto metodu M překrýt a to tak, že vlastnost P přestane platit. Pokud bylo možné vlastnost P odvodit ze specifikace nadtřídy, tak podtřída nemůže být podtyp, neboť P tam neplatí. Tj. máme podtřídu, která podle LSP není podtyp.

Naopak s BPravidly je činnost M (nyní funkce mimo třídu) definována funkcí, která se předá konstruktoru nadtřídy. Díky tomu podtřída nikdy nemůže dosáhnout chování M, které by už neuměla nadtřída - M v podtřídě je tedy speciální případ M v nadtřídě.

U toho mého původního příkladu M v nadtřídě vracela vždy 1 a M v podtřídě vždy 2. S BPravidly však podtřída může M ovlivnit pouze pomocí konstruktoru nadtřídy - M v nadtřídě umí nasimulovat každé chování M v podtřídě (stačí do konstruktoru nadtřídy předat totéž, co tam předala podtřída). V našem případě podtřída předala funkci vracející 2 - tuto funkci můžeme předat přímo při konstrukci nadtřídy, čímž se ukáže, že není pravda, že M v nadtřídě vrací vždy 1 (tj. ukáže se, že vlastnost P neplatí ani v nadtřídě).

Autor BPravidel to ukazuje na příkladu s FSet <: FBag:

A set is fully a bag. Because FSet constructors eventually call FBag constructors and do no alter the latter's result, every post-condition of a FSet constructor implies a post-condition of a FBag constructor. Since FBag and FSet values are immutable, the post-conditions that hold at their birth remain true through their lifespan. Because all FSet values are created by an FBag constructor, all FBag operations automatically apply to an FSet value. This concludes the proof that an FSet is a subtype of a FBag.

JSH

Re:Omezená dědičnost (je něco lepšího než OOP?)
« Odpověď #57 kdy: 01. 09. 2015, 10:16:51 »
Tak té nadtřídě při konstrukci podtřídy šoupnu ukazatel na funkci, která nesplňuje požadavky.

Zkusím ilustrovat hodně syntetickým příkladem (podle Shapes-no-oop) :
Kód: [Vybrat]
class Nad
{
  int (*const get)();
protected :
  Nad(int (*const g)()) : get(g) {}
  friend int get_odd(Nad const &);
};

// vzdycky vraci liche cislo
int get_odd(Nad const & n)
{
  return n.get();
}

class Huj: public Nad
{
  static int get_ok()
  {
     return 1;
  }
protected :
  Huj() : Nad(get_ok) {}
public :
  static const Huj* make()
  {
    return new Huj();
  }
};

class Fuj: public Nad
{
  static int get_ko()
  {
     return 2;
  }
protected :
  Fuj() : Nad(get_ko) {}
public :
  static const Fuj* make()
  {
    return new Fuj();
  }
};
Je tam porušené nějaké BPravidlo?

Kam tím směřuju? Ta lichá čísla jsou součástí specifikace nadtřídy a ona mi nedokáže zabránit tu specifikaci porušit. Pokud píšu cokoliv užitečného, tak budu mít část té specifikace někde v komentáři nad rámec jazyka. Tady by se to dalo ošetřit, nebo aspoň zkontrolovat assertem, ale tohle není možné udělat vždycky.

Jo, čistě teoreticky ta nadtřída umí vždycky to, co podtřída. Akorát to znamená jediné. Nejkonkrétnější specifikace o které si u jakékoliv nadtřídy můžu nechat zdát je něco jako nedefinované chování. To můžu udělat i pomocí
Kód: [Vybrat]
#define mysterious virtual
a nemusím si VFT psát ručně. U těch BPravidel prostě vidím přínos jen v tom třetím. První dvě mě jen nutí napsat si naprosto ekvivalentní konstrukce bez podpory jazyka.

Co mi přinese totální zákaz public metod v porovnání se situací kdy mám díky třetímu pravidlu povolené jen const metody?

Jann

Re:Omezená dědičnost (je něco lepšího než OOP?)
« Odpověď #58 kdy: 01. 09. 2015, 10:20:27 »
Všichni, kdož jste na takto položený dotaz odpověděli jinak, než že na něj nelze dát uspokojivou odpověď, byste se měli vys..t na programování a raději se věnovat něčemu jinému.
Jinak bude vypadat objektový návrh v C++, jinak v Javě, jinak v Pythonu, jinak ve Smalltalku, jinak pro účely grafické, jinak pro účely matematické, jinak pro účely čmárání po obrazovce myší, jinak pro účely vykreslení něčeho plottrem, jinak pro účely algebraické, jinak pro účely geometrické a docela jinak, pokud by se mělo všechno výše uvedené nějak skloubit dohromady. A pak přijde někdo s požadavkem doplnění sférické geometrie a další bude chtít diferenciální a uvidíte, jak jste v ...

Každý návrh se odvíjí od účelu. Nesmí být ani zbytečně abstraktní, ani zbytečně omezující. Odhadnutí správné míry je právě to, co dělá z relativně mechanické, kuchařkovité práce umění. Které ovšem 99% lidí v IT neovládá.

Radek Miček

Re:Omezená dědičnost (je něco lepšího než OOP?)
« Odpověď #59 kdy: 01. 09. 2015, 11:04:05 »
Kam tím směřuju? Ta lichá čísla jsou součástí specifikace nadtřídy a ona mi nedokáže zabránit tu specifikaci porušit.

Rozumím. Já jsem předpokládal, že ta specifikace nadtřídy je korektní.

Je možná tedy lepší používat tu první definici LSP (tu používá i autor BPravidel):

If for each object o1 of type S there is another object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2, then S is a subtype of T.

Já ji nicméně nemám tak rád, neboť tato definice pracuje se všemi chováními - nejen těmi ve specifikaci - což je někdy zbytečně omezující. Nicméně se nemusí řešit správnost specifikace a ani to, co znamená něco dokázat ze specifikace (v tom článku je to definováno - začátek strany 14 - We view each type specification as a theory presentation, i.e., a set of symbols, rules for forming well-formed formula..., ale pochybuji, že se tím v praxi bude někdo řídit).