Davranış Olarak Arayüzlü Soyut Temel Sınıf?


14

C # projem için bir sınıf hiyerarşisi tasarlamam gerekiyor. Temel olarak, sınıfın işlevleri WinForms sınıflarına benzer, bu yüzden örnek olarak WinForms araç setini ele alalım. (Ancak, WinForms veya WPF kullanamıyorum.)

Her sınıfın sağlaması gereken bazı temel özellikler ve işlevler vardır. Boyutlar, konum, renk, görünürlük (doğru / yanlış), Çizim yöntemi vb.

Tasarım tavsiyesine ihtiyacım var Soyut bir temel sınıf ve gerçekten tipte değil, daha çok davranışa benzer arayüzlere sahip bir tasarım kullandım. Bu iyi bir tasarım mı? Değilse, daha iyi bir tasarım ne olurdu.

Kod şöyle görünür:

abstract class Control
{
    public int Width { get; set; }

    public int Height { get; set; }

    public int BackColor { get; set; }

    public int X { get; set; }

    public int Y { get; set; }

    public int BorderWidth { get; set; }

    public int BorderColor { get; set; }

    public bool Visible { get; set; }

    public Rectangle ClipRectange { get; protected set; }

    abstract public void Draw();
}

Bazı Kontroller başka Kontroller içerebilir, bazıları sadece çocuk olarak bulunabilir (bu yüzden bu işlevler için iki arayüz yapmayı düşünüyorum:

interface IChild
{
    IContainer Parent { get; set; }
}

internal interface IContainer
{
    void AddChild<T>(T child) where T : IChild;
    void RemoveChild<T>(T child) where T : IChild;
    IChild GetChild(int index);
}

WinForms ekran metnini kontrol eder, böylece bu arayüze de girer:

interface ITextHolder
{
    string Text { get; set; }
    int TextPositionX { get; set; }
    int TextPositionY { get; set; }
    int TextWidth { get; }
    int TextHeight { get; }

    void DrawText();
}

Bazı Kontroller ana Kontrolleri içine yerleştirilebilir, böylece:

enum Docking
{ 
    None, Left, Right, Top, Bottom, Fill
}

interface IDockable
{
    Docking Dock { get; set; }
}

... ve şimdi somut sınıflar oluşturalım:

class Panel : Control, IDockable, IContainer, IChild {}
class Label : Control, IDockable, IChild, ITextHolder {}
class Button : Control, IChild, ITextHolder, IDockable {}
class Window : Control, IContainer, IDockable {}

Burada aklıma gelen ilk "sorun", arayüzlerin yayınlandıktan sonra temelde taşa yerleştirilmiş olmasıdır. Ancak diyelim ki gelecekte arayüzlerde değişiklik yapma ihtiyacını ortadan kaldıracak şekilde arayüzlerimi yeterince iyi hale getirebiliyorum.

Bu tasarımda gördüğüm bir başka sorun, bu sınıfların her birinin arayüzlerini uygulaması gerekeceği ve kodun çoğaltılması hızlı bir şekilde gerçekleşecek. Örneğin, Etiket ve Düğme'de DrawText () yöntemi ITextHolder arabiriminden veya çocukların IContainer yönetiminden türetilen her sınıftan türetilir.

Bu konudaki çözümüm, bu "çoğaltılmış" işlevleri özel bağdaştırıcılara uygulamak ve çağrıları yönlendirmektir. Dolayısıyla, Label ve Button öğelerinin her ikisi de ITextHolder arabiriminden devralınan yöntemlerin içinde çağrılacak bir TextHolderAdapter üyesine sahip olacaktır.

Bu tasarım beni temel sınıfta sanal yöntemler ve gereksiz "gürültü kodu" ile şişirilebilecek birçok ortak işleve sahip olmaktan kurtarmalıdır. Davranış değişiklikleri, Control türevi sınıfları değil, bağdaştırıcıları genişleterek gerçekleştirilebilir.

Bence buna "Strateji" kalıbı deniyor ve bu konuda milyonlarca soru ve cevap olmasına rağmen, bu tasarım için neleri dikkate aldığımı ve hangi kusurları düşünebileceğinizi düşünmek istiyorum. benim yaklaşımım.

Şunu da eklemeliyim ki, gelecekteki gereksinimler yeni sınıflar ve yeni işlevler gerektirecek neredeyse% 100 şansı var.


Neden sadece devralamaz System.ComponentModel.Componentveya System.Windows.Forms.Controlveya diğer mevcut baz sınıfların herhangi? Neden kendi kontrol hiyerarşinizi oluşturmanız ve tüm bu işlevleri tekrar sıfırdan tanımlamanız gerekiyor?
Cody Gray

3
IChildkorkunç bir isim gibi görünüyor.
Raynos

@Cody: ilk olarak, WinForms veya WPF derlemelerini kullanamıyorum, ikincisi - hadi, sadece sorduğum bir tasarım problemine bir örnek. Eğer şekiller veya hayvanlar için daha kolay düşünüyorsanız. derslerimin WinForms denetimlerine benzer davranması gerekiyor, ancak her şekilde değil ve tam olarak onlar gibi değil
grapkulec

2
WinForms veya WPF derlemelerini kullanamayacağınızı söylemek pek mantıklı değil, ancak oluşturduğunuz özel kontrol çerçevesini kullanabilirsiniz. Bu, tekerleği yeniden icat etmek için ciddi bir durum ve olası bir amaç için hayal edemiyorum. Ancak kesinlikle yapmanız gerekiyorsa, neden sadece WinForms denetimlerinin örneğini takip etmiyorsunuz ? Bu, bu soruyu oldukça eski kılıyor.
Cody Gray

1
@graphkulec bu yüzden bir yorum :) Vermek için gerçekten yararlı geri bildirim olsaydı sorunuzu cevaplardım. Hala korkunç bir isim;)
Raynos

Yanıtlar:


5

[1] Mülklerinize sanal "alıcılar" ve "ayarlayıcılar" ekleyin, bu özelliğe ihtiyacım olduğu için başka bir kontrol kütüphanesini kesmek zorunda kaldım:

abstract class Control
{
    // internal fields for properties
    protected int _Width;
    protected int _Height;
    protected int _BackColor;
    protected int _X;
    protected int _Y;
    protected bool Visible;

    protected int BorderWidth;
    protected int BorderColor;


    // getters & setters virtual !!!

    public virtual int getWidth(...) { ... }
    public virtual void setWidth(...) { ... }

    public virtual int getHeight(...) { ... }
    public virtual void setHeight(...) { ... }

    public virtual int getBackColor(...) { ... }
    public virtual void setBackColor(...) { ... }

    public virtual int getX(...) { ... }
    public virtual void setX(...) { ... }

    public virtual int getY(...) { ... }
    public virtual void setY(...) { ... }

    public virtual int getBorderWidth(...) { ... }
    public virtual void setBorderWidth(...) { ... }

    public virtual int getBorderColor(...) { ... }
    public virtual void setBorderColor(...) { ... }

    public virtual bool getVisible(...) { ... }
    public virtual void setVisible(...) { ... }

    // properties WITH virtual getters & setters

    public int Width { get getWidth(); set setWidth(value); }

    public int Height { get getHeight(); set setHeight(value); }

    public int BackColor { get getBackColor(); set setBackColor(value); }

    public int X { get getX(); set setX(value); }

    public int Y { get getY(); set setY(value); }

    public int BorderWidth { get getBorderWidth(); set setBorderWidth(value); }

    public int BorderColor { get getBorderColor(); set setBorderColor(value); }

    public bool Visible { get getVisible(); set setVisible(value); }

    // other methods

    public Rectangle ClipRectange { get; protected set; }   
    abstract public void Draw();
} // class Control

/* concrete */ class MyControl: Control
{
    public override bool getVisible(...) { ... }
    public override void setVisible(...) { ... }
} // class MyControl: Control

Bu önerinin daha "ayrıntılı" veya karmaşık olduğunu biliyorum, ama gerçek dünyada çok kullanışlı.

[2] Bir "IsEnabled" özelliği ekleyin, "IsReadOnly" ile karıştırmayın:

abstract class Control
{
    // internal fields for properties
    protected bool _IsEnabled;

    public virtual bool getIsEnabled(...) { ... }
    public virtual void setIsEnabled(...) { ... }

    public bool IsEnabled{ get getIsEnabled(); set setIsEnabled(value); }
} // class Control

Kontrolünüzü göstermeniz gerekebileceği anlamına gelir, ancak herhangi bir bilgi göstermeyin.

[3] Bir "IsReadOnly" özelliği ekleyin, "IsEnabled" ile karıştırmayın:

abstract class Control
{
    // internal fields for properties
    protected bool _IsReadOnly;

    public virtual bool getIsReadOnly(...) { ... }
    public virtual void setIsReadOnly(...) { ... }

    public bool IsReadOnly{ get getIsReadOnly(); set setIsReadOnly(value); }
} // class Control

Bu, kontrolün bilgileri görüntüleyebileceği, ancak kullanıcı tarafından değiştirilemeyeceği anlamına gelir.


temel sınıftaki tüm bu sanal yöntemleri sevmeme rağmen, tasarım hakkındaki düşüncelerimde onlara ikinci bir şans vereceğim. ve bana gerçekten IsReadOnly özelliğine ihtiyacım olduğunu hatırlattın :)
grapkulec

@grapkulec Temel sınıf olduğu için, türetilmiş sınıfların bu özelliklere sahip olacağını belirtmeniz gerekir, ancak davranışı kontrol eden yöntemlerin her sınıf amacı ;-)
umlcat

mmkey, ne demek istediğini görüyorum ve cevap vermeye zaman ayırdığın için teşekkürler
grapkulec

1
Bunu bir cevap olarak kabul ettim çünkü “havalı” tasarımımı arayüzlerle uygulamaya başladığımda kendimi çabucak sanal ayarlayıcılara ve bazen de alıcılara ihtiyaç duydum. Bu, arayüzleri hiç kullanmadığım anlamına gelmez, sadece kullanımlarını gerçekten ihtiyaç duyulan yerlerle sınırlandırdım.
grapkulec

3

Güzel soru! Fark ettiğim ilk şey, draw yönteminin herhangi bir parametresi olmadığı, nerede çizdiği? Bazı yüzey veya grafik nesnesi parametre olarak alması gerektiğini düşünüyorum (tabii ki arayüz olarak).

Garip bulduğum bir şey daha IContainer arayüzü. Bu sahiptir GetChildsonuçların, bir kere çocuk bu yöntemi ve herhangi bir parametre - belki bir koleksiyon dönmelidir veya bir arayüze Çizim yöntemini taşırsanız o zaman bu arabirimini uygulayan ve maruz kalmadan iç çocukların koleksiyonunu çizebilirsiniz beraberlik yöntemi olabilir .

Belki fikirler için WPF'ye de bakabilirsiniz - bence çok iyi tasarlanmış bir çerçeve. Ayrıca Kompozisyon ve Dekoratör tasarım modellerine de göz atabilirsiniz. Birincisi konteyner elemanları için, ikincisi elemanlara işlevsellik eklemek için yararlı olabilir. Bunun ilk bakışta ne kadar iyi çalışacağını söyleyemem, ama bence şu:

Çoğaltmayı önlemek için, metin öğesi gibi ilkel öğeler gibi bir şeye sahip olabilirsiniz. Sonra bu ilkelleri kullanarak daha karmaşık unsurlar oluşturabilirsiniz. Örneğin a Border, tek bir çocuğu olan bir öğedir. DrawYöntemini çağırdığınızda, bir kenarlık ve bir arka plan çizer ve ardından alt sınırın içine çizer. A TextElementyalnızca bir metin çizer. Şimdi bir düğme istiyorsanız, bu iki ilkeli oluşturarak bir tane oluşturabilirsiniz, yani metni Kenarlığın içine koyarak. Burada Sınır bir Dekoratör gibi bir şey.

Gönderi oldukça uzun sürüyor, bu yüzden bu fikri ilginç bulursanız bana bildirin ve bazı örnekler veya daha fazla açıklama verebilirim.


1
eksik parametreler bir problem değildir, sonuçta sadece gerçek yöntemlerin bir taslağıdır. WPF ile ilgili olarak ben onların fikrine dayalı düzen sistemi tasarladım, bu yüzden dekoratör kısmı zaten yerinde olduğunu düşünüyorum, dekoratörler için düşünmek zorunda kalacak. ilkel öğeler fikriniz makul görünüyor ve kesinlikle deniyorum. Teşekkürler!
grapkulec

@grapkulec Bir şey değil! Parametreler hakkında - evet, sorun değil, sadece niyetlerinizi doğru anladığımdan emin olmak istedim. Bu fikri sevmene sevindim. İyi şanslar!

2

Kodun kesilmesi ve "Soyut temel sınıf ve arayüzler ile tasarımım türler kadar değil, davranışlar daha çok iyi tasarım mı, değil mi?" Sorusunun çekirdeğine gidiyor, bu yaklaşımda yanlış bir şey olmadığını söyleyebilirim.

Aslında her arayüzün tüketen alt sistemin beklediği davranışı tanımladığı böyle bir yaklaşımı (işleme motorumda) kullandım. Örneğin, IUpdateable, ICollidable, IRenderable, ILoadable, ILoader vb. Açıklık için, bu "davranışların" her birini "Entity.IUpdateable.cs", "Entity.IRenderable.cs" gibi ayrı kısmi sınıflara ayırdım ve alanları ve yöntemleri olabildiğince bağımsız tutmaya çalıştım.

Davranışsal kalıpları tanımlamak için arayüzler kullanma yaklaşımı da benimle aynı fikirde çünkü jenerikler, kısıtlamalar ve ortak (ntra) değişken tipi parametreler birlikte çok iyi çalışıyor.

Bu tasarım yöntemi hakkında gözlemlediğim şeylerden biri şuydu: Eğer kendinizi bir avuç yöntemden daha fazlasıyla bir arayüz tanımlarken bulursanız, muhtemelen bir davranış uygulamıyorsunuzdur.


herhangi bir sorunla karşılaştınız mı veya herhangi bir anda böyle bir tasarımdan pişman oluyordunuz ve amaçlanan işleri yapmak için kendi kodunuzla savaşmak zorunda kaldınız mı? örneğin aniden o kadar çok davranış uygulaması yaratmanız gerektiği ortaya çıktı ki, bu hiç mantıklı değil ve sadece eski temaya bağlı kalmanızı dilediniz.
grapkulec
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.