Poprawne użycie polimorfizmu

0

Chciałbym zadać bardzo ogólne pytanie - czy w tym przykładowym kodzie poprawnie używam polimorfizmu ? Jeszcze tej techniki nie używałem w swoim kodzie ale przymierzam się do tego i chciałbym aby ktoś rzucił okiem czy kod jest poprawnie użyty - tutaj nie ma celu samego w sobie, chcę wiedzieć czy kod jest poprawny ?

#include <iostream>

using namespace std;

class Settings
{
public:
    virtual void settings()
    {
        std::cout << "brak ustawien" << std::endl;
    }
    virtual void saveSettings(Settings *s)
    {
        s->settings();
    };
};

class ServerSettings : public Settings
{
    virtual void settings() override
    {
        std::cout << "ustawienia serwera" << std::endl;
    }
    virtual void saveSettings(Settings *s) override
    {
        s->settings();
    }
};

class HostSettings : public Settings
{
    virtual void settings() override
    {
        std::cout << "ustawienia hosta" << std::endl;
    }
    virtual void saveSettings(Settings *s) override
    {
        s->settings();
    }
};

int main()
{
    Settings *s = nullptr;
    ServerSettings ss;
    HostSettings hs;

    s=&ss;

    s->saveSettings(s);

    s=&hs;

    s->saveSettings(s);

    return 0;
}

6

Technicznie wszystko wygląda poprawnie. Designowo: raczej nie wyobrażam sobie sytuacji kiedy taki kod miałby sens. W większości przypadków dziedziczenie robi się wtedy, gdy masz jakąś nową logikę (inną implementację metod). W tym wypadku jest metodę settings da się sparametryzować stringiem (jest jedna klasa Settings która trzyma tekst do wypisania). Metoda saveSettings jest kompletnie z d**y, bo robi to samo co s->settings() a dodatkowo this nie jest używany w żaden sposób.

0
slsy napisał(a):

Technicznie wszystko wygląda poprawnie. Designowo: raczej nie wyobrażam sobie sytuacji kiedy taki kod miałby sens. W większości przypadków dziedziczenie robi się wtedy, gdy masz jakąś nową logikę (inną implementację metod). W tym wypadku jest metodę settings da się sparametryzować stringiem (jest jedna klasa Settings która trzyma tekst do wypisania). Metoda saveSettings jest kompletnie z d**y, bo robi to samo co s->settings() a dodatkowo this nie jest używany w żaden sposób.

Czy możesz podać przykład Designowo poprawny ?

Bo ogólnie mam sytuację, że zrobiłem sobie QStackedWidget i każda Page która dołączana jest do QStackedWidgeta musi gdzieś zapisać swoje ustawienia. Planuję to zrobić tak, że każda strona, która trzyma swoje ustawienia załaduje je do zbiorczej klasy nazwijmy ją AllSettings i sądzę, że w tej sytuacji przydałby mi się polimorfizm, bo jak dołożę następną stronę z jakimiś ustawieniami, to nie będę grzebał w całym kodzie, tylko odpalę funkcję, która doda ustawienia z nowej strony do zbiorczej klasy, która trzyma ustawienia wszystkich stron.

3

Jeśli klasa ma jakąś metodę wirtualną to powinna też mieć wirtualny destruktor. W przeciwnym razie kasowanie przez wskaźnik na klasę bazową doprowadzi do niezdefiniowanego zachowania.

6

Absolutnie nie!
Ustawienia to ustawienia, czyli nazwa => wartość
I absolutnie nie istotnie czy to ustawienia dla Hosta czy Serwera (tak a propos nie widzę różnicy pomiędzy tymi nazwami).
Czy widziałeś kiedyś oprócz int typ intDlaWieku lub intDlaLicznika?
To po kiego mnożysz byty? https://pl.wikipedia.org/wiki/Brzytwa_Ockhama oraz https://pl.wikipedia.org/wiki/SOLID_(programowanie_obiektowe)
Powinna być klasa Settings oraz obiekty:
Settings SettingsForServer,SettingsForHost,SettingsForChat,SettingsForZkubinski;
Natomiast różnica może być gdzie te ustawiania mogą być przechowywane:

  • Plik json
  • Plik xml
  • Plik ini (windowsowy wynalazek ale dla ustawień pasuje jak ulał)
  • Plik bin (zwykły binarny)
    I jeszcze kilka innych no i tu można zastosować polimorfizm.
class SettingsFile
{
  protected:
  string path;
  public:
  SettingsFile(const string &path):path(path) {}
  virtual ~SettingsFile(); 
  virtual void read(Settings &s)=0; 
  virtual void write(const Settings &s)=0;
};

A dalej polimorfizm:

class SettingsFileJson: public class SettingsFile
{
  virtual void read(Settings &s)
  {
   ... 
  }
  virtual void write(const Settings &s)
  {
   ...
  }
};

Czyli https://pl.wikibooks.org/wiki/Kody_%C5%BAr%C3%B3d%C5%82owe/Most_(wzorzec_projektowy)

Czy rozumiesz że podany przez ciebie main() można zapisać prościej jak poniżej?

int main()
{
    ServerSettings ss;
    HostSettings hs;
    ss.settings(); // można też ss.saveSettings(&ss); ale po kiego jak jest krótszy zapis?
    hs.settings(); // można też hs.saveSettings(&hs); ale po kiego jak jest krótszy zapis?
    return 0;
}
4
slsy napisał(a):

Technicznie wszystko wygląda poprawnie.

Mam inne zdanie na temat. Syntaktycznie wygląda poprawnie. Ale TYLKO tyle.
Jak użycie rzeczownika i czasownika w języku obcym w prawidłowej kolejności, tylko że się to wcale nie dopina, niczego razem nie tworzy. Nie mówię o poziomie Szekspira, ale nawet rymowanki dla dzieci

Potem już nic się nie zgadza

class Settings
{
public:
    virtual void settings()

W klasie o nazwie rzeczownika (ok) jest metoda o nazwie rzeczownika (pomijając literkę tolower() takiego samego jak klasa - straszny dziwoląg), ktora ani nie przyjmuje argumentów, ani nie zwraca wyniku,
Wiec co robi ? Albo nic i nie ma sensu, albo zmienia obiektu stan z jakiegoś na jaki. Nie musi to być zawsze patologią, niekiedy profesjonalny programista tak zaprojektuje - ale tu jest w połączeniu z rzeczownikiem jako nazwą metody - patologią jest

zkubinski napisał(a):
 
 class Settings
 {
     virtual void saveSettings(Settings *s)>     {

Co tu mamy ... mamy metodę w obiekcie w instancji nr 1, która operuje innym obiektem tej klasy (instancją nr 2), przy czym zastrzega prawo zmiany zarówno siebie (nie ma const za nawiasem) jak i tego drugiego (argument nie ma const)
@zkubinski: może wytłumaczysz co względem siebie robią te dwie instancje obiektu ?
CZASEM, rzadko się pisze metody, które wchodza w kontakt z innym egzemplarzem tej samej klasy, ale rzadko. A skrajnie rzadko z prawe zmiany tego srugiego.

Już konkretnie tu, co w czym by miało dokonywać tego "save" ?
Pytanie retoryczne

zkubinski napisał(a):

Chciałbym zadać bardzo ogólne pytanie - czy w tym przykładowym kodzie poprawnie używam polimorfizmu ?

Z polimorfizmem masz tyle wspólnego, co jaskiniowiec, który znalazł na piasku stalowy wodoodporny zegarek. Może się przyda do rozbijania orzechów ?

Może pierwszy raz w życiu weźmiesz w rękę książkę, wystarczy na poziomie licealnym, i przeczytasz ze zrozumieniem?
Zostaw te settingsy ... poucz się Owoc -> Jabłko, Owoc -> Pomarańcza, Koszyk<Owoc>, Blender Szklanka i zaprojektuj metody.
Klasa rzeczownik, metoda czasownik.
Jak pokażesz coś przynajmniej dostatecznego, sam powiem żebyś robił te settingsy

3
zkubinski napisał(a):

Chciałbym zadać bardzo ogólne pytanie - czy w tym przykładowym kodzie poprawnie używam polimorfizmu ? Jeszcze tej techniki nie używałem w swoim kodzie ale przymierzam się do tego i chciałbym aby ktoś rzucił okiem czy kod jest poprawnie użyty - tutaj nie ma celu samego w sobie, chcę wiedzieć czy kod jest poprawny ?

Nie próbuj używać tego na siłę.

Dobre aplikacje nie wywodzą się z podejścia: "Hmm, gdzie by tu wcisnąć polimorfizm", "o, tutaj użyję".
Tylko raczej: "klik, klik, klik, hmm, tutaj przydałoby się wydzielić tą strukturę", "użyję polimorfizmu".

To pierwsze prowadzi do sztucznych i nikomu niepotrzebnych abstrakcji, a to drugie do sensownego podejścia. Będziesz w swoich projektach miał wiele miejsc które aż proszą się o polimorfizm, ale ten przykład który podałeś tutaj taki nie jest. Jak będziesz chciał specjalnie tam wepchnąć polimorfizm żeby tylko był to stworzysz jakiś przerost formy nad treścią.

0

Mam pomysł na użycie polimorfizmu

Chodzi o to, że w jednym z okien dialogowych używam obiektu typu QStackedWidget do tego obiektu, trzeba dodać inne obiekty tzw. strony. I teraz chciałbym tutaj zadać pytanie. Czy dla każdej nowej strony warto użyć polimorfizmu gdzie klasą nadrzędną byłaby np. klasa "Page", a klasy "Page1", "Page2" etc... dziedziczyłby metody wirtualne od klasy "Page" ? Np metodą wirtualną byłaby funkcja data, ponieważ każda z tych stron odbiera od usera różne dane, a na koniec te dane z tych wszystkich stron "Page" zebrałbym w całość przy pomocy jakiejś jednej metody i zrzucił do pliku.

Kodu nie mam, bo to co opisałem powyżej to koncepcja. Co powiecie na ten pomysł ?

0

Już w tym wątku dostałeś na to odpowiedź:

Czy widziałeś kiedyś oprócz int typ intDlaWieku lub intDlaLicznika?
To po kiego mnożysz byty? https://pl.wikipedia.org/wiki/Brzytwa_Ockhama oraz https://pl.wikipedia.org/wiki/SOLID_(programowanie_obiektowe)

W zasadzie pytasz, czy zamiast:

int a,b;
cin>>a>>b;
int c=a+b;
cout<<c<<endl;

Warto zrobić 4 klasy IntValue oraz dziedziczące po niej IntValueA, IntValueB, IntValueC?

0
zkubinski w komentarzu napisał(a):

to w jakich przypadkach byś go użył ? tj. polimorfizmu

Może praca studenta sprzed tygodnia, owszem C# ale znając C++ da się odczytać oraz pokazuje dwie ścieżki polimorfizmu:

    public abstract class Figure
    {
        protected int[] args;
        public Figure(string param,int length)
        {
            string[] data = param.Split(" \t\r\n\v".ToArray(), StringSplitOptions.RemoveEmptyEntries);
            if (length != data.Length) throw new ArgumentException(string.Format("Expected {0} parameters", length));
            args = new int[length];
            for (int i = 0; i < length; ++i) args[i] = int.Parse(data[i]);
        }
        public abstract void Draw(Graphics g);
    }
    
    public abstract class FigureFactory
    {
        private delegate Figure maker(string param);
        private static Dictionary<string, maker> map = new Dictionary<string, maker>()
        {
            {"Circle",(param)=>new Circle(param)},
            {"Rectangle",(param)=>new Rectangle(param)},
            {"Triangle",(param)=>new Triangle(param)},
        };
        public static readonly string[] keys = map.Keys.OrderBy((kind) => kind).ToArray();
        public static Figure make(string kind, string param) { return map[kind](param); }
    }
    
    public class Circle:Figure
    {
        public Circle(string param) : base(param,3) { }
        int cx { get { return args[0]; } }
        int cy { get { return args[1]; } }
        int r { get { return args[2]; } }
        public override void Draw(Graphics g)
        {
            g.DrawEllipse(Pens.Blue, cx - r, cy - r, r << 1, r << 1);
        }
        public override string ToString() { return string.Format("Circle({0},{1},{2})",cx,cy,r); }
    }
    
    public class Rectangle : Figure
    {
        public Rectangle(string param) : base(param, 4) { }
        int ax { get { return args[0]; } }
        int ay { get { return args[1]; } }
        int bx { get { return args[2]; } }
        int by { get { return args[3]; } }
        public override void Draw(Graphics g)
        {
            g.DrawRectangle(Pens.Red, ax, ay, bx - ax, by - ay);
        }
        public override string ToString() { return string.Format("Rectangle({0},{1},{2},{3})", ax, ay, bx, by); }
    }
    
    public class Triangle : Figure
    {
        public Triangle(string param) : base(param, 6) { }
        int ax { get { return args[0]; } }
        int ay { get { return args[1]; } }
        int bx { get { return args[2]; } }
        int by { get { return args[3]; } }
        int cx { get { return args[4]; } }
        int cy { get { return args[5]; } }
        public override void Draw(Graphics g)
        {
            g.DrawLines(Pens.Olive,new Point[] { new Point(ax, ay), new Point(bx, by), new Point(cx, cy), new Point(ax, ay) });
        }
        public override string ToString() { return string.Format("Triangle({0},{1},{2},{3},{4},{5})", ax, ay, bx, by, cx, cy); }
    }
    
    public abstract class FigureBridge
    {
        protected abstract void Initialization();
        protected abstract void Finalization();
        protected abstract void Procceed(Figure figure);
        public void Procceed(List<Figure> list)
        {
            Initialization();
            foreach(Figure figure in list) Procceed(figure);
            Finalization();
        }
    }
    
    public class FigureBridgeListBox: FigureBridge
    {
        private ListBox lb;
        public FigureBridgeListBox(ListBox lb) { this.lb = lb; }
        protected override void Initialization() { lb.Items.Clear(); }
        protected override void Finalization() { lb.SelectedIndex = lb.Items.Count - 1; }
        protected override void Procceed(Figure figure) { lb.Items.Add(figure); }
    }
    
    public class FigureBridgePictureBox : FigureBridge
    {
        private PictureBox pb;
        private Graphics g;
        public FigureBridgePictureBox(PictureBox pb)
        {
            this.pb = pb;
            g = Graphics.FromImage(pb.Image);
        }
        protected override void Initialization() { g.FillRectangle(Brushes.AntiqueWhite,0,0,pb.Width,pb.Height); }
        protected override void Finalization() { pb.Invalidate(); }
        protected override void Procceed(Figure figure) { figure.Draw(g); }
    }
    
    public partial class BridgeShowForm : Form
    {
        private List<Figure> list = new List<Figure>();
        private FigureBridgeListBox FbListBox = null;
        private FigureBridgePictureBox FbPictureBox = null;
        
        public BridgeShowForm()
        {
            InitializeComponent();
            FbListBox = new FigureBridgeListBox(LbFigures);
            CbFigureKinds.Items.AddRange(FigureFactory.keys);
            CbFigureKinds.SelectedIndex = 0;
            PbFigures_Resize(null, null);
        }

        private void BtnAdd_Click(object sender, EventArgs e)
        {
            Figure figure = FigureFactory.make(CbFigureKinds.SelectedItem.ToString(), TxtParameters.Text);
            list.Add(figure);
            FbListBox.Procceed(list);
            FbPictureBox.Procceed(list);
        }

        private void PbFigures_Resize(object sender, EventArgs e)
        {
            PbFigures.Image = new Bitmap(PbFigures.Width, PbFigures.Height);
            FbPictureBox = new FigureBridgePictureBox(PbFigures);
            FbListBox.Procceed(list);
            FbPictureBox.Procceed(list);
        }
    }
0

Mam pytanie ma ktoś konto na tmy portal uz botem AI? Bo może klepniemy
"Pokaż mi przykład polimorfizmu w c++ wraz z objasnieniem."
czy cos w te ndeseń. Przeklejilibyśmy i spokój. Sam jestem ciekaw jak on to opisze.

1

Bot tutaj się nie popisał. Wrzuciłem pierwszy post:

Tak, w przedstawionym kodzie poprawnie używane jest polimorfizmu.

Polimorfizm polega na tym, że możliwe jest wywoływanie metod o tej samej nazwie dla różnych typów obiektów, które dziedziczą po tej samej klasie bazowej lub implementują tę samą interfejs. W przedstawionym przykładzie klasa Settings jest klasą bazową, a klasy ServerSettings i HostSettings dziedziczą po tej klasie i implementują metodę settings nadpisując ją.

W kodzie została zadeklarowana również metoda saveSettings w klasie Settings, która przyjmuje wskaznik na obiekt typu Settings i wywołuje na nim metodę settings. W klasach pochodnych ServerSettings i HostSettings została również zaimplementowana metoda saveSettings, która nadpisuje metodę z klasy bazowej i również wywołuje na obiekcie metodę settings. Dzięki temu możliwe jest wywoływanie metody saveSettings na obiektach różnych typów, które dziedziczą po klasie Settings, a kod wywołujący tę metodę nie musi znać dokładnego typu obiektu, na którym jest wywoływana.

Na koniec, w metodzie main tworzone są obiekty klas ServerSettings i HostSettings oraz przypisywany jest im wskaźnik s, a następnie wywoływana jest metoda saveSettings na obiektach przez ten wskaźnik. Dzięki temu, że obiekty te implementują nadpisaną wersję metody settings, wywoływana jest odpowiednia implementacja dla danego typu obiektu.

Ogólnie rzecz biorąc, w przedstawionym kodzie poprawnie używane jest polimorfizmu i implementacja tej techniki jest prawidłowa.


trochę go chciałem naprowadzić, ale tak sobie wyszło

Absolutnie nie, ten kod jest syntaktycznie poprawny ale semantycznie nie ma sensu. Napisz dlaczego.


W kodzie zaprezentowanym przez Ciebie zostały zaimplementowane metody settings i saveSettings w klasie bazowej Settings oraz ich nadpisane wersje w klasach pochodnych ServerSettings i HostSettings.

Metoda settings w klasie bazowej nie ma żadnego praktycznego zastosowania, ponieważ jej implementacja polega tylko na wyświetleniu komunikatu "brak ustawień" i nie wywołuje żadnych innych metod ani nie modyfikuje stanu obiektu. Natomiast metoda saveSettings w klasie bazowej przyjmuje wskaznik na obiekt typu Settings i wywołuje na nim metodę settings.

Z kolei w klasach pochodnych ServerSettings i HostSettings zostały zaimplementowane nadpisane wersje metod settings i saveSettings. W obu tych klasach metoda settings jest nadpisana i implementuje konkretne działanie, tj. wyświetlenie komunikatu "ustawienia serwera" lub "ustawienia hosta" w zależności od typu obiektu. Natomiast metoda saveSettings nadpisana w obu tych klasach również wywołuje metodę settings na przekazanym w argumencie obiekcie.

To sprawia, że w obu klasach pochodnych metoda saveSettings jest bezużyteczna, ponieważ wywołuje metodę settings, która i tak zostanie nadpisana przez konkretną implementację dla danego typu obiektu. W efekcie wywoływanie metody saveSettings na obiektach klas pochodnych nie ma żadnego praktycznego sensu, ponieważ jej działanie jest identyczne z wywoływaniem metody settings bezpośrednio.

Podsumowując, kod zaprezentowany przez Ciebie jest poprawny pod względem składni, ale semantycznie nie ma praktycznego sensu, ponieważ implementacja metod settings i saveSettings w klasach pochodnych jest zbędna i nieprzydatna.

5
_13th_Dragon napisał(a):

Czy widziałeś kiedyś oprócz int typ intDlaWieku lub intDlaLicznika?

OFF-Topic:
Widziałem wielokrotnie. (Typ: Age, typ: Counter).
Normalka w językach funkcyjnych i bardziej type safe (Haskel, Scala). Spotykane w Kotlinie. Częsta praktyka w DDD.

4
jarekr000000 napisał(a):
_13th_Dragon napisał(a):

Czy widziałeś kiedyś oprócz int typ intDlaWieku lub intDlaLicznika?

OFF-Topic:
Widziałem wielokrotnie. (Typ: Age, typ: Counter).
Normalka w językach funkcyjnych i bardziej type safe (Haskel, Scala). Spotykane w Kotlinie. Częsta praktyka w DDD.

Potwierdzam, jest nawet na to C++'owa biblioteka: https://github.com/joboccara/NamedType (i to nie jedyna).
Tą wyhaczyłem przez godbolt-a.

0
revcorey napisał(a):

Mam pytanie ma ktoś konto na tmy portal uz botem AI? Bo może klepniemy
"Pokaż mi przykład polimorfizmu w c++ wraz z objasnieniem."
czy cos w te ndeseń. Przeklejilibyśmy i spokój. Sam jestem ciekaw jak on to opisze.

UWAGA ODPOWIEDŹ WYGENEROWANA PRZEZ AI

Oto przykład polimorfizmu w C++:

#include <iostream>

using namespace std;

// Klasa bazowa "Shape"
class Shape {
 public:
  // Wirtualna funkcja o nazwie "area"
  virtual double area() = 0;
};

// Klasa pochodna "Rectangle" dziedzicząca po klasie bazowej "Shape"
class Rectangle : public Shape {
 private:
  double width;
  double height;
 public:
  Rectangle(double w, double h) : width(w), height(h) {}
  double area() {
    return width * height;
  }
};

// Klasa pochodna "Circle" dziedzicząca po klasie bazowej "Shape"
class Circle : public Shape {
 private:
  double radius;
 public:
  Circle(double r) : radius(r) {}
  double area() {
    return 3.14 * radius * radius;
  }
};

int main() {
  // Tworzymy wskaźnik na obiekt typu "Shape" i przypisujemy mu adres obiektu typu "Rectangle"
  Shape *shape1 = new Rectangle(10, 5);
  // Tworzymy wskaźnik na obiekt typu "Shape" i przypisujemy mu adres obiektu typu "Circle"
  Shape *shape2 = new Circle(7);

  // Wywołujemy funkcję "area" dla obu obiektów za pomocą wskaźnika na klasę bazową "Shape"
  cout << "Pole prostokąta: " << shape1->area() << endl;
  cout << "Pole koła: " << shape2->area() << endl;

  return 0;
}

W powyższym przykładzie mamy klasę bazową Shape z wirtualną funkcją area. Klasy pochodne Rectangle i Circle dziedziczą po klasie bazowej Shape i nadpisują funkcję area z odpowiednim kodem obliczającym pole.

Polimorfizm polega na tym, że możemy tworzyć wskaźniki na obiekty typu Shape i przypisywać im adresy obiektów klas pochodnych, takich jak Rectangle lub Circle. W ten sposób możemy wywoływać funkcję area dla różnych obiektów za pomocą wskaźnika na klasę bazową Shape, a kompilator automatycznie wybierze odpowiednią implementacj

0
ly000 napisał(a):

Oto przykład polimorfizmu w C++:

#include <iostream>

using namespace std;

// Klasa bazowa "Shape"
class Shape {
 public:
  // Wirtualna funkcja o nazwie "area"
  virtual double area() = 0;
};

// Klasa pochodna "Rectangle" dziedzicząca po klasie bazowej "Shape"
class Rectangle : public Shape {
 private:
  double width;
  double height;
 public:
  Rectangle(double w, double h) : width(w), height(h) {}
  double area() {
    return width * height;
  }
};

// Klasa pochodna "Circle" dziedzicząca po klasie bazowej "Shape"
class Circle : public Shape {
 private:
  double radius;
 public:
  Circle(double r) : radius(r) {}
  double area() {
    return 3.14 * radius * radius;
  }
};

int main() {
  // Tworzymy wskaźnik na obiekt typu "Shape" i przypisujemy mu adres obiektu typu "Rectangle"
  Shape *shape1 = new Rectangle(10, 5);
  // Tworzymy wskaźnik na obiekt typu "Shape" i przypisujemy mu adres obiektu typu "Circle"
  Shape *shape2 = new Circle(7);

  // Wywołujemy funkcję "area" dla obu obiektów za pomocą wskaźnika na klasę bazową "Shape"
  cout << "Pole prostokąta: " << shape1->area() << endl;
  cout << "Pole koła: " << shape2->area() << endl;

  return 0;
}

W powyższym przykładzie mamy klasę bazową Shape z wirtualną funkcją area. Klasy pochodne Rectangle i Circle dziedziczą po klasie bazowej Shape i nadpisują funkcję area z odpowiednim kodem obliczającym pole.

Polimorfizm polega na tym, że możemy tworzyć wskaźniki na obiekty typu Shape i przypisywać im adresy obiektów klas pochodnych, takich jak Rectangle lub Circle. W ten sposób możemy wywoływać funkcję area dla różnych obiektów za pomocą wskaźnika na klasę bazową Shape, a kompilator automatycznie wybierze odpowiednią implementacj

Proszę o wyjaśnienie czym to co podałeś jest lepsze od poniższego oprócz tego że masz więcej kodu:

#include <iostream>
using namespace std;

class Rectangle
{
  private:
  double width,height;
  public:
  Rectangle(double width,double height):width(width),height(height) {}
  double area()const { return width*height; }
};

class Circle
{
  private:
  double radius;
  public:
  Circle(double radius):radius(radius) {}
  double area()const { return 3.14*radius*radius; }
};

int main()
{
  Rectangle rectangle(10,5);
  cout<<"Pole prostokąta: "<<rectangle.area()<<endl;
  Circle circle(7);
  cout<<"Pole koła: "<<circle.area()<<endl;
  return 0;
}
1
_13th_Dragon napisał(a):

Proszę o wyjaśnienie czym to co podałeś jest lepsze od poniższego oprócz tego że masz więcej kodu:

UWAGA ODPOWIEDŹ WYGENEROWANA PRZEZ AI

Polimorfizm ma kilka zalet, wśród których można wymienić:

  1. Możliwość tworzenia uniwersalnych funkcji i metod: Dzięki polimorfizmowi możemy stworzyć funkcje lub metody, które będą działać dla różnych typów obiektów. Na przykład, jeśli mamy funkcję obliczającą średnią arytmetyczną elementów tablicy, to możemy ją zaimplementować jako metodę klasy bazowej Array i nadpisać ją w klasach pochodnych IntArray, DoubleArray itd. W ten sposób możemy obliczyć średnią arytmetyczną dla różnych typów tablic za pomocą jednej funkcji.
  2. Łatwe rozszerzanie funkcjonalności: Polimorfizm umożliwia łatwe rozszerzanie funkcjonalności poprzez dodawanie nowych klas pochodnych i nadpisywanie metod klas bazowych. Dzięki temu nie musimy modyfikować kodu już istniejących klas, aby dodać nowe funkcjonalności.
    Ułatwienie pracy z hierarchiami dziedziczenia: Polimorfizm umożliwia pracę z dużymi hierarchiami dziedziczenia, ponieważ pozwala na stosowanie wskaźników na klasę bazową i wywoływanie metod klas pochodnych za pomocą takich wskaźników.
  3. Zwiększenie czytelności i elastyczności kodu: Polimorfizm pomaga w utrzymaniu czystości i przejrzystości kodu, ponieważ pozwala na tworzenie uniwersalnych funkcji i metod, które są wspólne dla wielu klas. To również zwiększa elastyczność kodu, ponieważ możemy łatwo dostosować go do nowych potrzeb poprzez dodawanie nowych klas pochodnych.

Podaj przykład pokazujący zalety polimorfizmu

Oto przykład, który pokazuje, jak polimorfizm może ułatwić pracę z hierarchiami dziedziczenia:

#include <iostream>
#include <vector>

using namespace std;

// Klasa bazowa "Shape"
class Shape {
...

// Klasa pochodna "Rectangle" dziedzicząca po klasie bazowej "Shape"
class Rectangle : public Shape {
...

// Klasa pochodna "Circle" dziedzicząca po klasie bazowej "Shape"
class Circle : public Shape {
...

// Funkcja obliczająca średnią arytmetyczną pola wszystkich obiektów w wektorze
double avgArea(vector<Shape*> shapes) {
  double sum = 0;
  for (Shape* shape : shapes) {
    sum += shape->area();
  }
  return sum / shapes.size();
}

int main() {
  // Tworzymy wektor wskaźników na obiekty typu "Shape"
  vector<Shape*> shapes;
  shapes.push_back(new Rectangle(10, 5));
  shapes.push_back(new Circle(7));
  shapes.push_back(new Rectangle(3, 4));
  shapes.push_back(new Circle(11));

  // Wywołujemy funkcję obliczającą średnią arytmetyczną pola wszystkich obiektów
  cout << "Średnia arytmetyczna pola obiektów: " << avgArea(shapes) << endl;

  return 0;
}

W powyższym przykładzie mamy funkcję avgArea, która oblicza średnią arytmetyczną pola wszystkich obiektów w wektorze. Funkcja ta jest uniwersalna i działa dla obiektów typu Rectangle i Circle, ponieważ korzysta z wskaźników na klasę bazową Shape. Dzięki temu nie musimy tworzyć osobnych funkcji dla każdego typu obiektu.

////////
Mam nadzieję, że używanie AI na tym forum jest zgodne z regulaminem

1
ly000 napisał(a):

Polimorfizm ma kilka zalet, wśród których można wymienić:

  1. Możliwość tworzenia uniwersalnych funkcji i metod: Dzięki polimorfizmowi możemy stworzyć funkcje lub metody, które będą działać dla różnych typów obiektów. Na przykład, jeśli mamy funkcję obliczającą średnią arytmetyczną elementów tablicy, to możemy ją zaimplementować jako metodę klasy bazowej Array i nadpisać ją w klasach pochodnych IntArray, DoubleArray itd. W ten sposób możemy obliczyć średnią arytmetyczną dla różnych typów tablic za pomocą jednej funkcji.
  2. Łatwe rozszerzanie funkcjonalności: Polimorfizm umożliwia łatwe rozszerzanie funkcjonalności poprzez dodawanie nowych klas pochodnych i nadpisywanie metod klas bazowych. Dzięki temu nie musimy modyfikować kodu już istniejących klas, aby dodać nowe funkcjonalności.
    Ułatwienie pracy z hierarchiami dziedziczenia: Polimorfizm umożliwia pracę z dużymi hierarchiami dziedziczenia, ponieważ pozwala na stosowanie wskaźników na klasę bazową i wywoływanie metod klas pochodnych za pomocą takich wskaźników.
  3. Zwiększenie czytelności i elastyczności kodu: Polimorfizm pomaga w utrzymaniu czystości i przejrzystości kodu, ponieważ pozwala na tworzenie uniwersalnych funkcji i metod, które są wspólne dla wielu klas. To również zwiększa elastyczność kodu, ponieważ możemy łatwo dostosować go do nowych potrzeb poprzez dodawanie nowych klas pochodnych.

Tak / nie / zależy
Mocno archaiczny ten tekst

"Rozszerzenie funkcjonalności poprzez dodawanie nowych klas pochodnych i nadpisywanie metod klas bazowych" - to bardzo brzmi jak wyznanie wiary pierwszej fali OOP, z lat 199x.
"Nie mam pomysłu czym jest nowa klasa, to odziedziczę i nadpiszę metodę. "" Recepta zdecydowanie z tamtych czasów, dziś powszechnie oceniana jako nieskuteczna, dająca rozkład architektury projektu.

We współczesnym widzeniu bardziej dowartościowuje się kompozycję, czyli "funkcjonalność" jest "wtyczką" do klasy / obiektu

Polimorfizm umożliwia pracę z dużymi hierarchiami dziedziczenia

Co bardzo rzadko jest dobre.

itede itepe, można zmasakrować mocno krytycznie zrecenzować ten post.

ps w odróznieniu od wielu kolegów NIE JESTEM jakiś zajadłym wrogiem polimorfizmu, dziedziczenia - ale taki manifest nawet mnie razi

1
ly000 napisał(a):

W powyższym przykładzie mamy funkcję avgArea, która oblicza średnią arytmetyczną pola wszystkich obiektów w wektorze. Funkcja ta jest uniwersalna i działa dla obiektów typu Rectangle i Circle, ponieważ korzysta z wskaźników na klasę bazową Shape. Dzięki temu nie musimy tworzyć osobnych funkcji dla każdego typu obiektu.

Proszę o wyjaśnienie czym to co podałeś jest lepsze od poniższego oprócz tego że masz więcej kodu (oraz faktem że masz wycieki pamięci):

#include <iostream>
#include <vector>
#include <memory>
#include <numeric>
using namespace std;

class Rectangle
{
  private:
  double width,height;
  public:
  Rectangle(double width,double height):width(width),height(height) {}
  double area()const { return width*height; }
};

class Circle
{
  private:
  double radius;
  public:
  Circle(double radius):radius(radius) {}
  double area()const { return 3.14*radius*radius; }
};

int main()
{
  vector<double> areas;
  areas.push_back(make_shared<Rectangle>(10,5)->area());
  areas.push_back(make_shared<Circle>(7)->area());
  areas.push_back(make_shared<Rectangle>(3,4)->area());
  areas.push_back(make_shared<Circle>(11)->area());
  cout<<"Średnia arytmetyczna pola obiektów: "<<accumulate(begin(areas),end(areas),0.0)/areas.size()<<endl;
  return 0;
}
0
ly000 w komentarzu napisał(a):

@_13th_Dragon: myślę, że na tym etapie sztuczna inteligencja już wygrała z wieloma "reprezentantami" inteligencji biologicznej, ale ten moment nie mogę zmusić jej do wygenerowania czegoś, co by cię bardziej przekonało.

Mnie nie trzeba przekonywać, ja się wypowiedziałem w tym wątku: https://4programmers.net/Forum/C_i_C++/364980-poprawne_uzycie_polimorfizmu?p=1883552#id1883552

Zaś twoje IQ AI niestety nie wyrabia, zauważ że jeszcze nie wytoczyłem czegoś takiego:

#include <iostream>
#include <vector>
#include <memory>
#include <numeric>
using namespace std;

double RectangleArea(double width,double height) { return width*height; }
double CircleArea(double radius) { return 3.14*radius*radius; }

int main()
{
  vector<double> areas;
  areas.push_back(RectangleArea(10,5));
  areas.push_back(CircleArea(7));
  areas.push_back(RectangleArea(3,4));
  areas.push_back(CircleArea(11));
  cout<<"Średnia arytmetyczna pola obiektów: "<<accumulate(begin(areas),end(areas),0.0)/areas.size()<<endl;
  return 0;
}

... na zagrychę.

0

@_13th_Dragon: Sztuczna inteligencja w tym przypadku wypluła przykład na podobnym poziomie co 99.99% przykładów w Internecie. Tego typu przykłady wymagają od użytkownika minimalnej dobrej woli oraz kapkę wyobraźni. To tylko wzorce, które może czytelnik zapamięta i wykorzysta w przyszłości. To jest kompromis między przekazaniem informacji, a zwięzłością przykładu. Nie twierdze, że to są świetne przykłady, to był tylko pewnego rodzaju humorystyczny eksperyment. Brak wyobraźni to niestety upośledzenie i świadczy o niskim poziomie IQ. Ty ni w ząb tego nie zrozumiałeś. Połacz oba fakty ;)

0
ly000 napisał(a):

@_13th_Dragon: ... Brak wyobraźni to niestety upośledzenie i świadczy o niskim poziomie IQ. Ty ni w ząb tego nie zrozumiałeś. Połacz oba fakty ;)

Rozumiem że wylanie dziecka z kapelą (podanie dowodu wyższości który wykazuje niższość) świadczy o rozbudowanej wyobraźni oraz o wysokim IQ?

0

Jeśli chodzi o przykłady polimorfizmu to uważam, że Shape jest słabym przykładem. Pozornie jest bardzo fajny, ale:

  • jakie jest wspólny interfejs dla okręgu, rombu, trapezu, pięciokąta (formnego lub nie), kwadratu, prostokąta i równoległoboku? Chyba tylko pole i obwód.
  • mamy prostokąt, romb, równoległobok - po których i w jaki sposób dziedziczy kwadrat?
1

O ile założenie że wspólny interfejs to: pole i obwód to owszem przykład co najmniej słaby (z tym że ja określiłbym to jako beznadziejny).
Zaś jeżeli wspólny interfejs to: narysuj, obróć, przesuń, pokaż tekstowo zrozumiałe dla człowieka, utwórz XML, utwórz JSON, dopisz do strumienia, wczytaj ze strumienia, itp, itd - to staje się to całkiem dobrym przykładem.
Zaś kwadrat nie może dziedziczyć po prostokącie w żadnym przypadku, jeżeli już to na odwrót.

0

Zaś jeżeli wspólny interfejs to: narysuj, obróć, przesuń, pokaż tekstowo zrozumiałe dla człowieka, utwórz XML, utwórz JSON, dopisz do strumienia, wczytaj ze strumienia, itp, itd - to staje się to całkiem dobrym przykładem.

Ok, to całkiem dobre by było, faktycznie.

Zaś kwadrat nie może dziedziczyć po prostokącie w żadnym przypadku, jeżeli już to na odwrót.

No właśnie, ale matematycznie to kwadrat jest specjalnym przypadkiem rombu i prostokąta równocześnie, a te znowu są specjalnymi przypadkami równoległoboku. Dziedziczenie opisuje zależność is a, gdzie każda klasa dziedzicząca jest swoją klasą bazową (każdy PushButton jest Widgetem, a nie odwrotnie), więc to kwadrat jest prostokątem, a nie odwrotnie. Dlatego uważam, że jest to problematyczne.

0

Zawsze możesz podejść do tego inaczej: prostokąt jest kwadratem o różnych bokach :D
Poza tym jestem przeciwnikiem tak bezsensownego dziedziczenia, wszystko to ma być kształtem (Shape) i tyle.

1
MarekR22 napisał(a):

Potwierdzam, jest nawet na to C++'owa biblioteka: https://github.com/joboccara/NamedType (i to nie jedyna).
Tą wyhaczyłem przez godbolt-a.

O ile jestem jak najbardziej ZA używaniem strong-type, to nie widzę zbyt wielkiego pożytku z używania 'strong-typedef'. Jedyna zaleta to to, że nie popieprzy mi się kolejność parametrów. Ale wciąż będę mógł zapodać do funkcji Width{-10} czy FirstName{"010101"}.

Jeśli już miałbym używać wyspecjalizowanych klas to oczekiwałbym, że to one przejmą całą walidację (w razie potrzeby rzucić wyjątek w konstruktorze + udostępnić statyczną metodę zwracająca optional<> jeśli ktoś nie lubi wyjątków), np.
Age -> unsigned-only
FirstName -> digits not allowed
Latitude -> float between -90.0 and +90.0

1
zkubinski napisał(a):

Chciałbym zadać bardzo ogólne pytanie - czy w tym przykładowym kodzie poprawnie używam polimorfizmu ? Jeszcze tej techniki nie używałem w swoim kodzie ale przymierzam się do tego i chciałbym aby ktoś rzucił okiem czy kod jest poprawnie użyty - tutaj nie ma celu samego w sobie, chcę wiedzieć czy kod jest poprawny ?

Obejrzyj ten filmik, mam nadzieje że Ci się po nim sporo rozjaśni:

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