Производные классы абстрактного класса в массиве
Всем добрый день.
У меня есть вопрос о создании и использовании производных классов некоторого базового абстрактного класса.
Я не могу понять, как это работает.
Моя цель: создать базовый класс, который определит, какие функции должны быть, а затем создать различные производные классы. Затем составить массив базового класса и создать в нем различные производные классы.
Например:
abstract class UIElement
{
//constructor
abstract public UIElement();
//draw element
abstract public void Draw(int mouseX, int mouseY);
//checks if the element is pointed at by mouse
abstract public Boolean IsPointed(int mouseX, int mouseY);
//defines what happens when clicked
abstract public void Click(int mouseX, int mouseY);
//destructor
abstract public ~UIElement();
}
Затем создайте несколько производных классов и сделайте так:
UIElement[] qwe = new UIElement[3];
qwe[0] = new UIElementButton();
qwe[1] = new UIElementLabel();
qwe[2] = new UIElementSomethingElse();
1) Как правильно это сделать, потому что я не могу найти окончательный ответ или статью, которая бы это объяснила...
2) О переменных класса, где они должны быть объявлены: базовый класс или производные?
3) Что, если разные производные классы имеют разные переменные, как это следует учитывать?
4) Могут ли производные классы иметь разные конструкторы, включая разные аргументы?
3 ответа
В комментариях ниже, вопросник ищет советы по передовой практике написания иерархий наследования с абстрактными классами.
С точки зрения общих рекомендаций и правильности, вы должны объявить свой базовый класс UIElement без абстракции в конструкторе и без финализатора. например
public abstract class UIElement
{
//constructor
protected UIElement();
//draw element
public abstract void Draw(int mouseX, int mouseY);
//checks if the element is pointed at by mouse
public abstract bool IsPointed(int mouseX, int mouseY);
//defines what happens when clicked
public abstract void Click(int mouseX, int mouseY);
}
Затем вы можете реализовать производные классы следующим образом
public class UIElementButton : UIElement
{
// Optional - create parameterless constructor and
// explicitly call base constructor
public UIElementButton() : base()
{
}
// Mandatory - must override abstract methods
public override Draw(int mouseX, int mouseY)
{
}
// Mandatory - must override abstract methods
public override Boolean IsPointed(int mouseX, int mouseY)
{
}
// Mandatory - must override abstract methods
public override void Click(int mouseX, int mouseY)
{
}
}
Вы правы в том, что если вы определяете производные классы в соответствии с вышеприведенным, вы можете создавать экземпляры элементов и сохранять их в массиве базового типа следующим образом:
UIElement[] qwe = new UIElement[3];
qwe[0] = new UIElementButton();
qwe[1] = new UIElementLabel();
qwe[2] = new UIElementSomethingElse();
qwe[0].Click(1, 2); // Invokes UIElementButton.Click
qwe[1].Draw(3, 4); // Invokes UIElementLabel.Draw
Вы также должны знать, что в WPF определен класс UIElement. Это не будет проблемой, если вы определите пространство имен для своего типа, но, возможно, рассмотрите более явное имя типа, такое как BaseElement
чтобы отличить ваш пользовательский тип.
По поводу ваших запросов в комментариях:
Дополнительные советы по использованию абстрактных классов см. В разделе Все об абстрактных классах - Codeproject. - Где должны быть объявлены переменные класса: базовый класс или производный? - Что, если разные производные классы имеют разные переменные, как это следует учитывать? - Могут ли производные классы иметь разные конструкторы, включая разные аргументы?
Это действительно тема для разных вопросов или собственного исследования в Google, однако, для начала я могу сказать:
Переменные класса должны быть объявлены в той области, где они необходимы. Смысл, если у вас есть переменная
Guid _uiElementId
и чтобы к нему могли обращаться производные типы, он должен быть объявлен в базовом классе и помечен какprotected
, Если у вас есть переменная в UIElementButton с именемPoint _mousePoint
и только кнопка UIElement нуждается в этом, оставьте это там и отметьте какprivate
Это нормально - любой тип может видеть свои собственные закрытые переменные и защищенные переменные из базовых классов. Просто убедитесь, что имена не конфликтуют
Да, они могут, например.
public class UIElementButton : UIElement
{
private string _buttonText;
// Overloaded ctor
public UIElementButton(string buttonText) : base()
{
buttonText = buttonText;
}
// Default constructor. Mark as private or protected
// to hide from external API
public UIElementButton() : base()
{
}
}
С наилучшими пожеланиями,
Если я правильно угадаю, ОП происходит от фона C++. Есть много параллелей между C++ и C#, но не все функции соответствуют 1:1.
Если вы хотите, чтобы ваш "базовый класс" просто установил допустимые операции для всех потомков, т.е. он не имеет кода, вы должны использовать интерфейс:
public interface IUIElement
{
//draw element
void Draw(int mouseX, int mouseY);
//checks if the element is pointed at by mouse
bool IsPointed(int mouseX, int mouseY);
//defines what happens when clicked
void Click(int mouseX, int mouseY);
}
Нет необходимости в конструкторе или деструкторе или в явном объявлении методов как открытых, потому что интерфейс не может содержать частные методы / свойства, которые передаются классам, которые его реализуют.
После этого вы можете объявить ваши классы как (Visual Studio может их заглушить):
public class UIElementButton : IUIElement
{
public UIElementButton()
{
...
}
void Draw(int mouseX, int mouseY)
{
...
}
bool IsPointed(int mouseX, int mouseY)
{
...
}
void Click(int mouseX, int mouseY)
{
}
}
Остальная часть вашего кода (с массивом) выглядит нормально, однако я бы предпочел список вместо массива:
List<IUIElement> qwe = new List<IUIElement>();
qwe.Add(new UIElementButton());
qwe.Add(new UIElementLabel());
qwe.Add(new UIElementSomethingElse());
// some sample usages
foreach(IUIElement element in qwe)
{
element.Click();
}
qwe[0].Click() //invokes UIElementButton.Click();
//gets all pointed elements
var pointedElements = qwe.Where(e => e.IsPointed(x,y));
Из вашего вопроса не ясно, что это проблема, но ваш код не будет компилироваться. В C# вы не можете иметь abstract
конструктор. И финализатор не может быть ни public
ни abstract
, Нет необходимости иметь ни один из них, вы можете просто удалить их.
И вам, вероятно, вообще не следует использовать финализатор, если вы не хотите распоряжаться каким-либо неуправляемым ресурсом. И если вам нужно это, вы, скорее всего, хотите реализовать IDisposable
тоже.