Progettazione API per la memorizzazione di dati di più fasi nell'oggetto

1
    struct co_ordinate {
        double x;
        double y;
    };

// ----- > DESIGN 1

    class Land {
      public:
        Land(std::vector<co_ordinate> co) shape_before_plotting(co) {}
        // actions
        // creates stage 2
        // populates shape_after_plotting_outside_boundary
        void do_plotting_of_boundary(size_t off_set){ 
              // logic here
              // this off_set is used to know where to add new co_ordinates
              // in the boundary
              // (the beginning and end of boundary is untouched)      
       }
        // creates stage 3
        // populates shape_after_plotting_land
        void do_internal_plotting(){
        // logic here
        //  adds new co-ordinate where line intersected inside the land after
        // stage 2.
        }

        // getters
        std::vector<co_ordinate> get_shape_before_plotting{
        return shape_before_plotting;
       }

        std::vector<co_ordinate> get_shape_after_plotting_outside_boundary {
        return shape_after_plotting_outside_boundary;
        }

        std::vector<co_ordinate> get_shape_after_plotting_land {
        return shape_after_plotting_land;
        }

       private:
        std::vector<co_ordinate> shape_before_plotting;

        std::vector<co_ordinate> shape_after_plotting_outside_boundary;

        std::vector<co_ordinate> shape_after_plotting_land;
    };


   int main(){
      Land my_land(vec);
      // stage 2 populates shape_after_plotting_outside_boundary
      my_land.do_plotting_of_boundary();

      // stage 3 populates shape_after_plotting_land 
      my_land.do_internal_plotting();

      // get co ordinate here

    }

// ----- > DESIGN 2

    class Land {
     public:
        Land(std::vector<co_ordinate> co) plotting(co) {}

        Land operator=(const Land& other) {// logic for assignment operator}

          // actions

         // creates stage 2
         // populates shape_after_plotting_outside_boundary
        void do_plotting_of_boundary(size_t off_set){ 
              // logic here
              // this off_set is use know where to add new co_ordinates
              // in the boundary
              // the beginning and end of boundary is untouched 

       }
        // creates stage 3
        // populates shape_after_plotting_land
        void do_internal_plotting(){
        // logic here
        //  adds new co-ordinate where line intersected inside the land.

        }
        // getters

        std::vector<co_ordinate> get_shape_co_oridinate(){
        return plotting;
       }

       private:
        std::vector<co_ordinate> plotting;
    };

    int main(){
      Land stage_1(vec);

       Land stage_2(stage_1);
      // plotting is replaced by 
      // new co-ordinates to store stage 2 co-ordinates
      stage_2.do_plotting_of_boundary();
      // use assignment operator
      // 
      Land stage_3(stag_2);
      // plotting is replaced by 
      // new co-ordinates to store stage 3 co-ordinates
      stage_3.do_internal_plotting();


      // get co ordinate here

      // Now to get different co-ordinate of stage 1
      // 
       std::vector<co_ordinate> hold_stage_1_co_ordinate = stage_1.get_shape_co_oridinate();

       // Now to get different co-ordinate of stage 2
      // 
       std::vector<co_ordinate> hold_stage_2_co_ordinate = stage_2.get_shape_co_oridinate();
        // Now to get different co-ordinate of stage 3
      // 
       std::vector<co_ordinate> hold_stage_3_co_ordinate = stage_3.get_shape_co_oridinate();
      }

Sto lavorando a un progetto in cui otteniamo la terra e li tracciamo. Rappresentiamo la terra in x, y sistema di coordinate.

fase 1 (rappresentazione coarse):

Per semplicità, diciamo che la terra che abbiamo ha quattro limiti.

confini delle curve: i contorni possono essere curvati in modo da rappresentare i bordi curvi abbiamo bisogno di più coordinate.

confini diritti: per rappresentare i confini diritti sono sufficienti solo due coordinate.

fase 2 (rappresentazione fine):

Una volta ottenuto il terreno, dividiamo il confine (segmento di linea) a un certo intervallo. Diciamo alla lunghezza di 5 metri. Questo procederà all'incremento del numero di coordinate terra usata per ripresentarsi.

fase 3 (rappresentazione fine e interna della coordinata):

In questa rappresentazione disegniamo le linee tra la coordinata sul confine dalla rappresentazione Fine e aggiungi una nuova coordinata che si interseca all'interno del terreno.

Ho bisogno di salvare tutti i coordinati che ripropongono la terra.

La mia domanda è:

Pensi che il modo in cui sto conservando il coordinato in Design 1 sia un buon design? Sto dubitando di me stesso perché il design dell'API sembra troppo prolisso e probabilmente inconsistente.

Ecco perché ho creato Desing 2, la cui API è semplice

std::vector<co_ordinate> get_shape_co_ordinate();

Ma in questo design devo gestire più oggetti. Quale pensi che sia il design corretto? C'è un design migliore di questo?

    
posta Aadarsh Sharma 13.11.2017 - 03:36
fonte

2 risposte

3

Che dire se il co_ordinate struct contiene un'altra informazione - in quale fase viene aggiunta? Quindi sarebbe simile a questo:

typedef enum Stage {
    Stage_Original = 0,
    Stage_Boundary,
    Stage_Internal
} Stage;

struct co_ordinate {
    double x;
    double y;
    Stage stage;
};

Quindi memorizzi tutti i punti come faresti normalmente per la fase 3. Quando vuoi ottenere una serie di punti, devi specificare quali punti vuoi e una nuova vector viene costruita al volo da quelli esistenti ?

Questo porta a un trade-off, che solo tu puoi decidere è ragionevole. Se la prestazione è un problema e la memoria no, puoi memorizzare il 3% divectors, uno che contiene tutti i punti della fase 3, uno che fa riferimento ai punti della fase 3 che sono stati creati per la fase 2 e uno che fa riferimento ai punti dalla fase 3 che erano i punti originali, proprio come il design 1. Ma se la memoria è un problema, allora consiglio la soluzione di cui sopra, che immagino sia simile al design 2.

Si noti che in quanto sopra cito riferimenti. Il co_ordinate struct come lo hai definito è 16 byte. Come ho definito, è probabilmente 18-20 byte. Se i tuoi riferimenti sono solo indici, potrebbero essere unsigned short s o unsigned int s indicizzazione nel vettore stage 3. Pertanto sarebbero solo 2-4 byte ciascuno. (Supponendo che tu non abbia più di 4 miliardi% dico_ordinates nel vettore stage 3). Quindi questo è come il tuo progetto 1, ma un po 'più efficiente dal punto di vista della memoria.

    
risposta data 13.11.2017 - 05:30
fonte
1

Se più tipesafety non ti è problematico, ti suggerisco di risolvere questo problema:

#include <iostream>
#include <vector>
#include <tuple>

struct Coords
{
    double x;
    double y;
};

enum class LandStages
{
    // some descriptive names, i will use StageN for simplicity
    Stage1,
    Stage2,
    Stage3
};

template <LandStages StageN>
class Land;

class LandBase
{
public:
    LandBase() = default;
    LandBase(std::vector<Coords> coords) :
        m_coords(std::move(coords))
    {
    }

    const std::vector<Coords>& coords() const
    {
        return m_coords;
    }
protected:
    std::vector<Coords> m_coords;
};

template <>
class Land<LandStages::Stage3> : public LandBase
{
public:
    using LandBase::LandBase;
};

template <>
class Land<LandStages::Stage2> : public LandBase
{
public:
    using LandBase::LandBase;

    Land<LandStages::Stage3> nextStage() const 
    {
        // simple doubleing of the vector, just to show that something is being done
        std::vector<Coords> next;
        next.reserve(m_coords.size()*2);
        for(const auto& c : m_coords)
        {
            next.emplace_back(c);
            next.emplace_back(c);
        }
        return {std::move(next)};
    }
};

template <>
class Land<LandStages::Stage1> : public LandBase
{
public:
    using LandBase::LandBase;

    Land<LandStages::Stage2> nextStage() const 
    {
        std::vector<Coords> next;
        next.reserve(m_coords.size()*2);
        for(const auto& c : m_coords)
        {
            next.emplace_back(c);
            next.emplace_back(c);
        }
        return {std::move(next)};
    }
};

template <class LandT>
void printLand(const LandT& land)
{
    for(const auto& p : land.coords())
    {
        std::cout << '(' << p.x << ", " << p.y << ") ";
    }
    std::cout << '\n';
}

template <LandStages... Stages>
class LandProcess
{
    template <size_t First, size_t Second, size_t... Tail>
    void init(std::index_sequence<First, Second, Tail...>)
    {
        std::get<Second>(m_stages) = std::get<First>(m_stages).nextStage();
        init(std::integer_sequence<size_t, Second, Tail...>{});
    }

    template <size_t... Tail>
    void init(std::index_sequence<Tail...>)
    {
    }
public:

    LandProcess(std::vector<Coords> coords)
    {
        std::get<0>(m_stages) = decltype(std::get<0>(m_stages))(coords);
        init(std::make_index_sequence<sizeof...(Stages)>{});
    }

    template <LandStages N>
    const Land<N>& getStage() const
    {
        return std::get<Land<N>>(m_stages);
    }

private:

    std::tuple<Land<Stages>...> m_stages;
};
using FullLandProcess = LandProcess<LandStages::Stage1, LandStages::Stage2, LandStages::Stage3>;

int main()
{
    FullLandProcess process({{0, 0}, {1, 0}, {1, 1}, {0, 1}});
    printLand(process.getStage<LandStages::Stage1>());
    printLand(process.getStage<LandStages::Stage2>());
    printLand(process.getStage<LandStages::Stage3>());
}

demo: link

Fondamentalmente funziona avendo ogni passo come un tipo separato (quindi non devi preoccuparti di fare la stessa routine due volte, ad esempio, a patto che tu assicuri che l'input sia valido). La maggior parte del codice è divertente per creare una classe ragionevole per rappresentare l'intero processo (che in questo caso è abbastanza facilmente personalizzabile, puoi includere solo alcune fasi)

Questa soluzione, pur essendo più sicura, potrebbe essere più problematica per te se desideri richiedere in modo dinamico stadi specifici (richiederebbe un'istruzione condizionale e possibilmente un polimorfismo di runtime) o la tua pipeline di stage è più complicata.

Inoltre, contrariamente al metodo presentato dall'utente 1118321, ciò richiede la copia di punti identici (ma può essere fatto non per, anche se complicherebbe il codice).

    
risposta data 13.11.2017 - 12:06
fonte

Leggi altre domande sui tag