Dalam beberapa bulan terakhir saya telah meminta orang-orang di sini di SE dan di situs lain menawarkan saya kritik konstruktif mengenai kode saya. Ada satu hal yang terus bermunculan hampir setiap waktu dan saya masih tidak setuju dengan rekomendasi itu; : P Saya ingin membahasnya di sini dan mungkin segalanya akan menjadi lebih jelas bagi saya.
Ini terkait dengan prinsip tanggung jawab tunggal (SRP). Pada dasarnya, saya memiliki kelas data Font
,, yang tidak hanya menampung fungsi untuk memanipulasi data, tetapi juga untuk memuatnya. Saya diberitahu keduanya harus terpisah, bahwa fungsi pemuatan harus ditempatkan di dalam kelas pabrik; Saya pikir ini adalah interpretasi yang salah dari SRP ...
Sebuah Fragmen dari Kelas Font Saya
class Font
{
public:
bool isLoaded() const;
void loadFromFile(const std::string& file);
void loadFromMemory(const void* buffer, std::size_t size);
void free();
void some();
void another();
};
Desain yang disarankan
class Font
{
public:
void some();
void another();
};
class FontFactory
{
public:
virtual std::unique_ptr<Font> createFromFile(...) = 0;
virtual std::unique_ptr<Font> createFromMemory(...) = 0;
};
Desain yang disarankan seharusnya mengikuti SRP, tapi saya tidak setuju - saya pikir itu terlalu jauh. The Font
kelas tidak lagi mandiri (tidak ada gunanya tanpa pabrik), dan FontFactory
kebutuhan untuk mengetahui rincian tentang pelaksanaan sumber daya, yang mungkin dilakukan melalui persahabatan atau getter publik, yang selanjutnya mengekspos pelaksanaan Font
. Saya pikir ini lebih merupakan kasus tanggung jawab yang terfragmentasi .
Inilah mengapa saya pikir pendekatan saya lebih baik:
Font
mandiri - Menjadi mandiri, lebih mudah dipahami dan dipelihara. Anda juga dapat menggunakan kelas tanpa harus memasukkan yang lain. Namun, jika Anda merasa perlu pengelolaan sumber daya yang lebih kompleks (pabrik), Anda juga dapat melakukannya dengan mudah (nanti saya akan berbicara tentang pabrik saya sendiriResourceManager<Font>
).Mengikuti perpustakaan standar - Saya percaya tipe yang ditentukan pengguna harus berusaha semaksimal mungkin untuk menyalin perilaku tipe standar dalam bahasa masing-masing. The
std::fstream
mandiri dan menyediakan fungsi sepertiopen
danclose
. Mengikuti perpustakaan standar berarti tidak perlu menghabiskan upaya belajar cara lain dalam melakukan sesuatu. Selain itu, secara umum, komite standar C ++ mungkin lebih tahu tentang desain daripada siapa pun di sini, jadi jika pernah ragu, salin apa yang mereka lakukan.Testabilitas - Ada yang salah, di mana masalahnya? - Apakah cara
Font
menangani data atau caraFontFactory
memuat data? Kamu tidak benar-benar tahu. Memiliki kelas mandiri dapat mengurangi masalah ini: Anda dapat mengujiFont
secara terpisah. Jika kemudian Anda harus menguji pabrik dan Anda tahuFont
berfungsi dengan baik, Anda juga akan tahu bahwa setiap kali terjadi masalah pasti ada di dalam pabrik.Ini adalah konteks agnostik - (Ini sedikit bersinggungan dengan poin pertama saya.) Melakukan
Font
hal tersebut dan tidak membuat asumsi tentang bagaimana Anda akan menggunakannya: Anda dapat menggunakannya sesuka Anda. Memaksa pengguna untuk menggunakan pabrik meningkatkan sambungan antar kelas.
Saya juga Punya Pabrik
(Karena desainnya Font
memungkinkan saya untuk.)
Atau lebih tepatnya seorang manajer, bukan sekadar pabrik ... Font
mandiri sehingga manajer tidak perlu tahu bagaimana membangunnya; alih-alih manajer memastikan file atau buffer yang sama tidak dimuat ke memori lebih dari sekali. Anda bisa mengatakan sebuah pabrik dapat melakukan hal yang sama, tetapi bukankah itu akan merusak SRP? Pabrik kemudian tidak hanya harus membangun objek, tetapi juga mengelolanya.
template<class T>
class ResourceManager
{
public:
ResourcePtr<T> acquire(const std::string& file);
ResourcePtr<T> acquire(const void* buffer, std::size_t size);
};
Berikut ini demonstrasi bagaimana manajer dapat digunakan. Perhatikan bahwa itu pada dasarnya digunakan persis seperti pabrik.
void test(ResourceManager<Font>* rm)
{
// The same file isn't loaded twice into memory.
// I can still have as many Fonts using that file as I want, though.
ResourcePtr<Font> font1 = rm->acquire("fonts/arial.ttf");
ResourcePtr<Font> font2 = rm->acquire("fonts/arial.ttf");
// Print something with the two fonts...
}
Intinya ...
(Ini ingin menempatkan tl; dr di sini, tapi saya tidak bisa memikirkannya.: \)
Nah, begitulah, saya sudah membuat kasus saya sebaik mungkin. Silakan memposting argumen balasan yang Anda miliki dan juga keuntungan apa pun yang menurut Anda memiliki desain yang disarankan di atas desain saya sendiri. Pada dasarnya, coba tunjukkan bahwa saya salah. :)
Jawaban:
Tidak ada yang salah dengan kode itu menurut saya, ia melakukan apa yang Anda butuhkan secara masuk akal dan cukup mudah untuk mempertahankannya.
Namun , masalah yang Anda miliki dengan kode ini adalah bahwa jika Anda ingin melakukan hal lain, Anda harus mengubah semuanya .
Inti dari SRP adalah bahwa jika Anda memiliki satu komponen 'CompA' yang melakukan algoritma A () dan Anda perlu mengubah algoritma A () Anda tidak harus mengubah 'CompB' juga.
Keterampilan C ++ saya terlalu berkarat untuk menyarankan skenario yang layak di mana Anda perlu mengubah solusi manajemen font Anda, tetapi kasus yang biasa saya buat adalah ide meluncur di lapisan caching. Idealnya, Anda tidak ingin hal yang memuat banyak hal tahu dari mana asalnya, atau hal yang dimuat tidak peduli dari mana asalnya, karena kemudian membuat perubahan lebih sederhana. Ini semua tentang rawatan.
Salah satu contoh mungkin Anda memuat font Anda dari sumber ketiga (misalnya gambar karakter sprite). Untuk mencapai ini, Anda perlu mengubah loader Anda (untuk memanggil metode ketiga jika dua yang pertama gagal) dan kelas Font itu sendiri untuk mengimplementasikan panggilan ketiga ini. Idealnya, Anda hanya akan membuat pabrik lain (SpriteFontFactory, atau apa pun), menerapkan metode loadFont (...) yang sama dan menempelkannya di daftar pabrik di suatu tempat yang dapat digunakan untuk memuat font.
sumber
Satu hal yang mengganggu saya tentang kelas Anda adalah bahwa Anda memiliki
loadFromMemory
danloadFromFile
metode. Idealnya, Anda hanya memilikiloadFromMemory
metode; sebuah font seharusnya tidak peduli bagaimana data dalam memori muncul. Hal lain adalah Anda harus menggunakan konstruktor / destruktor alih-alih memuat danfree
metode. Dengan demikian,loadFromMemory
akan menjadiFont(const void *buf, int len)
danfree()
akan menjadi~Font()
.sumber