2016-12-16 1 views
1

Я работаю над проектом, который имеет множество классов, которые происходят из класса View, где View предоставляет некоторые распространенные методы и где производные классы имеют поля, которые ссылаются на элементы пользовательского интерфейса, специфичные для этого представления. Например (в 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; } 
} 

В этом примере я должен позвонить Initialize() на всех ImageViews когда MainScreenView.Initialize называется. Это кажется неприемлемым и неудобным для ошибок, потому что вызов Initialize() должен быть добавлен каждый раз при добавлении нового подзадача в композицию MainScreenView. Поэтому я хотел бы исключить необходимость этих вызовов в производных классах, но я хочу поддерживать поля в полях, специфичных для представления.

Моя идея заключается в том, чтобы добавить коллекцию представлений для базового класса, который затем может рекурсивно быть инициализирован() следующим образом:

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, при этом все же быть в состоянии предоставить элементы, относящиеся к производному классу, без передачи ссылок на эти элементы на конструктор производного класса?

+0

Предпочитаете использовать интерфейсы (например, 'IView') над базовыми абстрактными классами. Вы можете получить только один класс в C#, и нет необходимости создавать зависимость от базового абстрактного класса во всей вашей программе. Пока интерфейс 'IView' будет общедоступным, ваш базовый класс может быть оставлен« внутренним »и скрыт от других сборок. Кроме того, тот факт, что каждый * конкретный * вид будет иметь свои собственные * специальные * общедоступные методы, означает, что нет возможности фактически абстрагировать их использование, поэтому я не уверен, как вы планируете использовать эти методы вообще. – Groo

ответ

2

UPDATE: Если вы хотите, чтобы убедиться, что все суб виды инициализации (т.е. никто не забудьте добавить их в базовый список класса суб просмотров) вы можете использовать отражение подход. Вот основная идея:

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 добавлен новый subView, новый подзадача должен быть добавлен в список параметров как Что ж. Это невозможно выполнить, и кто-то может забыть добавить его в список, чего я бы хотел избежать. – 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(); 

будет правильно инициализировать все виды.