从基类中一般初始化派生类'字段的强大方法?

时间:2016-12-16 10:29:27

标签: c# oop inheritance

我正在开发一个项目,该项目具有从类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; }
}

在这个例子中,我必须在调用MainScreenView.Initialize时对所有ImageViews调用Initialize()。这感觉容易出错并且不方便,因为每次将新的子视图添加到MainScreenView组合时都必须添加Initialize()调用。因此,我想在派生类中消除对这些调用的需要,但我想将字段维护到特定于视图的字段。

我的想法是向基类添加一个Views集合,然后可以递归地初始化(),如下所示:

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()有同样的问题。

所以我的问题是:是否有办法在View基类中完成子视图的所有初始化调用,同时仍然能够提供派生类特定的元素而不传递对这些元素的引用到派生类'构造函数?

2 个答案:

答案 0 :(得分: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类型的字段添加到您的班级,则会使用其他子视图进行初始化。

ORIGINAL ANSWER:只需将两个视图添加到基类子视图列表:

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

答案 1 :(得分: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();

将正确初始化所有视图。