Tek sorumluluk ve özel veri türleri


10

Geçtiğimiz aylarda burada SE ve diğer sitelerdeki insanlar için kodumla ilgili bazı yapıcı eleştiriler sundum. Neredeyse her seferinde ortaya çıkan bir şey var ve hala bu öneriye katılmıyorum; : P Burada tartışmak istiyorum ve belki de işler bana daha açık hale gelecektir.

Tek sorumluluk prensibi (SRP) ile ilgilidir. Temelde, Fontsadece veri manipüle etmek için değil, aynı zamanda onu yüklemek için fonksiyonları tutan bir veri sınıfı var. İkisinin ayrı olması gerektiği, yükleme fonksiyonlarının fabrika sınıfına yerleştirilmesi gerektiği söylendi; Bence bu, SRP'nin yanlış bir yorumu ...

Yazı Tipi Sınıfımdan Bir Parça

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();
};

Önerilen Tasarım

class Font
{
  public:
    void some();
    void another();
};


class FontFactory
{
  public:
    virtual std::unique_ptr<Font> createFromFile(...) = 0;
    virtual std::unique_ptr<Font> createFromMemory(...) = 0;
};

Önerilen tasarım SRP'yi takip ediyor, ancak katılmıyorum - sanırım çok ileri gidiyor. FontSınıf artık kendi kendine yeterli (o fabrikanın olmadan hiçbir işe yaramaz) ve değildir FontFactoryihtiyaçlar ayrıca uygulanmasını maruz muhtemelen dostluk veya kamu getters aracılığıyla yapılır kaynak, uygulanması, ilgili ayrıntıları bilmek Font. Bence bu daha çok parçalanmış bir sorumluluk vakası .

İşte bu yüzden yaklaşımımın daha iyi olduğunu düşünüyorum:

  • Fontkendi kendine yeter - Kendi kendine yeterli olmak, anlamak ve sürdürmek daha kolaydır. Ayrıca, başka bir şey eklemek zorunda kalmadan sınıfı kullanabilirsiniz. Bununla birlikte, daha karmaşık bir kaynak yönetimine (bir fabrika) ihtiyacınız olduğunu fark ederseniz, bunu da kolayca yapabilirsiniz (daha sonra kendi fabrikam hakkında konuşacağım ResourceManager<Font>).

  • Standart kitaplığı izler - Kullanıcı tanımlı türlerin, ilgili dilde standart türlerin davranışını kopyalamak için mümkün olduğunca denemesi gerektiğine inanıyorum. std::fstreamKendi kendine yeterli olduğunu ve benzeri işlevleri sağlar openve close. Standart kütüphaneyi takip etmek, çabaları öğrenmek için çaba harcamaya gerek olmadığı anlamına gelir. Ayrıca, genel olarak konuşursak, C ++ standart komitesi muhtemelen tasarım hakkında buradakinden daha fazla şey biliyor, bu yüzden şüpheniz varsa ne yaptıklarını kopyalayın.

  • Test edilebilirlik - Bir şeyler ters gidiyor, sorun nerede olabilir? - FontVerilerini işleme şekli FontFactorymi yoksa verileri yükleme şekli mi? Gerçekten bilmiyorsun. Sınıfların kendi kendine yeterli olması bu sorunu azaltır: tek başına test edebilirsiniz Font. Daha sonra fabrikayı test etmeniz gerekiyorsa ve Fontiyi çalıştığını biliyorsanız, bir sorun olduğunda fabrikanın içinde olması gerektiğini de bilirsiniz.

  • Bu bağlam agnostik - (Bu benim ilk nokta ile biraz kesişir.) Bir Fontşey yapar ve onu nasıl kullanacağınız hakkında herhangi bir varsayımda bulunmaz: istediğiniz gibi kullanabilirsiniz. Kullanıcıyı fabrika kullanmaya zorlamak, sınıflar arasındaki bağlantıyı artırır.

Benim de bir fabrikam var

(Çünkü tasarımı Fontbana izin veriyor.)

Ya da daha çok bir yönetici, sadece bir fabrika değil ... Fontkendi kendine yeterlidir, bu yüzden yöneticinin nasıl inşa edileceğini bilmesine gerek yoktur ; bunun yerine yönetici aynı dosya veya ara belleğin birden fazla belleğe yüklenmemesini sağlar. Bir fabrikanın da aynısını yapabileceğini söyleyebilirsiniz, ama bu SRP'yi bozmaz mı? O zaman fabrika sadece nesne inşa etmekle kalmaz, aynı zamanda onları da yönetir.

template<class T>
class ResourceManager
{
  public:
    ResourcePtr<T> acquire(const std::string& file);
    ResourcePtr<T> acquire(const void* buffer, std::size_t size);
};

İşte yöneticinin nasıl kullanılabileceğine dair bir gösteri. Temelde bir fabrikada kullanıldığına dikkat edin.

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...
}

Sonuç olarak...

(Bu bir tl koymak istiyorum; dr, ama bir tane düşünemiyorum.: \)
Eh, işte orada, davamı olabildiğince iyi yaptım. Lütfen sahip olduğunuz karşı argümanları ve önerilen tasarımın kendi tasarımım üzerinde olduğunu düşündüğünüz avantajları gönderin. Temel olarak, bana yanlış olduğumu göstermeye çalış. :)


2
Bana Martin Fowler'in ActiveRecord ve DataMapper'lerini hatırlatıyor .
Kullanıcı

En dıştaki, kullanıcı arayüzünde rahatlık (mevcut tasarımınız) sağlayın. Gelecekteki uygulama değişikliklerinizi kolaylaştırmak için SRP'yi dahili olarak kullanın. İtalik ve kalın atlayan Font yükleyici süslemelerini düşünebilirim; sadece Unicode BMP, vb. yükler
rwong


@rwong Bu sunumu biliyorum, bir yer imi vardı ( video ). :) Ama diğer yorumunda ne dediğini anlamıyorum ...
Paul

1
@rwong Zaten tek bir astar değil mi? Doğrudan veya ResourceManager aracılığıyla bir Font yükleseniz de yalnızca bir satıra ihtiyacınız vardır. Kullanıcılar şikayet ederse RM'yi yeniden uygulamamı ne engeller?
Paul

Yanıtlar:


7

Bence bu kodda yanlış bir şey yok, mantıklı ve bakımı kolay bir şekilde ihtiyacınız olan şeyi yapıyor.

Ancak , bu kodla ilgili sorun başka bir şey yapmak istiyorsanız, tüm değiştirmek zorunda kalacak olmasıdır .

SRP'nin amacı, A () algoritmasını yapan tek bir 'CompA' bileşeniniz varsa ve A () algoritmasını değiştirmeniz gerektiğinde, 'CompB' yi de değiştirmeniz gerekmemesidir.

C ++ becerilerim, yazı tipi yönetimi çözümünüzü değiştirmeniz gereken iyi bir senaryo önermek için çok paslı, ancak yaptığım genel durum, bir önbellek katmanında kayma fikri. İdeal olarak, yükleri yükleyen şeyin nereden geldiğini bilmesini istemezsiniz veya yüklenen şeyin nereden geldiğine dikkat etmesi gerekmez, çünkü daha sonra değişiklik yapmak daha kolaydır. Her şey sürdürülebilirlikle ilgilidir.

Bunun bir örneği, yazı tipinizi üçüncü bir kaynaktan yüklediğiniz olabilir (bir karakter hareketli resmi). Bunu başarmak için yükleyicinizi (ilk ikisi başarısız olursa üçüncü yöntemi çağırmak için) ve bu üçüncü çağrıyı uygulamak için Font sınıfının kendisini değiştirmeniz gerekecektir. İdeal olarak, sadece başka bir fabrika (SpriteFontFactory veya her ne olursa olsun) yaparsınız, aynı loadFont (...) yöntemini uygularsınız ve yazı tipini yüklemek için kullanılabilecek bir yerde bir fabrika listesine yapıştırırsınız.


1
Ah, anlıyorum: bir yazı tipi yüklemek için bir yol daha eklersem, yöneticiye bir tane daha alma fonksiyonu ve kaynağa bir tane daha yükleme fonksiyonu eklemem gerekir. Gerçekten de bu bir dezavantaj. Bununla birlikte, bu yeni kaynağın ne olabileceğine bağlı olarak, muhtemelen verileri farklı işlemek zorunda kalacaksınız (TTF'ler bir şey, yazı tipi sprite'ler başka), bu nedenle belirli bir tasarımın ne kadar esnek olacağını gerçekten tahmin edemezsiniz. Ne demek istediğini anlıyorum.
Paul

Evet, dediğim gibi, C ++ becerilerim oldukça paslı, bu yüzden konunun uygun bir gösterisini yapmak için mücadele ettim, esneklik konusunu kabul ediyorum. Dediğim gibi, gerçekten kodunuzla ne için gittiğine bağlı, orijinal kodunuzun soruna mükemmel bir şekilde makul bir çözüm olduğunu düşünüyorum.
Ed James

Harika soru ve harika cevap ve en iyi şey, birden fazla geliştiricinin ondan öğrenebilmesidir. Bu yüzden burada takılmayı seviyorum :). Oh ve bu yüzden yorumum tamamen gereksiz değil, SRP biraz zor olabilir, çünkü kendinize 'ne olursa' sormak zorundasınız, ki bu karşı gelebilir: 'erken optimizasyon tüm kötülüğün kökü' veya ' YAGNI 'felsefeleri. Asla siyah beyaz bir cevap yoktur!
Martijn Verburg

0

Sınıfınız hakkında beni rahatsız eden bir şey, sahip olduğunuz loadFromMemoryve loadFromFileyöntemlerinizdir. İdeal olarak, sadece loadFromMemoryyönteminiz olmalıdır ; bir yazı tipi bellekteki verilerin nasıl geldiği ile ilgilenmemelidir. Başka bir şey ise, yük ve freeyöntemler yerine yapıcı / yıkıcı kullanmanızdır . Böylece loadFromMemoryolur Font(const void *buf, int len)ve free()olur ~Font().


Yük fonksiyonlarına iki kurucudan erişilebilir ve yıkıcıda serbest çağrılır - bunu burada göstermedim. Yazı tipini doğrudan bir dosyadan yükleyebilmek, önce dosyayı açmak, verileri bir arabelleğe yazmak ve ardından Yazı tipine aktarmak yerine uygun buluyorum. Bazen de bir tampondan yüklemem gerekiyor, bu yüzden her iki yönteme de sahibim.
Paul
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.