Apakah saya mencoba memisahkan kelas terlalu banyak di sini?

8

Saya sedang mengerjakan mesin rendering medan real-time. Saya memiliki kelas QuadTree dan Node. Kelas QuadTree memperluas / memecah pohon tergantung di mana kamera berada. Jadi masuk akal bahwa QuadTree bertanggung jawab atas objek Node seumur hidup. Masalahnya adalah bahwa ada banyak data yang perlu dikaitkan dengan masa pakai itu dan tidak ada hubungannya dengan QuadTree. Data-data ini bahkan mungkin tidak saling terkait. Saya telah mencari cara yang bersih untuk memisahkan kelas saya dengan benar tanpa hasil. Setiap perubahan (kadang-kadang bahkan yang kecil) memerlukan perubahan melalui beberapa file, sebagian besar waktu dengan file yang tidak terkait dengannya. Saya akhirnya memiliki sesuatu yang tampaknya berhasil, tetapi saya pikir saya telah mencoba untuk memisahkannya terlalu banyak, yang meningkatkan kompleksitas untuk tidak banyak manfaat. Beginilah cara saya melakukannya:

Kelas QuadTree saya seharusnya tidak berurusan dengan apa pun kecuali pohon. Tetapi setiap kali saya membuat Node, saya harus mengaitkan data dengan node ini. Karena ini akan mencemari kelas untuk melakukannya di kelas QuadTree, saya menambahkan antarmuka untuk berkomunikasi antara QuadTree dan kelas yang tugasnya membuat data ini. Pada titik ini, saya pikir saya telah melakukannya dengan cara yang benar. Kode semu:

class QTInterface
{
    virtual void nodeCreated( Node& node ) = 0;
    virtual void nodeDestroyed( Node& node ) = 0;
};

class QuadTree
{
public:
    QuadTree( ...., QTInterface& i ) : i( i ) {}

    void update( Camera camera )
    {
        // ....
        i.nodeCreated( node );
        // ....
        i.nodeDestroyed( node );
    }

private:
    QTInterface& i;
    Node root;
};

Sekarang saya perlu mengaitkan beberapa data acak ke masing-masing Node ini. Jadi di kelas saya yang mengimplementasikan QTInterface, saya memiliki peta yang melakukan hal itu:

class Terrain : public QTInterface
{
    void nodeCreated( Node node )
    {
        Data data;
        // ... create all the data associated to this node
        map[ node ] = data
        // One more thing, The QuadTree actually needs one field of Data to continue, so I fill it there
        node.xxx = data.xxx
    }

    void nodeDestroyed( Node node )
    {
        // ... destroy all the data associated to this node
        map.erase( node );
    }

};

Node dan QuadTree sekarang independen dari bagian lain dari kode dan jika saya harus kembali ke sana, itu hanya karena saya harus mengubah sesuatu dalam algoritma QuadTree.

Tapi di situlah saya pikir saya memiliki masalah pertama saya. Sebagian besar waktu saya tidak khawatir tentang optimasi sampai saya melihatnya, tetapi saya pikir jika saya harus menambahkan overhead semacam ini untuk memisahkan kelas saya dengan benar, itu karena desainnya memiliki kekurangan.

Masalah lain dengan ini, adalah bahwa data yang terkait dengan node akan berakhir dengan sekumpulan banyak data. Untuk hasil yang sama dengan sedikit rasa sakit saya bisa saja menggunakan kelas Node sebagai tas.

Jadi, beberapa pertanyaan di sini:

  • Apakah saya terlalu rumit? Haruskah saya memperpanjang kelas Node, menjadikannya sekumpulan data sebelum digunakan oleh beberapa kelas?

  • Jika tidak, seberapa baik alternatif saya? Apakah ada cara yang lebih baik?

  • Saya selalu mengalami kesulitan dengan cara memisahkan kelas saya dengan benar. Adakah saran yang bisa saya berikan nanti? (Seperti pertanyaan apa yang harus saya tanyakan pada diri saya misalnya, atau bagaimana Anda memproses? Memikirkan hal ini di atas kertas tampak sangat abstrak bagi saya, dan segera kode sesuatu menghasilkan hasil refactoring kemudian)

Catatan: Saya mencoba menyederhanakan masalah sebanyak yang saya bisa untuk menghindari pertanyaan yang sangat panjang yang diisi dengan rincian yang tidak perlu. Semoga saya tidak menghilangkan yang penting.

Sunting: Beberapa detail telah ditanyakan:

Kamera tidak hanya dapat memilih node yang terlihat karena itu berarti saya harus menyimpan semua node dalam memori, yang tidak mungkin karena mesin seharusnya membuat medan yang sangat besar dengan resolusi tinggi. Kedalaman pohon akan dengan mudah menjadi 25+. Selain itu juga lebih mudah untuk mengetahui kapan node baru telah dibuat / dihancurkan (pada dasarnya lebih mudah: jika node tidak memiliki anak dan kedalamannya tidak 0, itu karena node perlu dibuat, jika node memiliki anak-anak dan algoritma berhenti di sana, itu berarti mereka terlihat frame sebelumnya tetapi tidak sekarang jadi saya harus menghapusnya, dan data terikat padanya).

Contoh data yang perlu dihitung adalah tinggi dan normal dari node-node ini ( https://en.wikipedia.org/wiki/Heightmap dan https://en.wikipedia.org/wiki/Normal_mapping ).

Membuat data ini melibatkan:

  • mengirim data Node yang dihitung oleh QuadTree ke workqueue multithreaded
  • Setelah heightmap dibuat, perbarui satu-satunya bidang Node yang perlu QuadTree melanjutkan algoritma: Tinggi min / maks.
  • Setelah itu, perbarui tekstur GPU menggunakan heightmap dan normalmap yang dihitung pada CPU.

Tapi ini hanya cara dia menghitung data. Saya juga bisa melakukannya di GPU, dan itu akan membutuhkan langkah-langkah yang sangat berbeda. Dan itulah alasan saya ingin memisahkannya dari kelas QuadTree, karena saya ingin bertukar di antara keduanya dengan mudah (untuk tujuan pengujian) tanpa harus memperbaiki semua kode saya. kopling desain

Aulaulz
sumber
1
Mengapa Anda membuat dan menghancurkan node setiap saat? Tidakkah kamera hanya memilih simpul pohon mana yang harus ditampilkan? Bisakah Anda memberikan contoh konkret dari data yang ingin Anda tambahkan?
null
Saya telah mengedit posting saya untuk menjawab pertanyaan Anda.
Aulaulz

Jawaban:

2

Untuk secara dinamis mengaitkan dan memisahkan data dengan cepat terlepas dari masa node QT sedangkan QT yang dikombinasikan dengan kamera memiliki pengetahuan tentang kapan data harus dikaitkan / dipisahkan dengan cepat, itu agak sulit untuk digeneralisasi dan saya pikir solusi Anda adalah sebenarnya tidak buruk. Itu hal yang sulit untuk didisain dengan cara yang sangat bagus dan umum. seperti, "uhh .... uji dengan baik dan kirimkan!" Oke, sedikit lelucon. Saya akan mencoba menawarkan beberapa pemikiran untuk dijelajahi. Salah satu hal yang paling mencolok bagi saya adalah di sini:

void nodeCreated(Node& node)
{
    ...
    // One more thing, The QuadTree actually needs one field of 
    // Data to continue, so I fill it there
    node.xxx = data.xxx
}

Ini memberitahu saya bahwa simpul ref / pointer tidak hanya digunakan sebagai kunci ke wadah asosiatif eksternal. Anda sebenarnya sedang mengakses dan memodifikasi internal node quadtree di luar quadtree itu sendiri. Dan harus ada cara yang cukup mudah untuk setidaknya menghindari itu sebagai permulaan. Jika itu satu-satunya tempat Anda memodifikasi simpul internal di luar quadtree, maka Anda mungkin dapat melakukan ini (katakanlah xxxadalah pasangan pelampung):

std::pair<float, float> nodeCreated(const Node& node)
{
    Data data;
    ...
    map[&node] = data;
    ...
    return data.xxx;
}

Pada titik mana quadtree dapat menggunakan nilai pengembalian fungsi ini untuk menetapkan xxx. Itu sudah melonggarkan kopling sedikit ketika Anda tidak lagi mengakses internal simpul pohon di luar pohon.

Menghilangkan kebutuhan untuk Terrainmengakses internal quadtree sebenarnya akan menghilangkan satu-satunya tempat di mana Anda menggabungkan hal-hal yang sangat tidak perlu. Ini adalah satu-satunya PITA nyata jika Anda menukar dengan implementasi GPU, misalnya, karena implementasi GPU mungkin menggunakan rep internal yang sama sekali berbeda untuk node.

Tetapi untuk masalah kinerja Anda, dan di sana saya memiliki lebih banyak pemikiran daripada bagaimana Anda secara maksimal mencapai decoupling dengan hal semacam ini, saya benar-benar akan menyarankan representasi yang sangat berbeda di mana Anda dapat mengubah asosiasi data / disosiasi menjadi operasi waktu konstan yang murah. Agak sulit untuk menjelaskan kepada seseorang yang tidak terbiasa membuat kontainer standar yang memerlukan penempatan baru untuk membangun elemen di tempat dari memori yang terkumpul sehingga saya akan mulai dengan beberapa data:

struct Node
{
    ....
    // Stores an index to the data being associated on the fly
    // or -1 if there's no data associated to the node.
    int32_t data;
};

class Quadtree
{
private:
    // Stores all the data being associated on the fly.
    std::vector<char> data;

    // Stores the size of the data being associated on the fly.
    int32_t type_size;

    // Stores an index to the first free index of data
    // to reclaim or -1 if the free list is empty.
    int32_t free_index;

    ...

public:
    // Creates a quadtree with the specified type size for the
    // data associated and disassociated on the fly.
    explicit Quadtree(int32_t itype_size): type_size(itype_size), free_data(-1)
    {
        // Make sure our data type size is at least the size of an integer
        // as required for the free list.
        if (type_size < sizeof(int32_t))
            type_size = sizeof(int32_t);
    }

    // Inserts a buffer to store a data element and returns an index
    // to that.
    int32_t alloc_data()
    {
        int32_t index = free_index;
        if (free_index != -1)
        {
            // If a free index is available, pop it off the
            // free list (stack) and return that.
            void* mem = data.data() + index * type_size;
            free_index = *static_cast<int*>mem;
        }
        else
        {
            // Otherwise insert the buffer for the data
            // and return an index to that.
            index = data.size() / type_size;
            data.resize(data.size() + type_size);
        }
        return index;
    }

    // Frees the memory for the nth data element. 
    void free_data(int32_t n)
    {
        // Push the nth index to the free list to make
        // it available for use in subsequent insertions.
        void* mem = data.data() + n * type_size;
        *static_cast<int*>(mem) = free_index;
        free_index = n;
    }

    ...
};

Pada dasarnya itu adalah "daftar gratis yang diindeks". Tetapi ketika Anda menggunakan perwakilan ini untuk data terkait, Anda dapat melakukan sesuatu seperti ini:

class QTInterface
{
    virtual std::pair<float, float> createData(void* mem) = 0;
    virtual void destroyData(void* mem) = 0;
};

void Quadtree::update(Camera camera)
{
    ...
    node.data = alloc_data();
    node.xxx = i.createData(data.data() + node.data * type_size);
    ...
    i.destroyData(data.data() + node.data * type_size);
    free_data(node.data);
    node.data = -1;
    ...
}

class Terrain : public QTInterface
{
    // Note that we don't even need access to nodes anymore,
    // not even as keys to use. We've completely decoupled
    // terrains from tree internals.
    std::pair<float, float> createData(void* mem) override
    {
        // Construct the data (placement new) using the memory
        // allocated by the tree.
        Data* data = new(mem) Data(...);

        // Return data to assign to node.xxx.
        return data->xxx;
    }

    void destroyData(void* mem) override
    {
        // Destroy the data.
        static_cast<Data*>(mem)->~Data();
    }
};

Semoga ini semua masuk akal, dan tentu saja ini sedikit lebih terpisah dari desain asli Anda karena tidak mengharuskan klien untuk memiliki akses internal ke bidang simpul pohon (sekarang bahkan tidak lagi memerlukan pengetahuan tentang node apa pun, bahkan tidak digunakan sebagai kunci ), dan itu jauh lebih efisien karena Anda dapat mengaitkan dan memisahkan data ke / dari node dalam waktu-konstan (dan tanpa menggunakan tabel hash yang akan menyiratkan konstanta yang jauh lebih besar). Saya berharap data Anda dapat disejajarkan menggunakan max_align_t(tidak ada bidang SIMD, mis.) Dan dapat disalin secara sepele, jika tidak semuanya menjadi jauh lebih rumit karena kita akan membutuhkan pengalokasi selaras dan mungkin harus menggulung wadah daftar gratis kami sendiri. Nah, jika Anda hanya memiliki tipe yang tidak dapat disalin sepele dan tidak perlu lebih dari itumax_align_t, kita dapat menggunakan implementasi penunjuk daftar gratis yang mengumpulkan dan menautkan node yang tidak dikontrol untuk menyimpan Kelemen data masing-masing untuk menghindari keharusan merealokasi blok memori yang ada. Saya dapat menunjukkan bahwa jika Anda membutuhkan alternatif seperti itu.

Ini sedikit maju dan sangat spesifik C ++, mengingat ide mengalokasikan dan membebaskan memori untuk elemen sebagai tugas terpisah dari membangun dan menghancurkan mereka. Tetapi jika Anda melakukannya dengan cara ini, Terrainmenyerap tanggung jawab minimum dan tidak memerlukan pengetahuan internal tentang representasi pohon apa pun lagi, bahkan tidak menangani node yang buram. Namun tingkat kontrol memori ini biasanya yang Anda butuhkan jika Anda ingin merancang struktur data yang paling efisien.

Gagasan mendasar di sana adalah bahwa Anda memiliki klien menggunakan tree pass dalam ukuran tipe data yang ingin mereka kaitkan / pisahkan saat terbang ke quadtree ctor. Kemudian quadtree memiliki tanggung jawab mengalokasikan dan membebaskan memori menggunakan ukuran tipe itu. Kemudian meneruskan tanggung jawab membangun dan menghancurkan data kepada klien menggunakan QTInterfacedan pengiriman dinamis. Oleh karena itu, satu-satunya tanggung jawab di luar pohon yang masih berhubungan dengan pohon itu, adalah membangun dan menghancurkan elemen-elemen dari ingatan yang dialokasikan oleh kuadrat dan mendelokasikannya sendiri. Pada saat itu dependensi menjadi seperti ini:

masukkan deskripsi gambar di sini

Yang sangat masuk akal mengingat kesulitan apa yang Anda lakukan dan skala input. Pada dasarnya Anda Terrainhanya bergantung pada Quadtreedan QTInterface, dan tidak lagi internal quadtree atau node-nya. Sebelumnya Anda punya ini:

masukkan deskripsi gambar di sini

Dan tentu saja masalah melotot dengan itu, terutama jika Anda sedang mempertimbangkan mencoba implementasi GPU, adalah bahwa ketergantungan dari Terrainke Node, sejak pelaksanaan GPU kemungkinan akan ingin menggunakan rep simpul yang sangat berbeda. Tentu saja jika Anda ingin menjadi SOLID hardcore, Anda akan melakukan sesuatu seperti ini:

masukkan deskripsi gambar di sini

... bersama dengan mungkin sebuah pabrik. Tetapi IMO yang total berlebihan (paling INodetidak total total IMO) dan tidak akan sangat membantu dalam kasus granular seperti fungsi quadtree jika masing-masing memerlukan pengiriman dinamis.

Saya selalu mengalami kesulitan dengan cara memisahkan kelas saya dengan benar. Adakah saran yang bisa saya berikan nanti? (Seperti pertanyaan apa yang harus saya tanyakan pada diri saya misalnya, atau bagaimana Anda memproses? Memikirkan hal ini di atas kertas tampak sangat abstrak bagi saya, dan segera kode sesuatu menghasilkan hasil refactoring kemudian)

Secara umum dan kasar, decoupling sering bermuara pada membatasi jumlah informasi yang dibutuhkan kelas atau fungsi tertentu tentang sesuatu yang lain untuk melakukan hal tersebut.

Saya berasumsi Anda menggunakan C ++ karena tidak ada bahasa lain yang saya tahu memiliki sintaks yang tepat, dan di C ++ mekanisme decoupling yang sangat efektif untuk struktur data adalah template kelas dengan polimorfisme statis jika Anda dapat menggunakannya. Jika Anda mempertimbangkan wadah standar seperti std::vector<T, Alloc>, vektor tidak digabungkan dengan apa pun yang Anda tentukan untuk Tapa pun. Ini hanya membutuhkan yang Tmemenuhi beberapa persyaratan antarmuka dasar seperti itu copy-constructible dan memiliki konstruktor default untuk konstruktor isian dan isian ukuran. Dan itu tidak akan memerlukan perubahan sebagai hasil dari Tperubahan.

Jadi, dengan mengaitkannya dengan hal di atas, ini memungkinkan struktur data untuk diimplementasikan menggunakan pengetahuan minimal yang absolut tentang apa yang dikandungnya, dan yang memisahkannya sejauh tidak memerlukan informasi jenis apa pun di muka (terlebih dahulu ada berbicara dalam hal dependensi kode / kopling, bukan informasi waktu kompilasi) tentang apa Titu.

Cara paling praktis kedua untuk meminimalkan jumlah informasi yang diperlukan adalah dengan menggunakan polimorfisme dinamis. Misalnya, jika Anda ingin menerapkan struktur data yang cukup umum yang meminimalkan pengetahuan tentang apa yang disimpannya, maka Anda mungkin menangkap persyaratan antarmuka untuk apa yang disimpan dalam satu atau lebih antarmuka:

// Contains all the functions (pure virtual) required of the elements 
// stored in the container.
class IElement {...};

Tapi bagaimanapun juga intinya untuk meminimalkan jumlah informasi yang Anda butuhkan di muka dengan pengkodean ke antarmuka daripada ke detail nyata. Di sini satu-satunya hal besar yang Anda lakukan yang tampaknya memerlukan lebih banyak informasi daripada yang diperlukan adalah bahwa Anda Terrainharus memiliki informasi lengkap tentang internal dari simpul Quadtree, misalnya Dalam kasus seperti itu, dengan asumsi satu-satunya alasan yang Anda butuhkan yaitu untuk menetapkan sepotong data ke sebuah simpul, kita dapat dengan mudah menghilangkan ketergantungan itu ke internal simpul pohon dengan hanya mengembalikan data yang harus ditugaskan ke simpul dalam abstrak itu QTInterface.

Jadi jika saya ingin memisahkan sesuatu, saya hanya fokus pada apa yang perlu dilakukan dan membuat antarmuka untuk itu (baik secara eksplisit menggunakan warisan atau implisit menggunakan polimorfisme statis dan pengetikan bebek). Dan Anda sudah melakukan itu sampai batas tertentu dari quadtree itu sendiri menggunakan QTInterfaceuntuk memungkinkan klien untuk menimpa fungsinya dengan subtipe dan memberikan rincian konkret yang diperlukan untuk quadtree untuk melakukan hal itu. Satu-satunya tempat di mana saya pikir Anda gagal adalah bahwa klien masih memerlukan akses ke internal quadtree. Anda dapat menghindari itu dengan meningkatkan apa QTInterfaceyang tepatnya saya sarankan ketika saya membuatnya mengembalikan nilai untuk ditugaskannode.xxxdalam implementasi quadtree itu sendiri. Jadi itu hanya masalah membuat hal-hal yang lebih abstrak dan antarmuka lebih lengkap sehingga hal-hal tidak memerlukan informasi yang tidak perlu tentang satu sama lain.

Dan dengan menghindari informasi yang tidak perlu ( Terrainharus tahu tentang Quadtreesimpul internal), Anda sekarang lebih bebas untuk menukar Quadtreedengan implementasi GPU, misalnya, tanpa mengubah Terrainimplementasinya juga. Hal-hal yang tidak diketahui satu sama lain adalah bebas untuk berubah tanpa mempengaruhi satu sama lain. Jika Anda benar-benar ingin menukar keluar implementasi GPU quadtree dari yang CPU, Anda mungkin pergi sedikit ke arah rute SOLID di atas denganIQuadtree(membuat quadtree itu sendiri abstrak). Itu datang dengan hit pengiriman dinamis yang mungkin agak mahal dengan kedalaman pohon dan ukuran input yang Anda bicarakan. Jika tidak, setidaknya itu memerlukan jauh lebih sedikit perubahan pada kode jika hal-hal yang menggunakan quadtree tidak perlu tahu tentang representasi simpul internal untuk bekerja. Anda mungkin dapat menukar satu dengan yang lain hanya memperbarui satu baris kode untuk typedef, misalnya, bahkan jika Anda tidak menggunakan antarmuka abstrak ( IQuadtree).

Tapi di situlah saya pikir saya memiliki masalah pertama saya. Sebagian besar waktu saya tidak khawatir tentang optimasi sampai saya melihatnya, tetapi saya pikir jika saya harus menambahkan overhead semacam ini untuk memisahkan kelas saya dengan benar, itu karena desainnya memiliki kekurangan.

Belum tentu. Decoupling seringkali menyiratkan pergeseran ketergantungan dari beton ke abstrak. Abstraksi cenderung menyiratkan penalti runtime kecuali jika kompiler menghasilkan kode pada waktu kompilasi untuk pada dasarnya menghilangkan biaya abstraksi saat runtime. Sebagai gantinya Anda mendapatkan lebih banyak ruang bernapas untuk membuat perubahan tanpa mempengaruhi hal-hal lain, tetapi itu sering kali mengekstraksi semacam penalti kinerja kecuali jika Anda menggunakan pembuatan kode.

Sekarang Anda dapat menghilangkan kebutuhan untuk struktur data asosiatif non-sepele (peta / kamus, yaitu) untuk mengaitkan data ke node (atau apa pun) dengan cepat. Dalam kasus di atas saya baru saja membuat node langsung menyimpan indeks ke data yang akan dialokasikan / dibebaskan dengan cepat. Melakukan hal-hal semacam ini tidak banyak terkait dengan mempelajari cara memisahkan hal-hal secara efektif, seperti bagaimana memanfaatkan tata letak memori untuk struktur data secara efektif (lebih banyak di bidang optimisasi murni).

Prinsip dan kinerja SE yang efektif bertentangan satu sama lain pada tingkat yang cukup rendah. Seringkali decoupling akan memecah tata letak memori terpisah untuk bidang yang biasa diakses bersama-sama, mungkin melibatkan lebih banyak alokasi tumpukan, mungkin melibatkan pengiriman yang lebih dinamis, dll. Ini menjadi cepat diremehkan saat Anda bekerja menuju kode tingkat yang lebih tinggi (mis: operasi yang diterapkan ke seluruh gambar, tidak per -pixel operasi ketika perulangan melalui piksel individu), tetapi memang memiliki biaya yang berkisar dari sepele hingga parah tergantung pada berapa banyak biaya yang dikeluarkan dalam kode Anda yang paling kritis, gila seperti melakukan pekerjaan paling ringan di setiap iterasi.

Apakah saya terlalu rumit? Haruskah saya memperpanjang kelas Node, menjadikannya sekumpulan data sebelum digunakan oleh beberapa kelas?

Saya pribadi tidak berpikir itu sangat buruk jika Anda tidak mencoba untuk menggeneralisasi struktur data Anda terlalu banyak, hanya menggunakannya dalam konteks yang sangat terbatas, dan Anda sedang berhadapan dengan konteks yang sangat kritis terhadap kinerja untuk jenis masalah yang Anda alami sebelumnya sudah ditangani. Dalam hal ini Anda akan mengubah quadtree Anda menjadi detail implementasi medan Anda, misalnya, daripada sesuatu yang akan digunakan secara luas dan umum, dengan cara yang sama seseorang mungkin mengubah octree menjadi detail implementasi mesin fisika mereka dengan tidak lagi membedakan ide "antarmuka publik" dari "internal". Mempertahankan invarian yang terkait dengan indeks spasial kemudian berubah menjadi tanggung jawab kelas yang menggunakannya sebagai detail implementasi pribadi.

Untuk merancang abstraksi yang efektif (antarmuka, yaitu) dalam konteks kinerja-kritis sering mengharuskan Anda untuk memahami sebagian besar masalah dan solusi yang sangat efektif untuk itu di muka. Ini benar-benar dapat berubah menjadi ukuran kontra-produktif untuk mencoba menggeneralisasi dan mengabstraksi solusi sambil secara bersamaan mencoba untuk mencari tahu desain yang efektif melalui beberapa iterasi. Salah satu alasannya adalah bahwa konteks kritis kinerja memerlukan representasi data yang sangat efisien dan pola akses. Abstraksi menempatkan penghalang antara kode yang ingin mengakses data: penghalang yang berguna jika Anda ingin data bebas berubah tanpa memengaruhi kode tersebut, tetapi halangan jika Anda secara bersamaan mencoba mencari cara paling efektif untuk mewakili dan akses data tersebut di tempat pertama.

Tetapi jika Anda melakukannya dengan cara ini, sekali lagi saya akan melakukan kesalahan dengan mengubah quadtree menjadi detail implementasi pribadi dari medan Anda, bukan sesuatu untuk digeneralisasi dan digunakan di luar implementasi mereka. Dan Anda harus meninggalkan ide untuk dapat dengan mudah menukar implementasi GPU dari implementasi CPU, karena itu biasanya akan memerlukan datang dengan abstraksi yang bekerja untuk keduanya dan tidak secara langsung tergantung pada detail konkret (seperti repetisi node) dari keduanya.

Point of Decoupling

Tetapi mungkin dalam beberapa kasus ini bahkan dapat diterima untuk hal-hal yang lebih umum digunakan. Sebelum orang-orang berpikir saya mengomel omong kosong, pertimbangkan antarmuka gambar. Berapa banyak dari mereka yang cukup untuk prosesor video yang perlu menerapkan filter gambar pada video secara realtime jika gambar tidak mengekspos internalnya (akses langsung ke array piksel yang mendasarinya dalam format piksel tertentu)? Tidak ada yang saya tahu menggunakan sesuatu seperti abstrak / virtual di getPixelsini dansetPixeldi sana sambil melakukan konversi format piksel berdasarkan per-piksel. Jadi dalam konteks kinerja-kritis yang cukup di mana Anda harus mengakses hal-hal pada tingkat yang sangat granular (per-pixel, per-node, dll), Anda mungkin kadang-kadang harus mengekspos internal struktur yang mendasarinya. Tetapi akibatnya Anda harus memasangkan beberapa hal dengan erat, dan tidak mudah mengubah representasi gambar yang mendasarinya (misalnya, perubahan dalam format gambar), sehingga tidak mempengaruhi segala sesuatu yang mengakses piksel yang mendasarinya. Tetapi mungkin ada lebih sedikit alasan untuk berubah dalam kasus itu, karena mungkin sebenarnya lebih mudah untuk menstabilkan representasi data daripada antarmuka abstrak. Prosesor video mungkin dapat menerima gagasan menggunakan format piksel RGBA 32-bit dan bahwa keputusan desain mungkin tidak berubah untuk tahun-tahun mendatang.

Idealnya Anda ingin dependensi mengalir menuju stabilitas (hal-hal yang tidak berubah) karena mengubah sesuatu yang memiliki banyak dependensi akan berlipat ganda dalam biaya dengan jumlah dependensi. Itu mungkin atau mungkin bukan abstraksi dalam semua kasus. Tentu saja itu mengabaikan manfaat menyembunyikan informasi dalam mempertahankan invarian, tetapi dari sudut pandang penggandengan, titik utama decoupling adalah untuk membuat hal-hal menjadi lebih murah untuk berubah. Itu berarti mengalihkan ketergantungan dari hal-hal yang dapat berubah menjadi hal-hal yang tidak akan berubah, dan itu tidak membantu sedikit pun jika antarmuka abstrak Anda adalah bagian yang paling cepat berubah dari struktur data Anda.

Jika Anda ingin setidaknya meningkatkan sedikit dari perspektif penggandengan, maka pisahkan bagian simpul Anda yang harus diakses klien dari bagian yang tidak. Saya berasumsi klien setidaknya tidak perlu memperbarui tautan node, jadi tidak perlu membuka tautan. Anda setidaknya harus dapat membuat beberapa nilai agregat yang terpisah dari keseluruhan apa yang diwakili oleh node untuk diakses / dimodifikasi oleh klien NodeValue.


sumber
3

Membaca yang tersirat, sepertinya Anda terlalu fokus pada tampilan pohon Anda. Ide Anda tampaknya adalah "Saya memiliki pohon ini, dengan simpul, yang saya lampirkan objek dan pohon harus memberi tahu objek apa yang harus dilakukan". Namun harus sebaliknya. Bagaimanapun, pohon itu hanyalah tampilan yang harus mengikuti objek (domain bermasalah) Anda. Benda-benda tersebut seharusnya tidak memiliki pengetahuan / jejak pohon (node). Ini adalah pandangan yang membaca objek dan menampilkan dirinya sesuai.

Anda mungkin ingin menerapkan beberapa peristiwa pada objek yang dapat dilanggan pohonnya, sehingga ia tahu kapan harus menutup, memperluas, membuat atau menghapus simpul.

Jadi, biarkan pohonnya mengikuti model Anda.

Martin Maat
sumber