2016-12-16 75 views
1

我正在開發一個項目,它有許多從類View派生的類,其中View提供了一些常用的方法,派生類的字段引用了該視圖的特定UI元素。例如(在C#):魯棒的方法來從基類一般初始化派生類的字段?

public abstract class View 
{ 
    public virtual void Initialize(){} 
    public virtual void Activate(){} 
    public virtual void Deactivate(){} 
}  

public class MainScreenView : View 
{ 
    private ImageView portraitImageView; 
    private ImageView landscapeImageView; 

    public MainScreenView(ImageView portrait, ImageView landscape) 
    { 
     portraitImageView = portrait; 
     landscapeImageView = landscape; 
    } 

    public override Initialize() 
    { 
     base.Initialize(); 
     portraitImageView.Initialize(); // I would like to eliminate these calls! 
     landscapeImageView.Initialize(); 
    } 

    public ImageView GetPortrait() { return portraitImageView; } 
    public ImageView GetLandscape() { return landscapeImageView; } 
} 

public class ImageView : View 
{ 
    private Image image; 

    public ImageView(Image image) { this.image = image; } 
    public override void Initialize() { base.Initialize(); image.Show(); } 
    public Image GetImage() { return image; } 
} 

在這個例子中,我要對所有ImageViews調用初始化()時MainScreenView.Initialize被調用。這感覺容易出錯並且不方便,因爲每次將新的子視圖添加到MainScreenView組合時,都必須添加Initialize()調用。因此,我想在派生類中消除對這些調用的需求,但是我想將這些字段保留到特定於視圖的字段中。

我的想法是添加視圖基類,然後可以遞歸初始化()的集合,如下:

public abstract class View 
{ 
    private List<View> subViews; 

    public virtual void Initialize() 
    { 
     foreach(View in subViews) { view.Initialize(); } 
    } 

    // This gets called before Initialize() is called. 
    public void AddSubViews(View[] views) 
    { 
     subViews = new List<View>(); 
     subViews.AddRange(views); 
    } 
} 

public class MainScreenView : View 
{ 
    private ImageView portraitImageView; 
    private ImageView landscapeImageView; 

    public MainScreenView() 
    { 
     portraitImageView = ???; 
     landscapeImageView = ???; 
    } 

    // Even if View.subViews had been protected instead of private, this couldn't return an element from the list because the required index is unknown. 
    public ImageView GetPortrait() { return portraitImageView; } 
    public ImageView GetLandscape() { return landscapeImageView; } 
} 

public class ImageView : View 
{ 
    private Image image; 

    public ImageView() { this.image = ??? } 
    public override void Initialize() { base.Initialize(); image.Show(); } 
    public Image GetImage() { return image; } // Even if View.subViews had been protected instead of private, this couldn't return an element from the list because the required index is unknown. 
} 

然而,因爲所有的單個子視圖現在是「匿名'(它們通過索引而不是字段名來訪問),除非我也通過派生類的構造函數添加子視圖,如我在第一個示例中所做的那樣,我無法執行傳遞給構造函數的對象與列表中的對象相同,或者從派生類的構造函數中調用AddSubViews,其中每次添加新子視圖時都手動添加子視圖...具有相同問題是在子視圖上調用Initialize()在派生類中。

所以我的問題是:是有辦法有子視圖的所有初始化調用的視圖基類正在做,同時仍能夠提供派生類特定的元素,而無需通過引用這些元素派生類的構造函數?

+0

更喜歡在基本抽象類上使用接口(例如'IView')。您只能從C#中的單個類派生出來,並且不需要在整個程序中爲基本抽象類創建依賴項。雖然'IView'接口將是公共的,你的基類可以被「內部」保留,並隱藏其他程序集。另外,每個*特定*視圖都有自己的*特定*公共方法這一事實意味着沒有辦法真正抽象它們的用法,所以我不確定你打算如何使用這些方法。 – Groo

回答

2

更新:如果你想確保所有的子視圖被初始化(即沒有人忘記將它們添加到子視圖的基類列表),你可以使用反射的方法。這是主要的想法:

public interface IView // you don't need abstract class 
{ 
    void Initialize(); 
} 

使用反射來獲取它們實現IView所有類字段和被初始化:

public class View : IView 
{ 
    private IView portraitView; 
    private IView landscapeView; 

    // assign some values to sub-views 

    public virtual void Initialize() 
    { 
     var flags = BindingFlags.NonPublic | BindingFlags.Instance; 
     var subViews = from field in GetType().GetFields(flags) 
         let value = field.GetValue(this) 
         where value != null && value is IView 
         select (IView)value; 

     foreach (var subView in subViews) 
      subView.Initialize(); 
    } 
} 

這麼簡單。現在,如果有人會將IView類型的字段添加到您的課程中,則會使用其他子視圖進行初始化。


原來的答案:只需添加這兩種觀點基類子視圖列表:

public MainScreenView(ImageView portrait, ImageView landscape) 
{ 
    portraitImageView = portrait; 
    landscapeImageView = landscape; 
    AddSubViews(new View [] { portrait, landscape }); 
} 

也請記住,你正在重新創建子視圖每次列表,當您試圖添加新觀點:

public void AddSubViews(View[] views) 
{ 
    subViews = new List<View>(); // here 
    subViews.AddRange(views); 
} 

我認爲這是較好的一類場初始化期間只有一次創建子視圖列表:

private readonly List<View> subViews = new List<View>(); 

public void AddSubViews(params View[] views) // you can use params here 
{ 
    subViews.AddRange(views); 
} 

現在,你根本就打電話

AddSubViews(portrait, landscape); 
+0

是的,將它們添加到子視圖列表是我想到的(顯然在我的問題中沒有清楚地提到),但是當新的子視圖添加到MainScreenView時,必須將新的子視圖添加到參數列表中好。這不能執行,有人可以忘記將它添加到列表中,這是我想避免的。 – Soulrot

+0

@Soulrot看到更新的答案。我在寫基於硒的測試框架時使用了這種方法。自初始化視圖初始化基於類字段的所有控件 –

+1

雖然我傾向於使用反射作爲最後的手段,但它可能是唯一能達到我想要的,所以更新的答案將適用於我!事實上,我在另一個子系統中使用了非常相似的東西,但不確定爲什麼我在這種情況下沒有想到這種方法。 – Soulrot

1

您可以使用以下模式:

public abstract class View 
{ 
    private IEnumerable<View> SubViews { get; } 

    protected View(params View[] subViews) 
    { 
     SubViews = subViews; 
    } 

    public void Initialize() 
    { 
     OnInitialize(); 

     foreach (var view in SubViews) 
     { 
      view.Initialize(); 
     } 
    } 

    protected abstract void OnInitialize(); 
} 

現在你具體的意見將是這樣的:

public class MainScreenView : View 
{ 
    private readonly ImageView portraitImageView; 
    private readonly ImageView landscapeImageView; 

    public MainScreenView(ImageView portrait, ImageView landscape) 
     : base(portrait, landscape) 
    { 
     portraitImageView = portrait; 
     landscapeImageView = landscape; 
    } 

    protected override void OnInitialize() { } 
    public ImageView GetPortrait() { return portraitImageView; } 
    public ImageView GetLandscape() { return landscapeImageView; } 
} 

public class ImageView : View 
{ 
    private readonly Image image; 

    public ImageView(Image image) 
     : base() 
    { 
     this.image = image; 
    } 

    protected override void OnInitialize() { image.Show(); } 
    public string GetImage() { return image; } 
} 

最後,

var main = new MainScreenView(new ImageView(portraitImage), new ImageView(landScapeImage)); 
main.Initialize(); 

將正確初始化所有視圖。

相關問題