Kwadrat dziedziczy po prostokącie - pytanie

0

Powiedzcie,czy dobrze to rozumuje i czy kod jest poprawny

Skoro każdy kwadrat jest prostokątem,ale nie każdy prostokąt kwadratem to kwadrat jest szczególnym przypadkiem prostokąta. W związku z tym klasa kwadrat dziedziczy od klasy prostokąt.

Oto kod.Zmienne odpowiadające za pozycje oraz bok A są chronione,ponieważ cecha dotycząca pozycji jest wspólna tak samo bok jest cechą wspólną - w przypadku prostokąta boki można opisać dwoma zmiennymi (4 boki,ale 2 pary równych) a w przypadku kwadratu jedną zmienną (4 boki tej samej dlugosci),dlatego w klasie kwadratu nie trzeba definiować zmiennej odpowiedzialnej za bok.

#include <iostream>

class Rectangle
{
    float sideB;
protected:
    int posX,posY,sideA;
public:
    Rectangle(): posX(10),posY(10){}
    inline void setSides(int a,int b){sideA=a;sideB=b;}
    inline float getSquare()const{return sideA*sideB;}
    inline int getPosX()const{return posX;}
    inline int getPosY()const{return posY;}
};

class Square: public Rectangle
{
public:
    Square():Rectangle(){posX=20;posY=20;}
    inline void setSide(int a){sideA=a;}
    inline float getSquare()const{return sideA*sideA;}
    inline int getPosX()const{return posX;}
    inline int getPosY()const{return posY;}
};

int main(int argc, char  *argv[])
{
    Rectangle simpleRectangle;
    Square simpleSquare;

    float a,b;
    std::cout<<"Podaj bok a prostokata: ";
    std::cin >> a;
    std::cout<<"Podaj bok b prostokata: ";
    std::cin >> b;
    simpleRectangle.setSides(a,b);

    std::cout<<"Podaj bok kwadratu: ";
    std::cin >> a;
    simpleSquare.setSide(a);

    std::cout<<"Pole prostokata na pozycji ("<<simpleRectangle.getPosX()<<","<<simpleRectangle.getPosY()<<") wynosi: "<<simpleRectangle.getSquare()<<'\n';
    std::cout<<"Pole kwadratu na pozycji ("<<simpleSquare.getPosX()<<","<<simpleSquare.getPosY()<<") wynosi: "<<simpleSquare.getSquare()<<std::endl;

    return 0;
}

Zmartwiło mnie to,że klasa kwadratu nie ma zadnych zmiennych dlatego pytanie kieruje do Was czy ten kod jest poprawny.

7

Możesz zrobić nielegalny kwadrat poprzez:

simpleSquare.setSides(a, a + 1);

... zatem nie jest to prawidłowy design.

Nie jesteś pierwszą, która wpadła na takie wykorzystanie dziedziczenia - ten problem ma nawet swoją nazwę: circle-ellipse problem :-)

Rozwiązaniem jest niewykorzystywanie na siłę dziedziczenia tam, gdzie ono nie pasuje - zwróć szczególną uwagę na sekcję Challenge the premise of the problem w zalinkowanym przeze mnie artykule.

0

Hm to jak rozumować dziedziczenie ?
Na takiej zasadzie: Zwierze -> Kot, Ksztalt->Prostokat, Liczby rzeczywiste -> Liczby naturalne

0

Dziękuje za wyjaśnienie!

5

to jest naruszenie Liskov substitution principle
Polecam obejrzeć, bo tu jest dokładnie wytłumaczone co jest nie tak, z Kwadratem dziedziczącym po Prostokącie:

9
  1. W ogóle należy dwa razy sie zastanowic zanim zrobi się jakiekolwiek dziedziczenie ;) W większośći przypadków kompozycja jest lepszym rozwiązaniem
  2. Bardziej ogólnie: należy patrzeć na relacje między obiektami na podstawie przejawianego ZACHOWANIA (z tym tez wiąże się wspomniana wyżej zasada LSP). Niby każdy kwadrat jest prostokątem, ale czy kwadrat przedstawia takie samo zachowanie jak prostokąt? Jeśli masz metodę która pozwala ustawić długość dwóch boków, to siłą rzeczy takie zachowanie nie jest wspólne dla kwadrata i prostokąta. Wiec czy kwadrat może dziedziczyć po prostokącie? Tak, ale tylko jeśli interfejs prostokąta zawiera jedynie metody, które są poprawnym zachowaniem także dla kwadratów.
2

Dziedziczenie przydaje się w sytuacji, kiedy mamy do czynienia z funkcją ( tutaj calculateArea ) posiadającą różną implementację dla poszczególnych obiektów ( Rectangle , Circle ), które można związać ze sobą za pomocą dziedziczenia interfejsu Shape.

#include <iostream>
#include <memory>
#include <vector>

using namespace std;

struct Point
{
    double x {0};
    double y {0};
};

class Shape
{
public:
    virtual double calculateArea() const = 0;
    virtual ~Shape(){};
};

class Rectangle : public Shape
{
public:
    Rectangle( Point position_ , Point sides_ ): position {position_} , sides {sides_} {}
    double calculateArea() const override { return sides.x*sides.y; };

private:
    Point position;
    Point sides;
};

class Circle : public Shape
{
public:
    Circle( Point position_ , double radius_ ): position {position_} , radius {radius_} {}
    double calculateArea() const override { return 3.14159*radius*radius; };

private:
    Point position;
    double radius;
};

int main()
{
    vector<unique_ptr<Shape>> shapes;

    shapes.emplace_back(make_unique<Rectangle>(Point{0,0},Point{10,20}));
    shapes.emplace_back(make_unique<Circle>(Point{0,0},5));

    for( const auto& shape : shapes )
    {
        cout << "Area of shape = " << shape->calculateArea() << "\n";
    }

    return 0;
}

0

Jak dla mnie kwadrat jest prostokątem i dziedziczenie jest tutaj ok. Trzeba je tylko poprawnie zaimplementować:

class Rectangle
{
public:
    Rectangle(): posX(10),posY(10){}
    virtual ~Rectangle(){}
    virtual void setSides(int a,int b){sideA=a;sideB=b;}
    inline float getSquare()const{return sideA*sideB;}
    inline int getPosX()const{return posX;}
    inline int getPosY()const{return posY;}
protected:
    int posX,posY,sideA,sideB;
};

class Square: public Rectangle
{
public:
    Square(){posX=20;posY=20;}
    inline void setSide(int a){sideA=sideB=a;}
protected:
    void setSides(int a,int b) override {sideA=sideB=std::min(a,b);}
};

Analogiczne rozwiązanie dla "Challenge the premise of the problem" z Wiki:

class Person
{
    virtual void walkNorth(int meters) {...}
    virtual void walkEast(int meters) {...}
};

class Prisoner: Person
{
    void walkNorth(int meters) override {...}
    void walkEast(int meters) override {...}
};

3
Pebal napisał(a):

Jak dla mnie kwadrat jest prostokątem i dziedziczenie jest tutaj ok. Trzeba je tylko poprawnie zaimplementować:

A dlaczego uważasz, że "poprawne" jest "min", a nie "max"?

10

@Pebal:
Problemem nie jest wybór pomiędzy min i max. Problemem jest to, że Twój Kwadrat w modyfikuje "kontrakt" zdefiniowany przez Prostokąt.

Co się stanie w takiej sytuacji ?

void test_area(Rectangle & rect)
{
        // Interfejs klasy prostokąt mówi, że możemy dla każdego prostokąta ustawić jego wymiary
        rect.setSides(10, 20);
    
        // hmm, troszkę nieszczęśliwa nazwa dla funkcji zwracającej pole, ale NVM
        // wiemy, że dla prostokąta którego wymiary sobie skonfigurowaliśmy chwilkę wcześniej pole powinno być równe 200
        assert (rect.getSquare() == 200);
}

int main()
{
        Square s;
        // Skoro każdy kwadrat jest prostokątem, to możemy kwadrat przekazać do funkcji test_area()
        test_area(s);
}
6

@Pebal: Za długie mi wyszło na komentarz

Jakiego rysowania? W kodzie który pokazałeś nie było nic o rysowaniu, było za to liczenie pola. Skąd mogę wiedzieć, czy Twoja funkcja rysująca będzie wymagać zmodyfikowania?
Jeśli nie dotyka tych elementów klasy dla których zmieniłeś kontrakt - to pewnie nie wymaga.
Ale ja tego nie wiem. - i to na tym polega głównie problem. Definiując klasę kwadrat w taki sposób jak to zrobiłeś świadomie złamałeś kontrakt klasy prostokąt.

Robisz to w swoim kodzie który ma kilkaset linijek - ok, to nie (MÓJ) problem. Ale być może Rectangle to klasa którą dostałeś z jakiejś zewnętrznej biblioteki, być może jest używana w projekcie który ma już kilkaset tysięcy linii kodu ? Co wtedy ? Czy mogę wszystkich tych funkcji bezpiecznie używać z Twoim Kwadratem ?

Reguła susbstytucji Liskov mówi (w uproszczeniu), że cały ten kod powinien działać poprawnie jeśli dasz mu obiekt typu pochodnego. W przypadku Twojego kwadratu takiej gwarancji nie ma.

@xxx_xx_x:
To w zasadzie dokładnie taka sama sytuacja.

Jeśli z jakichś powodów dla klasy Person zdefiniowano metodę goToPub() i w kontrakcie ustalono że ta metoda zawsze działa poprawnie, to definiując klasę Prisoner jako klasę pochodną od Person i redefiniując jej tą metodę w poniższy sposób też zmieniasz kontrakt ;)

void Prisoner::goToPub() override
{
  throw PermissionDeniedException();
}
1

Te dylematy zniknęłyby, gdyby założyć paradygmat programowania funkcyjnego - czyli, że nie zmieniamy stanu prostokąta po utworzeniu obiektu.

inline void setSides(int a,int b){sideA=a;sideB=b;}

Wtedy to by nie miało sensu, bo jakie setSides? Czy naprawdę potrzebujemy zmieniać stan utworzonego obiektu? (i oto trzeba się zapytać siebie tworząc daną klasę. Może się okazać, że to całe setSides jest kompletnie niepotrzebne, bo raz utworzony obiekt nie będzie nigdy zmieniany).

Ale nawet zakładając zmianę stanu, można by to inaczej rozwiązać.

Skoro każdy kwadrat jest prostokątem,ale nie każdy prostokąt kwadratem to kwadrat jest szczególnym przypadkiem prostokąta. W związku z tym klasa kwadrat dziedziczy od klasy prostokąt.

Powiedziałbym, że kwadrat nie jest szczególnym przypadkiem prostokąta, tylko raczej normalnym prostokątem z nałożonymi ograniczeniami co do tego, jakie wartości mogą przyjmować długości jego boków.

Dlaczego dziedziczenie jest słabym pomysłem - kwadrat jest jednym z wielu prostokątów, więc można użyć klasy Rectangle i nałożyć ograniczenia/constrainty/walidację, która się odpali po każdej zmianie boku i przeliczy tak wartości boków, żeby zaspokoić narzucone ograniczenia.

Być może będziemy chcieli kiedyś zrobić inne ograniczenia - np. chcielibyśmy mieć prostokąt, gdzie zawsze szerokość jest 2 razy większa niż długość, albo chcielibyśmy ustawić maksymalną długość i szerokość boków, więc przydałaby się jakaś specjalna metoda validate (albo callback dynamicznie wrzucany w konstruktorze).

Z drugiej strony robienie klas reprezentujących figury geometryczne ma sens tylko jak robisz coś, do czego będziesz to używał. Np. edytor graficzny. Bo tak na sucho, to trochę mija się z celem.

2

@Pebal przykład:
Masz taki kod :

interface GLX {
    fun drawRectangle(x : Float, y : Float, w : Float, h : Float)
}
open class Rectangle(var x : Float, var y : Float, var w : Float, var h : Float) {
    fun scale(s : Float) {
        w *= s;
        h *= s;
    }
}

class Square(x : Float, y : Float, a : Float) : Rectangle(x, y, a, a)

fun draw(glx: GLX, rect : List<Rectangle>) {
    rect.forEach { rect ->
        glx.drawRectangle(rect.x, rect.y, rect.w, rect.h)
    }
}

fun process(list : List<Rectangle>) : List<Rectangle> {
    list.forEach { it.scale(2.0f) }
    return list
}

fun main(vararg args : String) {
    val glx : GLX = object : GLX {
        override fun drawRectangle(x: Float, y: Float, w: Float, h: Float) {
            println("DRAW : $x, $y -> $w, $h" )
        }
    }

    val obj : List<Rectangle> = listOf(Square(2f, 5f, 10f))

    draw(glx, process(obj))

}

Teraz przychodzi feature że chcemy do metody process dodać zmianę w, h o wektor v

Wiec modyfikujemy klase rect:

open class Rectangle(var x : Float, var y : Float, var w : Float, var h : Float) {
    fun scale(s : Float) {
        w *= s;
        h *= s;
    }
    
    fun expand(x : Float, y : Float) {
        w += x;
        h += y;
    }
}

Modyfikujemy process:

fun process(list : List<Rectangle>) : List<Rectangle> {
    list.forEach {
        it.scale(2.0f)
        it.expand(3f, 5f)
    }
    return list
}

I oczywiście renderujemy kwadrat:
DRAW: 2.0, 5.0 -> 23.0, 25.0

Oh wait, od kiedy kwadrat ma wymiary 23x25. Powiesz że trzeba było przeciażyć metodę i znowu nałożyć obostrzenia?

  1. Co mnie do tego zmusi? kod się kompiluje, klas dziedziczących może być kilkadziesią mogę jakiejś nie wykryć.
  2. Może klasa Rectangle pochodzi z jakiejś biblioteki, a ja stworzyłem na jej podstawie klasę child(square) i podniesienie wersji biblioteki spowodowało posypanie się kontraktów?
  3. Gdzie mam zysk że musze każdą metodę modyfikować na potrzeby kwadratu??
1

skoro to jest c++ to kwadrat dziedziczy jednocześnie po prostokącie i rombie , a obie te klasy mają jako wirtualnego przodka wielokąt albo figura_geometryczna
A na poważnie wszelkie dziedziczenia i funkcjonalności zależą od tego do czego w projekcie ma byc użyty ten kwadrat. Jak do nauki geometrii to dziedziczenie wielokącie mogłoby mieć sens

0
Bartłomiej Golenko napisał(a):

@Pebal:
Problemem nie jest wybór pomiędzy min i max. Problemem jest to, że Twój Kwadrat w modyfikuje "kontrakt" zdefiniowany przez Prostokąt.

Co się stanie w takiej sytuacji ?

void test_area(Rectangle & rect)
{
        // Interfejs klasy prostokąt mówi, że możemy dla każdego prostokąta ustawić jego wymiary
        rect.setSides(10, 20);
    
        // hmm, troszkę nieszczęśliwa nazwa dla funkcji zwracającej pole, ale NVM
        // wiemy, że dla prostokąta którego wymiary sobie skonfigurowaliśmy chwilkę wcześniej pole powinno być równe 200
        assert (rect.getSquare() == 200);
}

Zdajesz sobie sprawę z tego, że w bibliotekach GUI, modyfikacja wysokości i szerokości okna może nie przynieść pożądanego skutku? Czy w takim przypadku należałoby stworzyć osobną klasę okna, która zakłada jego stały rozmiar, choć nieznany w trakcie tworzenia tylko po to, aby Twoje testy mogły się wykonać poprawnie?

0

Poniżej przykład prostej implementacji klasy Rectangle, umożliwiającej użycie walidatorów do kontrolowania stanu obiektu. Dzięki walidatorom możliwe jest utworzenie obiektu klasy Rectangle, który zachowuje się jak kwadrat. Implementacja ta nie łamie zasad Liskov, a jednak kod działa analogicznie jak ten, w którym klasa kwadratu dziedziczy po klasie prostokąta. Różnica jest taka, że w przypadku dziedziczenia, walidator był aplikowany niejawnie przez klasę dziedziczącą a w przypadku tej implementacji, walidator przekazywany jest jawnie w konstruktorze. Implementacja ta, podobnie jak wariant z dziedziczeniem, nie będzie przechodzić niektórych testów, gdy będą używane walidatory modyfikujące. Moim zdaniem Liskov narzuca zbyt daleko idące ograniczenia, gdyż zawęża możliwą do uzyskania funkcjonalność kodu. Nie jestem zwolennikiem kurczowego trzymania się takich pomysłów.

template<class T>
class Validators
{
public:
    template<class... V>
    Validators(V&&... v) {
        registerValidators(std::forward<V>(v)...);
    }

    template<class V, class... A>
    void registerValidators(V&& v, A&&... a) {
        registerValidator(std::forward<V>(v));
        registerValidators(std::forward<A>(a)...);
    }

    template<class F>
    bool verify(F f) const {
        return std::accumulate(_v.cbegin(), _v.cend(), true, [&](bool res, auto& v) { return res & f(v); });
    }

private:
    std::vector<std::shared_ptr<T>> _v;

    void registerValidators() const {}

    template<class V, typename = std::enable_if_t<!std::is_convertible_v<V, std::shared_ptr<T>>>>
    void registerValidator(V&& v) {
        _v.emplace_back(std::make_shared<V>(v));
    }

    void registerValidator(std::shared_ptr<T> v) {
        _v.emplace_back(std::move(v));
    }
};

class Rectangle
{
public:
    struct Validator
    {
        virtual ~Validator() = default;
        virtual bool verifyPos(int&, int&) const { return true; }
        virtual bool verifySize(int&, int&) const { return true; }
    };

    template<class... V, typename = std::enable_if_t<((std::is_base_of_v<Validator, V>||std::is_convertible_v<V, std::shared_ptr<Validator>>)&&...)>>
    Rectangle(V&&... v): Rectangle(0, 0, 0, 0, std::forward<V>(v)...) {
    }

    template<class... V, typename = std::enable_if_t<((std::is_base_of_v<Validator, V>||std::is_convertible_v<V, std::shared_ptr<Validator>>)&&...)>>
    Rectangle(int w, int h, V&&... v): Rectangle(0, 0, w, h, std::forward<V>(v)...) {
    }

    template<class... V, typename = std::enable_if_t<((std::is_base_of_v<Validator, V>||std::is_convertible_v<V, std::shared_ptr<Validator>>)&&...)>>
    Rectangle(int x, int y, int w, int h, V&&... v): _v(std::forward<V>(v)...) {
        setPos(x, y);
        setSize(w, h);
    }

    int x() const { return _x; }
    int y() const { return _y; }
    int w() const { return _w; }
    int h() const { return _h; }

    void setPos(int x, int y) {
        _v.verify([&](auto& v){ return v->verifyPos(x, y); });
        _x = x; _y = y;
    }

    void setSize(int w, int h) {
        _v.verify([&](auto& v){ return v->verifySize(w, h); });
        _w = w; _h = h;
    }

    int area() const {
        return w() * h();
    }
private:
    int _x, _y, _w, _h;
    Validators<Validator> _v;
};

struct SquareValidator: Rectangle::Validator
{
    bool verifySize(int& w, int& h) const override {
        return w != h ? [&]{ w = h = std::min(w, h); return false; }() : true;
    }
};

int main()
{
    auto rect = Rectangle();
    auto square = Rectangle(SquareValidator());

    rect.setSize(10, 20);
    std::cout << "Rect area: " << rect.area() << std::endl;

    square.setSize(10, 20);
    std::cout << "Square area: " << square.area() << std::endl;

    return 0;
}
0

A teraz tworzymy klasę Square:

class Square: public Rectangle
{
public:
    struct Validator: Rectangle::Validator
    {
        bool verifySize(int& w, int& h) const override {
            return w != h ? [&]{ w = h = std::min(w, h); return false; }() : true;
        }
    };

    template<class... V, typename = std::enable_if_t<((std::is_base_of_v<Validator, V>||std::is_convertible_v<V, std::shared_ptr<Validator>>)&&...)>>
    Square(V&&... v): Square(0, 0, 0, std::forward<V>(v)...) {
    }

    template<class... V, typename = std::enable_if_t<((std::is_base_of_v<Validator, V>||std::is_convertible_v<V, std::shared_ptr<Validator>>)&&...)>>
    Square(int s, V&&... v): Square(0, 0, s, std::forward<V>(v)...) {
    }

    template<class... V, typename = std::enable_if_t<((std::is_base_of_v<Validator, V>||std::is_convertible_v<V, std::shared_ptr<Validator>>)&&...)>>
    Square(int x, int y, int s, V&&... v): Rectangle(x, y, s, s, std::forward<V>(v)..., _v) {
    }

    int s() const { return w(); }

    void setSize(int s) {
        Rectangle::setSize(s, s);
    }

private:
    inline static const auto _v = std::make_shared<Validator>();
};

i teoria o naruszeniu zasad Liskov w przypadku dziedziczenia klasy prostokąta przez klasę kwadratu lega w gruzach. To nie naruszanie zasad Liskov a zignorowanie założeń klasy bazowej.

3

Ten kod

    square.setSize(10, 20);
    std::cout << "Square area: " << square.area() << std::endl;

wypisuje 100, podczas gdy prawidłowym wynikiem jest, że nie istnieje kwadrat o bokach 10, 20.

3

Klasa kwadrat nie powinna mieć API pozwalającego na przypisanie dwóch różnych boków. Kwadrat ma jedną długość boku.

1

Nikt Cię do niczego nie zmusi. Jeżeli chcesz aby program działał źle, to będzie działał źle. Jednak spokojnie można to zaimplementować tak, aby działało poprawnie.

Tak z ciekawości się wtrącę
@Pebal -> Podaj POPRAWNĄ implementację. A nie ogólniki.

0
zkubinski napisał(a):

Tak z ciekawości się wtrącę
@Pebal -> Podaj POPRAWNĄ implementację. A nie ogólniki.

Podałem dwie implementacje, których rezultat działania jest identyczny w tym jedną, która na pewno nie łamie zasady Liskov, gdyż nie ma w niej dziedziczenia klasy prostokąta. Zasada Liskov jest przez wielu zwyczajnie nadinterpretowana. W myśl tego, co tu niektórzy sądzą, poniższa implementacja łamie zasadę Liskov:

class TextView
{
    virtual void setText(const std::string& text) {
        ... = text;
    }

    std::string text() const{ return ... }
};

class PasswordTextView: TextView
{
    void setText(const std::string& text) override {
        Input::setText(std::string(text.size(), '*'));
    }
};

Gdyby byłoby to prawdą, to albo zasada Liskov, albo polimorfizm byłyby zupełnie pozbawione sensu.

4

Gdyby byłoby to prawdą, to albo zasada Liskov, albo polimorfizm byłyby zupełnie pozbawione sensu

Istnieje też trzeci wariant: Twoja implementacja PasswordTextView zwyczajnie nie ma sensu, bo mieszasz warstwę logiki z prezentacją.

0
Patryk27 napisał(a):

Istnieje też trzeci wariant: Twoja implementacja PasswordTextView zwyczajnie nie ma sensu, bo mieszasz warstwę logiki z prezentacją.

Widzę, że kolega zafascynowany MVVM, z którym nota bene są jeszcze większe jaja jak z interpretacją zasady Liskov, i zapomniał o innych wzorcach. Jeżeli uważasz, że ta konstrukcja jest niepoprawna, to pokaż właściwą.

1

Widzę, że kolega zafascynowany MVVM

Ani słowem o tym nie wspomniałem.

Jeżeli uważasz, że ta konstrukcja jest niepoprawna, to pokaż właściwą.

class TextView {
  std::string content;
  std::optional<char> mask;
}

:-)

0
class TextView {
  string content;
  std::optional<char> mask;
}

Nie potrzebuję maski w prostym polu tekstowym. Poproszę o przykład z użyciem dziedziczenia.

2

Prosz :-)

Na co dzień nie piszę w C++, stąd składniowo kod może leżeć, lecz ideowo chodzi o coś takiego:

abstract class TextView {
  std::string content;

  virtual std::string getContentForRendering();
}

class RegularTextView: TextView {
  std::string getContentForRendering() {
    return content;
  }
}

class MaskedTextView: TextView {
  char mask;

  std::string getContentForRendering() {
    return std::string(content.size(), mask);
  }
}

Innym podejściem (całkowicie separującym logikę od prezentacji) mogłoby być wykorzystanie osobnych klas w stylu TextController + RegularTextRenderer / MaskedTextRenderer, lecz nie chciałem wrzucać "napuchniętego" przykładu.

2

@Pebal. Pozwól, że wtrącę swoje trzy grosze rozmijając się z tematem

Widzę, że kolega zafascynowany MVVM, z którym nota bene są jeszcze większe jaja

Nie jestem zawodowym programistą C++ ale jestem na tyle ogarnięty w C++ żeby móc się wypowiedzieć w tym temacie.

Na swoim przykładzie powiem, że podział na "warstwę prezentacji" i na "warstwę biznesową" ma bardzo duży sens, gdyż ja pisząc swój program miałem ten problem, że musiałem zmienić dwie rzeczy "mechanikę" i "wygląd" - wcześniej miałem wszystko pomieszane ze wszystkim i wiesz co, szło po prostu rzygnąć, bo musiałem szukać tego co chcę zmienić, potem zmieniać w kilku miejscach i nie za bardzo mogłem się połapać w swoim własnym kodzie. Przepisanie wszystkiego na nowo trochę mi zajęło i gdy to rozdzieliłem na te dwie warstwy, to czas spędzony na przepisanie się opłacił, bo jak przyszło do następnej modyfikacji, to zrobiłem tą poprawkę w jakieś 30 min... Więc tutaj nie o żadne "fascynacje" chodzi ale o ułatwienie sobie roboty i oszczędności czasu, bo potem mogłem skupić się na innym problemie.

Nie chcę podważać twoich kompetencji ale mam wrażenie, że nie czujesz tematu i nie chcesz przyjąć do wiadomości dobrych praktyk.

Co do dziedziczenia "kwadrat"-> "prostokąt" - sądzę, że w tym przykładzie bardziej chodziło o przekazanie "idei" nowicjuszom aby zrozumieli czym jest programowanie obiektowe i jak "to rozumieć" aby mogli złapać sens i poukładać sobie w głowie "czym to się je". Bo w sumie idea tej techniki, to dziedziczenie i polimorfizm czynią OO coś fascynującego i ułatwiającego robotę bez powielania kodu, oszczędności pamięci itp itd...

A ty po prostu puściłeś wodze fantazji... bo matematycznie rzecz ujmując, twój kod z dziedziczenia z klasy prostokąt<-kwadrat nie ma najmniejszego sensu, gdyż to są dwie zupełnie różne figury które nie mają ze sobą zupełnie nic wspólnego - tylko jedna wspólna cecha je łączy - to "dwa równe boki" które czynią kwadrat "w szczególnym wypadku prostokątem". Mając tą wiedzę, to na sam koniec narzuca się pytanie - Co miałby dziedziczyć kwadrat po prostokącie ? Wymiary tych dwóch boków ? Nie uważasz, że to bezsens ? Także wrzuć na luz.

0
Patryk27 napisał(a):

Prosz :-)

Na co dzień nie piszę w C++, stąd składniowo kod może leżeć, lecz ideowo chodzi o coś takiego:

abstract class TextView {
...
}

Ok, ale takie podejście wymaga spełnienia warunku: Klasa implementująca inne zachowanie może dziedziczyć tylko po klasie abstrakcyjnej. Odpada więc możliwość rozbudowy istniejących klas i trzeba przewidzieć możliwe wariacje projektując klasę bazową. Słabo.

1

@Pebal Odpada więc możliwość rozbudowy istniejących klas

dlaczego ? Uważam, że nie. Bo wirtualne składowe służą do przesłaniania ich własnymi wersjami. Więc jak uznasz za stosowne, to w jakiejś klasie możesz napisać swoją wersję jakiejś metody i tyle. Przykładu nie podam, bo na chwilę obecną nic mi do głowy nie przychodzi.

0
zkubinski napisał(a):

Nie jestem zawodowym programistą C++ ale jestem na tyle ogarnięty w C++ żeby móc się wypowiedzieć w tym temacie.

Na swoim przykładzie powiem, że podział na "warstwę prezentacji" i na "warstwę biznesową" ma bardzo duży sens, gdyż ja pisząc swój program miałem ten problem, że musiałem zmienić dwie rzeczy "mechanikę" i "wygląd" - wcześniej miałem wszystko pomieszane ze wszystkim i wiesz co, szło po prostu rzygnąć, bo musiałem szukać tego co chcę zmienić, potem zmieniać w kilku miejscach i nie za bardzo mogłem się połapać w swoim własnym kodzie. Przepisanie wszystkiego na nowo trochę mi zajęło i gdy to rozdzieliłem na te dwie warstwy, to czas spędzony na przepisanie się opłacił, bo jak przyszło do następnej modyfikacji, to zrobiłem tą poprawkę w jakieś 30 min... Więc tutaj nie o żadne "fascynacje" chodzi ale o ułatwienie sobie roboty i oszczędności czasu, bo potem mogłem skupić się na innym problemie.

Nie chcę podważać twoich kompetencji ale mam wrażenie, że nie czujesz tematu i nie chcesz przyjąć do wiadomości dobrych praktyk.

Ależ ja nie neguję potrzeby rozdzielania modelu od widoku a wręcz przeciwnie. Twierdzę jedynie, że wzorzec MVVM jest zdrowo trzepnięty do tego stopnia, że mało kto go rozumie i trzeba było stworzyć dedykowane frameworki, aby można go było w sensowny sposób używać. Osobiście jestem zwolennikiem wzorca Model View (Observer).

Co do dziedziczenia "kwadrat"-> "prostokąt" - sądzę, że w tym przykładzie bardziej chodziło o przekazanie "idei" nowicjuszom aby zrozumieli czym jest programowanie obiektowe i jak "to rozumieć" aby mogli złapać sens i poukładać sobie w głowie "czym to się je". Bo w sumie idea tej techniki, to dziedziczenie i polimorfizm czynią OO coś fascynującego i ułatwiającego robotę bez powielania kodu, oszczędności pamięci itp itd...

Sory, ale to nie ja zapuściłem tekst, że tak dziedziczyć nie można, bo Liskov itp.

A ty po prostu puściłeś wodze fantazji... bo matematycznie rzecz ujmując, twój kod z dziedziczenia z klasy prostokąt<-kwadrat nie ma najmniejszego sensu, gdyż to są dwie zupełnie różne figury które nie mają ze sobą zupełnie nic wspólnego - tylko jedna wspólna cecha je łączy - to "dwa równe boki" które czynią kwadrat "w szczególnym wypadku prostokątem". Mając tą wiedzę, to na sam koniec narzuca się pytanie - Co miałby dziedziczyć kwadrat po prostokącie ? Wymiary tych dwóch boków ? Nie uważasz, że to bezsens ? Także wrzuć na luz.

Zdecyduj się. Albo można tak dziedziczyć, bo cele szkolenia, zobrazowanie możliwości języka, itp., albo nie można bo wyobraźnia ponosi.

1 użytkowników online, w tym zalogowanych: 0, gości: 1