Bir C ++ uygulaması yazıyorum. Çoğu uygulama gerekli veri alıntılarını okur ve yazar ve bu bir istisna değildir. Veri modeli ve serileştirme mantığı için üst düzey bir tasarım oluşturdum. Bu soru, tasarımımın bu belirli hedefler göz önünde bulundurularak incelenmesini istiyor:
Veri modellerini keyfi formatlarda okumak ve yazmak için kolay ve esnek bir yol elde etmek için: raw binary, XML, JSON, et. ark. Verilerin formatı, verilerin kendisinden ve serileştirme isteyen koddan ayrılmalıdır.
Serileştirmenin mümkün olduğunca hatasız olmasını sağlamak. G / Ç, çeşitli nedenlerden dolayı doğası gereği risklidir: Tasarımım başarısız olması için daha fazla yol sunuyor mu? Öyleyse, bu riskleri azaltmak için tasarımı nasıl yeniden düzenleyebilirim?
Bu proje C ++ kullanıyor. İster sevin ister nefret edin, dilin kendi şeyleri yapma şekli vardır ve tasarım dil ile değil , dil ile çalışmayı amaçlamaktadır .
Son olarak, proje wxWidgets üzerine inşa edilmiştir . Ben daha genel bir dava için geçerli bir çözüm ararken, bu özel uygulama bu araç kiti ile güzel çalışması gerekir.
Aşağıda C ++ ile yazılmış ve tasarımı gösteren çok basit bir sınıflar dizisi verilmiştir. Bunlar şimdiye kadar kısmen yazdığım gerçek sınıflar değil, bu kod kullandığım tasarımı gösteriyor.
İlk olarak, bazı örnek DAO'lar:
#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <vector>
// One widget represents one record in the application.
class Widget {
public:
using id_type = int;
private:
id_type id;
};
// Container for widgets. Much more than a dumb container,
// it will also have indexes and other metadata. This represents
// one data file the user may open in the application.
class WidgetDatabase {
::std::map<Widget::id_type, ::std::shared_ptr<Widget>> widgets;
};
Sonra, DAO'ları okumak ve yazmak için saf sanal sınıflar (arayüzler) tanımlarım. Fikir, verilerin kendisinden ( SRP ) verilerin serileştirilmesini özetlemektir .
class WidgetReader {
public:
virtual Widget read(::std::istream &in) const abstract;
};
class WidgetWriter {
public:
virtual void write(::std::ostream &out, const Widget &widget) const abstract;
};
class WidgetDatabaseReader {
public:
virtual WidgetDatabase read(::std::istream &in) const abstract;
};
class WidgetDatabaseWriter {
public:
virtual void write(::std::ostream &out, const WidgetDatabase &widgetDb) const abstract;
};
Son olarak, istenen G / Ç tipi için uygun okuyucu / yazıcıyı alan kod. Ayrıca tanımlanan okuyucuların / yazarların alt sınıfları olacaktır, ancak bunlar tasarım incelemesine hiçbir şey eklemez:
enum class WidgetIoType {
BINARY,
JSON,
XML
// Other types TBD.
};
WidgetIoType forFilename(::std::string &name) { return ...; }
class WidgetIoFactory {
public:
static ::std::unique_ptr<WidgetReader> getWidgetReader(const WidgetIoType &type) {
return ::std::unique_ptr<WidgetReader>(/* TODO */);
}
static ::std::unique_ptr<WidgetWriter> getWidgetWriter(const WidgetIoType &type) {
return ::std::unique_ptr<WidgetWriter>(/* TODO */);
}
static ::std::unique_ptr<WidgetDatabaseReader> getWidgetDatabaseReader(const WidgetIoType &type) {
return ::std::unique_ptr<WidgetDatabaseReader>(/* TODO */);
}
static ::std::unique_ptr<WidgetDatabaseWriter> getWidgetDatabaseWriter(const WidgetIoType &type) {
return ::std::unique_ptr<WidgetDatabaseWriter>(/* TODO */);
}
};
Tasarımımın belirtilen hedeflerine göre, özel bir endişem var. C ++ akışları metin veya ikili modda açılabilir, ancak önceden açılmış bir akışı kontrol etmenin bir yolu yoktur. Bir XML veya JSON okuyucu / yazıcıya ikili bir akım sağlamak programcı hatası yoluyla mümkün olabilir. Bu, ince (veya çok ince olmayan) hatalara neden olabilir. Kodun hızlı başarısız olmasını tercih ederim, ancak bu tasarımın bunu yapacağından emin değilim.
Bunun bir yolu, akışı okuyucuya veya yazara açma sorumluluğunu ortadan kaldırmak olabilir, ancak bunun SRP'yi ihlal ettiğini ve kodu daha karmaşık hale getireceğine inanıyorum. Bir DAO yazarken, yazar akışın nereye gittiğini umursamalıdır: bir dosya, standart çıkış, bir HTTP yanıtı, bir soket, herhangi bir şey olabilir. Bu endişe serileştirme mantığına dahil edildiğinde çok daha karmaşık hale gelir: belirli bir akış türünü ve hangi kurucuyu çağıracağını bilmelidir.
Bu seçeneğin yanı sıra, basit, esnek ve onu kullanan koddaki mantık hatalarını önlemeye yardımcı olan bu nesneleri modellemek için daha iyi bir yol ne olacağından emin değilim.
Çözümün entegre edilmesi gereken kullanım durumu basit bir dosya seçimi iletişim kutusudur . Kullanıcı Dosya menüsünden "Aç ..." veya "Farklı Kaydet ..." i seçer ve program WidgetDatabase'i açar veya kaydeder. Tek tek Widget'lar için "İçe Aktar ..." ve "Dışa Aktar ..." seçenekleri de olacaktır.
Kullanıcı açmak veya kaydetmek için bir dosya seçtiğinde, wxWidgets bir dosya adı döndürür. Bu olaya yanıt veren işleyici, dosya adını alan, bir serileştirici edinen ve ağır kaldırma işlemi için bir işlev çağıran genel amaçlı kod olmalıdır. İdeal olarak bu tasarım, başka bir kod parçası, bir soket üzerinden bir mobil cihaza WidgetDatabase göndermek gibi dosya olmayan G / Ç gerçekleştiriyorsa da işe yarayacaktır.
Bir widget kendi biçimine kaydediyor mu? Mevcut biçimlerle birlikte çalışır mı? Evet! Yukarıdakilerin hepsi. Dosya iletişim kutusuna geri dönerek Microsoft Word'ü düşünün. Microsoft, DOCX formatını geliştirmekte özgürdü, ancak belirli kısıtlamalar içinde istediler. Aynı zamanda, Word eski ve üçüncü taraf biçimlerini (örneğin PDF) okur veya yazar. Bu program farklı değil: bahsettiğim "ikili" biçim, hız için tasarlanmış henüz tanımlanmış bir iç biçim. Aynı zamanda, kendi alanındaki açık standart formatları (soru ile ilgisiz) okuyabilmeli ve yazabilmelidir, böylece diğer yazılımlarla çalışabilmelidir.
Son olarak, yalnızca bir tür Widget vardır. Alt nesneleri olacaktır, ancak bu serileştirme mantığı tarafından işlenecektir. Program hem Widget'larını yük asla ve dişlisi. Bu tasarımın yalnızca Widget'lar ve WidgetDatabases ile ilgili olması gerekir.