Pierwsze kroki związane z kompozycją

0
#include <iostream>

class Point
{
    int x;
    int y;
public:
    int getX(){return x;}
    int getY(){return y;}
    inline void setX(int a){x=a;}
    inline void setY(int b){y=b;}
};

class Console
{
    Point pnt;
public:
    void enterDataX(int a)
    {
     std::cout<<"Wspolrzedna x: ";
     std::cin >> a;
     pnt.setX(a);
    }
    void enterDataY(int b)
    {
    std::cout<<"Wspolrzedna y: ";
     std::cin >> b;
     pnt.setY(b);
    }
    int showDataX(){return pnt.getX();}
    int showDataY(){return pnt.getY();}
};
int main()
{
    Console csl;
    int x,y;
    csl.enterDataX(x);
    csl.enterDataY(y);
    std::cout<<"P = ("<<csl.showDataX()<<","<<csl.showDataY()<<")"<<std::endl;
    return 0;
}

Do oceny.
Kod działa,ale jest to pierwszy kod napisany w OOP,gdzie chce pozbyć się nawyku korzystania z dziedziczenia ucząc się dobrych zachowań.

0

No niezły, podoba się dla mnie ten kod.

0
void enterDataX(int a)
{
 std::cout<<"Wspolrzedna x: ";
 std::cin >> a;
 pnt.setX(a);
}
void enterDataY(int b)
{
std::cout<<"Wspolrzedna y: ";
 std::cin >> b;
 pnt.setY(b);
}

Mnie zastanawia to w jaki sposób możnaby było skrócić ten fragment kodu do uniwersalnej funkcji pozwalającej na wprowadzenie danych

2

https://godbolt.org/z/d479fe7n7

<source>: In function 'int main()':
<source>:38:19: error: 'x' is used uninitialized in this function [-Werror=uninitialized]
   38 |     csl.enterDataX(x);
      |     ~~~~~~~~~~~~~~^~~
<source>:37:11: error: 'y' may be used uninitialized in this function [-Werror=maybe-uninitialized]
   37 |     int x,y;
      |           

Zresztą sama funkcja jest bezsensu ignoruje parametr:

    void enterDataX(int a)
    {
     std::cout<<"Wspolrzedna x: ";
     std::cin >> a; // tu wartość "a" jest nadpisywana, więc parametr jest ignorowany
     pnt.setX(a);
    }

Sam pomysł by operacje IO zamknąć w osobnej klasie jest jak najbardziej słuszny, tylko wykonie jakieś dziwne.

1

Masz pomieszane odpowiedzialności, konsola niemal bezpośrednio eksponuje interfejs punktu. Poza tym nie potrzebujesz argumentów w metodach enterData
Zamiast

void enterDataX(int a)
{
 std::cout<<"Wspolrzedna x: ";
 std::cin >> a;
 pnt.setX(a);
}

Napisz

void enterDataX()
{
 int a;
 std::cout<<"Wspolrzedna x: ";
 std::cin >> a;
 pnt.setX(a);
}
0

Co do wyjaśnienia czym jest OOP to polecam Wujka Boba, bo nie chodzi tu używanie klas.

0

Tak to jest jak się robi przerwę w nauce programowania 2 lata :/

1

w ogóle odpowiedz sobie na pytanie jaki sens ma klasa Point w obecnej postaci, lepiej

struct Point
{
    int x;
    int y;
};

edit:
w niektórych przypadkach deklaruje się klasę w klasie. Teoretycznie można zrobić tu i tak tzn. point będzie w Console. Ale czy to jest zasadne w tym przypadku? Tak sobie.

3

Może porównaj i sam oceń:

#include <iostream>
using namespace std;

struct Point
{
    int x,y;
    Point():x(0),y(0) {}
    Point(int x,int y):x(x),y(y) {}
    friend ostream &operator<<(ostream &s,const Point &p) { return s<<'('<<p.x<<','<<p.y<<')'; }
    friend istream &operator>>(istream &s,Point &p) { return s>>p.x>>p.y; }
};

class Console
{
	private:
    Point pnt;
	public:
    void enterCoords()
    {
    	cout<<"Poday wspolrzedne x y: ";
     	cin>>pnt;
    }
    Point &getCoord() { return pnt; }
};


int main()
{
    Console csl;
    csl.enterCoords();
    cout<<"P = "<<csl.getCoord()<<endl;
    return 0;
}

Generalnie nie musisz chować i opakowywać w settery/gettery niezależne dane.
Czyli dopóki x,y mogą mieć każdą możliwą kombinację to settery/gettery tylko szkodzą.

1

@Butterfly994: Na Twoim miejscu zadałbym sobie zajebiście ważne pytanie, po co chcesz się tego OOPu uczyć? Jeśli chcesz sobie przypomnieć jak się programuje to zaczynanie od wysokopoziomowych technik organizacji kodu gdy nie umie się pisać kodu to lekkie nieporozumienie. Technik organizacji kodu najlepiej się uczyć jak zacznie Ci dokuczać bałagan. Wtedy empirycznie będziesz miał okazję sprawdzić jaka technika rozwiązała Twój problem a jaka nie. Twój kod z tematu można by zapisać w ten sposób

struct Point {int x,y;};
int main()
{
  Point p;
  std::cout "Podaj x \n";
  std::cin >> p.x;
  std::cout << "Podaj y \n";
  std::cin >> p.y;

  std::cout << "Podano " << p.x << ":" << p.y << "\n";
  
  return 0;
}

Mniej niż połowa tego, co sam napisałeś.

OOP to zestaw technik na budowanie relacji w kodzie między pomniejszymi systemami, ten kod jest zbyt trywialny żeby móc wyjaśnić cokolwiek. Dodam jeszcze, że w zdecydowanej większości hobbystycznych projektów jesteś w stanie całkowicie się obejść bez OOPu nie tracąc ani skalowalności ani testowalności, ale to już osobny temat.

Więc musisz sobie zadać pytanie, czy wolisz poświecić czas na naukę jak pisać programy czy na naukę jak organizować kod, którego nie masz i nie umiesz pisać?

1
#include <iostream>



template <typename VALUE_CLASS>
class Console
{
protected:
    const std::string what;
    virtual const std::string & get_name() const = 0;
    virtual VALUE_CLASS & get_value() = 0;
    virtual void output()
    {
        std::cout << what << " " << get_name() << ": ";
    }
    virtual void input()
    {
        std::cin >> get_value();
    }
public:    
    Console(const std::string & w): what{w} {}
    void enter()
    {
        output();
        input();
    }
};

template <typename VALUE_CLASS, typename CONSOLE_CLASS = Console<VALUE_CLASS>>
class Coordinate: public CONSOLE_CLASS
{
private:
    VALUE_CLASS value;
    const std::string name;
protected:
    virtual const std::string & get_name() const override { return name; }
    virtual VALUE_CLASS & get_value() override { return value; }
public:
    Coordinate(const std::string & n): CONSOLE_CLASS{"wspolrzedna"}, name{n} {}
    VALUE_CLASS get() const { return value; }
    void set(VALUE_CLASS a){ value=a; }
};

class Point
{
public:
    Coordinate<int> x,y;
    Point(): x{"x"}, y{"y"} {}
};

std::ostream & operator<<(std::ostream & s, const Point & p)
{
    s << "P = (" << p.x.get() << "," << p.y.get() << ")";
    return s;
}

int main()
{
    Point pnt;
    pnt.x.enter();
    pnt.y.enter();
    std::cout << pnt << std::endl;
    return 0;
} 

Taka luźna wariacja na zadany temat. Ma pewne zalety:

  1. każda współrzędna wie jak się nazywa
  2. gdybyśmy chcieli użyć np. współrzędnych typu float albo double to się łatwo da zrobić
  3. redefiniując w Coordinate metody input i/lub output można czytać/pisać do innego strumienia, albo np. dodać ekstra formatowanie
  4. zmieniając klasę bazową w Coordinate na coś innego można całkowicie zmienić zachowanie
  5. kod wypisywania jak również getter i setter są związane ze współrzędną

No i ma pewnie wady:

  1. jest "overengineered" dla tego problemu ;)))
  2. łatwo uzyskać nieczytelny komunikat błędu (trzeba by użyć konceptów, żeby nałożyć pewne ograniczenia np. czym ma być Console)
0

po co Point, coś, co jest koncepcyjnie pojemnikiem na dane/krotką, ma mieć gettery i settery?

Dalej. Robisz to na intach. Czy robisz to celowo, czy z przypadku? Załóżmy, że masz dwa punkty (0, 0) i (5, 3) i chcesz obliczyć współrzędne punktu, który leży pośrodku. A ten wypadałby na pozycji (2.5, 1.5), czyli tego w ogóle nie da się zapisać, bo twoja klasa obsługuje tylko liczby całkowite. Ale to nie musi być złe, pod warunkiem, że faktycznie chcesz mieć układ współrzędnych, który zawsze będzie zaokrąglany do liczby całkowitej (jeśli chcesz mieć szachownicę i tylko całkowite współrzędne będą valid).

Czyli typy lepiej przemyśleć.

Klasy Console natomiast nie kumam. Na pewno nie powinna się tak nazywać (bo to nie jest jakaś uniwersalna "konsola", a raczej wrapper na klasę Point (model), który dodatkowo może przyjmować dane od użytkownika(kontroler) oraz zwracać prezentację(widok). Innymi słowy w klasie Console zawarte są wszystkie 3 literki wzorca MVC... może by to miało sens w jakimś specyficznym zastosowaniu w większym projekcie (żeby zrobić jakiś widżet do debugowania klasy Point i później używać w wielu miejscach), ale tutaj to jest sztuka dla sztuki, zaciemnianie kodu.

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