Setelah melakukan beberapa penelitian, saya tidak dapat menemukan contoh sederhana untuk menyelesaikan masalah yang sering saya temui.
Katakanlah saya ingin membuat aplikasi kecil di mana saya dapat membuat Square
s, Circle
s, dan bentuk lainnya, menampilkannya di layar, memodifikasi properti mereka setelah memilihnya, dan kemudian menghitung semua perimeter mereka.
Saya akan melakukan kelas model seperti ini:
class AbstractShape
{
public :
typedef enum{
SQUARE = 0,
CIRCLE,
} SHAPE_TYPE;
AbstractShape(SHAPE_TYPE type):m_type(type){}
virtual ~AbstractShape();
virtual float computePerimeter() const = 0;
SHAPE_TYPE getType() const{return m_type;}
protected :
const SHAPE_TYPE m_type;
};
class Square : public AbstractShape
{
public:
Square():AbstractShape(SQUARE){}
~Square();
void setWidth(float w){m_width = w;}
float getWidth() const{return m_width;}
float computePerimeter() const{
return m_width*4;
}
private :
float m_width;
};
class Circle : public AbstractShape
{
public:
Circle():AbstractShape(CIRCLE){}
~Circle();
void setRadius(float w){m_radius = w;}
float getRadius() const{return m_radius;}
float computePerimeter() const{
return 2*M_PI*m_radius;
}
private :
float m_radius;
};
(Bayangkan saya memiliki lebih banyak kelas bentuk: segitiga, segi enam, dengan setiap kali variabel penekan mereka dan getter dan setter terkait. Masalah yang saya hadapi memiliki 8 subclass tetapi demi contoh saya berhenti di 2)
Sekarang saya punya ShapeManager
, instantiating dan menyimpan semua bentuk dalam array:
class ShapeManager
{
public:
ShapeManager();
~ShapeManager();
void addShape(AbstractShape* shape){
m_shapes.push_back(shape);
}
float computeShapePerimeter(int shapeIndex){
return m_shapes[shapeIndex]->computePerimeter();
}
private :
std::vector<AbstractShape*> m_shapes;
};
Akhirnya, saya memiliki pandangan dengan spinbox untuk mengubah setiap parameter untuk setiap jenis bentuk. Sebagai contoh, ketika saya memilih kotak di layar, widget parameter hanya menampilkan Square
parameter terkait (terima kasih AbstractShape::getType()
) dan mengusulkan untuk mengubah lebar kotak. Untuk melakukan itu saya memerlukan fungsi yang memungkinkan saya untuk memodifikasi lebar ShapeManager
, dan ini adalah bagaimana saya melakukannya:
void ShapeManager::changeSquareWidth(int shapeIndex, float width){
Square* square = dynamic_cast<Square*>(m_shapes[shapeIndex]);
assert(square);
square->setWidth(width);
}
Apakah ada desain yang lebih baik yang menghindari saya untuk menggunakan dynamic_cast
dan mengimplementasikan pasangan pengambil / penyetel ShapeManager
untuk setiap variabel subkelas yang mungkin saya miliki? Saya sudah mencoba menggunakan template tetapi gagal .
Masalah yang dihadapi saya adalah tidak benar-benar dengan Shapes tetapi dengan berbeda Job
s untuk printer 3D (ex: PrintPatternInZoneJob
, TakePhotoOfZone
, dll) dengan AbstractJob
sebagai kelas dasar mereka. Metode virtual adalah execute()
dan tidak getPerimeter()
. Satu-satunya waktu saya perlu menggunakan penggunaan konkret adalah mengisi informasi spesifik yang dibutuhkan pekerjaan :
PrintPatternInZone
perlu daftar titik untuk dicetak, posisi zona, beberapa parameter pencetakan seperti suhuTakePhotoOfZone
membutuhkan zona apa untuk mengambil foto, jalur di mana foto akan disimpan, dimensi, dll ...
Ketika saya akan menelepon execute()
, Jobs akan menggunakan informasi spesifik yang mereka miliki untuk menyadari tindakan yang seharusnya mereka lakukan.
Satu-satunya waktu saya perlu menggunakan jenis konkret Pekerjaan adalah ketika saya mengisi atau menampilkan informasi tesis ini (jika a TakePhotoOfZone
Job
dipilih, widget yang menampilkan dan memodifikasi parameter zona, jalur, dan dimensi akan ditampilkan).
The Job
s kemudian dimasukkan ke dalam daftar Job
s yang mengambil pekerjaan pertama, mengeksekusinya (dengan memanggil AbstractJob::execute()
), pergi ke yang berikutnya, dan terus sampai akhir daftar. (Inilah sebabnya saya menggunakan warisan).
Untuk menyimpan berbagai jenis parameter, saya menggunakan JsonObject
:
Keuntungan: struktur yang sama untuk pekerjaan apa pun, tidak ada dynamic_cast saat mengatur atau membaca parameter
masalah: tidak dapat menyimpan pointer (ke
Pattern
atauZone
)
Apakah Anda ada cara yang lebih baik untuk menyimpan data?
Lalu bagaimana Anda menyimpan tipe konkritJob
untuk menggunakannya ketika saya harus memodifikasi parameter spesifik dari tipe itu? JobManager
hanya memiliki daftar AbstractJob*
.
sumber
changeValue(int shapeIndex, PropertyKey propkey, double numericalValue)
manaPropertyKey
bisa menjadi enum atau string, dan "Lebar" (yang menandakan bahwa panggilan ke setter akan memperbarui nilai lebar) adalah salah satu dari nilai yang diizinkan.Jawaban:
Saya ingin memperluas "saran lain" Emerson Cardoso karena saya percaya itu adalah pendekatan yang benar dalam kasus umum - meskipun Anda tentu saja dapat menemukan solusi lain yang lebih cocok untuk masalah tertentu.
Masalah
Dalam contoh Anda,
AbstractShape
kelas memilikigetType()
metode yang pada dasarnya mengidentifikasi tipe beton. Ini umumnya merupakan pertanda bahwa Anda tidak memiliki abstraksi yang baik. Inti dari abstraksi, tidak harus peduli dengan detail dari tipe konkret.Juga, jika Anda tidak terbiasa dengan itu, Anda harus membaca tentang Prinsip Terbuka / Tertutup. Ini sering dijelaskan dengan contoh bentuk, sehingga Anda akan merasa seperti di rumah.
Abstraksi yang Berguna
Saya berasumsi Anda telah memperkenalkannya
AbstractShape
karena Anda merasa itu berguna untuk sesuatu. Kemungkinan besar, beberapa bagian dari aplikasi Anda perlu mengetahui perimeter bentuk, terlepas dari apa bentuknya.Ini adalah tempat di mana abstraksi masuk akal. Karena modul ini tidak mementingkan bentuk beton, modul ini
AbstractShape
hanya dapat bergantung pada modul . Untuk alasan yang sama, tidak perlugetType()
metode - jadi Anda harus menyingkirkannya.Bagian lain dari aplikasi hanya akan berfungsi dengan bentuk tertentu, misalnya
Rectangle
. Area-area itu tidak akan mendapat manfaat dariAbstractShape
kelas, jadi Anda tidak boleh menggunakannya di sana. Untuk hanya memberikan bentuk yang benar ke bagian-bagian ini, Anda perlu menyimpan bentuk beton secara terpisah. (Anda dapat menyimpannya sebagaiAbstractShape
tambahan, atau menggabungkannya dengan cepat).Meminimalkan Penggunaan Beton
Tidak ada jalan lain: Anda perlu jenis beton di beberapa tempat - setidaknya selama konstruksi. Namun, kadang-kadang yang terbaik untuk menjaga penggunaan jenis beton terbatas pada beberapa area yang didefinisikan dengan baik. Area terpisah ini memiliki satu-satunya tujuan berurusan dengan berbagai jenis - sementara semua logika aplikasi tidak digunakan.
Bagaimana Anda mencapai ini? Biasanya, dengan memperkenalkan lebih banyak abstraksi - yang mungkin atau mungkin tidak mencerminkan abstraksi yang ada. Misalnya, GUI Anda tidak benar - benar perlu tahu bentuk apa yang dihadapinya. Hanya perlu tahu bahwa ada area di layar tempat pengguna dapat mengedit bentuk.
Jadi, Anda mendefinisikan abstrak
ShapeEditView
yang Anda milikiRectangleEditView
danCircleEditView
implementasi yang menampung kotak teks aktual untuk lebar / tinggi atau radius.Pada langkah pertama, Anda bisa membuat
RectangleEditView
kapan saja Anda membuatRectangle
dan kemudian memasukkannya ke dalamstd::map<AbstractShape*, AbstractShapeView*>
. Jika Anda lebih suka membuat tampilan sesuai kebutuhan, Anda dapat melakukan yang berikut:Either way, kode di luar logika penciptaan ini tidak harus berurusan dengan bentuk konkret. Sebagai bagian dari penghancuran bentuk, Anda harus menghapus pabrik, jelas. Tentu saja, contoh ini terlalu disederhanakan, tapi saya harap idenya jelas.
Memilih Opsi yang tepat
Dalam aplikasi yang sangat sederhana, Anda mungkin menemukan bahwa solusi (casting) kotor hanya memberi Anda hasil maksimal.
Mempertahankan daftar terpisah secara eksplisit untuk setiap jenis beton mungkin merupakan cara yang harus dilakukan jika aplikasi Anda terutama berkaitan dengan bentuk beton, tetapi memiliki beberapa bagian yang bersifat universal. Di sini, masuk akal untuk abstrak sejauh fungsi umum membutuhkannya.
Secara keseluruhan, membayar jika Anda memiliki banyak logika yang beroperasi pada bentuk, dan bentuk yang tepat benar-benar detail untuk aplikasi Anda.
sumber
[rect, rectView]() { rectView.bind(rect); return rectView; }
. Omong-omong, ini tentu saja harus dilakukan dalam modul presentasi, misalnya dalam RectangleCreatedEventHandler.Salah satu pendekatan adalah membuat barang lebih umum untuk menghindari casting ke tipe tertentu .
Anda bisa menerapkan pengambil / penyetel dasar properti float " dimensi " di kelas dasar, yang menetapkan nilai dalam peta, berdasarkan pada kunci spesifik untuk nama properti. Contoh di bawah ini:
Kemudian, di kelas manajer Anda, Anda hanya perlu menerapkan satu fungsi, seperti di bawah ini:
Contoh penggunaan dalam Tampilan:
Saran lain:
Karena manajer Anda hanya mengekspos setter dan perhitungan perimeter (yang diekspos oleh Shape juga), Anda bisa membuat Instantiate tampilan yang tepat ketika Anda instantiate kelas Shape tertentu. MISALNYA:
sumber