Obiekt funkcyjny

0

Chciałbym stworzyć klasę, która będzie zajmowała się sczytywaniem danych z pliku, a następnie zwróci zapełniony danymi wektor. Jak to zrobić poprawnie?

  1. Powinienem stworzyć obiekt tej klasy i użyć funkcji typu sczytajDane() i zwrocWektor()
  2. Napisać metody statyczne(wtedy w metodzie musiałbym tworzyć wszystkie zmienne, chyba że da się inaczej)
  3. Po prostu napisać funkcje w oddzielnym pliku i nie tworzyć klas na siłę.
  4. Jakoś inaczej
1

Idealnie by było gdybyś stworzył interfejs (klasę abstrakcyjną technicznie rzecz biorąc) Serializable I stworzył funkcję/klasę która automatycznie zapełni kontener odpowiednimi obiektami wczytując że strumienia. Prawdopodobnie dodatkowo będzie tam obsługa jakiegoś nagłówka. Takie jest wzorcowe rozwiązanie obiektowe.

1

W tym zagadnieniu przyda ci się Bracie @Bolae zastosowanie szablonów, aby klasa poradziła sobie z dowolnym typem danych wczytywanych z pliku.

2

Dużo zależy od tego jakie są to dane, czy format jest skomplikowany. A zmierzam do tego, że jeśli jest to coś relatywnie prostego to i kod powinien być prosty. Zrób to najprościej jak potrafisz, może być funkcja, która przyjmuje nazwę pliku a zwraca wektor z danymi. Jeśli okaże się, że jest to coś więcej niż kilka linijek wtedy zrób refaktoring tak aby poprawić czytelność.
Może być tak, że samo parsowanie jest złożone, wtedy należało by pomyśleć o jakimś parserze. Ale możemy tylko gdybać bo nie znamy szerszego kontekstu.
Co do pytania o poprawność. Prostota i czytelność kodu to najważniejsze cechy dobrego kodu. Jeśli kod działa, nie zawiera błędów a przy tym jest czytelny i prosty to już połowa sukcesu. Połowa, bo są jeszcze inne cechy - poczytaj np. o SOLID.

0

@rrowniak: @elwis @MasterBLB

Tak wyglądałoby moje rozwiązanie gdybym nie korzystał z klas, jednakże nie do końca mnie to satysfakcjonuje, byłbym wdzięczny gdyby ktoś mi wyjaśnił jak to zapisać obiektowo.

#include <vector>
#include <fstream>
#include "Field.h" //interfejs(tak to chyba się nazywa)
//klasy dziedziczące
#include "Chest.h"
#include "Stone.h"
#include "Target.h"


using namespace std;

vector<Field*> readMap() //Field* wskaźnik polimorficzny
{
	short temp;
	ifstream inFile;
	vector<Field*> tab;
	inFile.open("map.txt"); //w pliku znajduje się 60 znaków
	while (inFile.good())
	{
		inFile >> temp;
		if (temp == 1)
			tab.push_back(new Chest());
		else if(temp == 2)
			tab.push_back(new Target());
		else if(temp == 3)
			tab.push_back(new Stone());
	}
	return tab;
}

1

Wrócę do chaty za jakieś 45 min to ci Bracie napiszę jakbym ja to zrobił.

0

Na szybkiego:

#include "Field.h"

#include "Chest.h"
#include "Stone.h"
#include "Target.h"

#include <vector>
#include <fstream>

// using namespace std; // niewskazane

std::unique_ptr<Field> FieldFactory(std::string_view data)
{
    std::istringstream inputs { data };

    std::string typeDesc;
    if (inputs >> typeDesc) {
          if (typeDesc == "Chest") {
               return std::make_unique<Chest>(inputs);
          }
          else if (typeDesc == "Stone") {
               return std::make_unique<Stone>(inputs);
          }
          ....
    }
    return {};
}

std::vector<std::unique_ptr<Field>> readMap(std::istream &input)
{
    std::vector<std::unique_ptr<Field>> result;
    std::string objectLine;
    while (std::getline(intput, objectLine)) {
        if (auto field = FieldFactory(objectLine)) {
             result.push_back(std::move(field));
        } else {
             std::error << "Failed to load line with data: " << objectLine << std::endl;
        }
    }

    return result;
}

Ale lepiej było by użyć parsera xml-a, oraz zrobić mapę typów obiektów na ich fabryki.

Bolae napisał(a):
  1. Po prostu napisać funkcje w oddzielnym pliku i nie tworzyć klas na siłę.

Lepiej rób klasy na siłę, żeby trzymać się: Single responsibility principle.
Kod, który piszą ludzie unikający tworzenia dodatkowych klas, szybko zmienia się w spaghetti code.

0

@MarekR22: Fajnie napisane, ale nie rozumiem na czym polega obiektowość tego rozwiązania(albo nie rozumiem programowania obiektowego).
Poza tym z tego co czytałem, powszechnie używane przestrzenie nazw, czyli chyba wyłącznie "std", są w pełni akceptowalne, chyba że ta opinia jest już niepopularna?

2

Więc tak Bracie - ja użyłbym szablonu aby dać sobie radę z dowolnym typem danych, tak jak to robi na przykład std::vector. Tak mniej więcej by ona wyglądała:

enum DataLoaderErrors
{
     isOK = 0,
     emptyDataFileName,
     fileNotFound,
     fileOpeningError,
     fileIsEmpty
};

template<typename T>
class DataLoader
{
public:
    explicit DataLoader(const std::string &pathAndFileName);
    explicit DataLoader() = default;
    virtual ~DataLoader() = default;

    virtual DataLoaderErrors loadDataFromFile();

    virtual void setDataFile(const std::string &pathAndFileName);
    virtual const std::string& getDataFile() const;
    
    virtual const std::vector<T>& getDataConst() const;
    virtual std::vector<T>& getData();

protected:
     std::vector<T> data;
     std::string dataFile;
};

Co do typu T trzeba by dodatkowo dać mu wymóg, że będzie umiał zrobić formatowanie ifstream >> T; przeciążając operator. Ale to bez obaw, jak ktoś zapomni to już przy kompilacji mu się to posypie.
Dzięki takiemu podejściu będziesz miał Bracie sympatyczną klasę która sobie poradzi z każdym typem danej spełniającej powyższy warunek. Co więcej, ponieważ ten DataLoader jest klasą to ktoś będzie mógł go odziedziczyć, i dopisać potrzebne mu nowe zdolności, ot np, DataHandlera który nie tylko będzie potrafił czytać z pliku dane typu T i zapisywać je do wektora, ale także zapisywać je z wektora do pliku, a najlepsze w tym jest to, że nie będzie musiał zawracać ci przy tym d**y o dopisanie czegoś.

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