Это выглядит как главный пример полиморфизма.
Вместо того, чтобы пытаться сделать переключение типов в вашей реализации ICriteria
, почему бы вам не добавить метод к ICriteria
(или, возможно, виртуальный метод в некоторый общий базовый класс всех реализаций ICriteria
), и просто позвони?
Очевидно, что для реализации этого метода потребуется доступ к объектам, которые не принадлежат вашим ICriteria
экземплярам, но эту проблему вы можете решить, используя другие шаблоны проектирования в соответствии со спецификой вашего сценария.
Обновление:
Вот полное решение, включающее отправленный вами код:
Создайте новый интерфейс ICriteriaView
, который моделирует представление (в вашем случае Form
), где отображаются ICriteria
. Форма должна быть обработана в зависимости от конкретного интерфейса, который реализуют критерии, поэтому добавьте метод с одной перегрузкой для каждого интерфейса, существующего в вашем коде. Не добавляйте перегрузку для ICriteria
. [1]
interface ICriteriaView {
void ProcessCriteria(IChoices criteria);
void ProcessCriteria(ITextCriteria criteria);
}
Ваша форма будет реализовывать этот интерфейс, предоставляя методы, при которых будет происходить подходящая обработка для каждого подтипа ICriteria
:
class MyForm : ICriteriaView {
public void ProcessCriteria(IChoices criteria) {
this.SaveMultipleChoiceValues(criteria);
}
public void ProcessCriteria(ITextCriteria criteria) {
// do nothing
}
private void SaveMultipleChoiceValues(IChoices criteria)
{
foreach (DataGridViewRow row in dgvCriteriaControls.Rows)
{
if (row == dgvCriteriaControls.Rows[dgvCriteriaControls.Rows.Count - 1])
continue;
//multipleChoice.AddChoice(row.Cells["Name"].Value.ToString());
string choice = row.Cells["Name"].Value.ToString();
criteria.AddChoice(choice);
}
}
}
Каждая реализация ICriteria
должна будет реализовать метод, который вызывает соответствующую перегрузку ICriteriaView
для своего типа. Вот где происходит «магия перенаправления»: мы будем использовать полиморфизм, чтобы заставить компилятор «обнаружить» фактический тип ICriteria
, в котором находится наш объект, а затем используем перегрузку метода на ICriteriaView.ProcessCriteria
для доступа к соответствующему коду.
interface ICriteria {
void PerformProcessingOn(ICriteriaView view);
}
interface IChoices : ICriteria {
}
interface ITextCriteria : ICriteria {
}
И здесь происходит отправка с соответствующей перегрузкой:
class MultipleChoice : IChoices {
public PerformProcessingOn(ICriteriaView view) {
view.ProcessCriteria(this);
}
}
class SimpleInput : ITextCriteria {
public PerformProcessingOn(ICriteriaView view) {
view.ProcessCriteria(this);
}
}
Тогда ваш код будет делать:
// Get selected criteria
var selectedCriteria = cmbType.SelectedItem as ICriteria;
// Here's where polymorphism kicks in
selectedCriteria.PerformProcessingOn(this);
// Finally, code that runs the same for all objects
_category.AddCriteria(selectedCriteria);
selectedCriteria.LabelText = txtLabeltext.Text;
this.Close();
Обслуживание:
Каждый раз, когда вы добавляете новую реализацию подинтерфейса ICriteria
, определение ICriteria
заставит вас реализовать метод PerformProcessingOn
. Внутри этого метода все, что вы можете сделать - это вызвать view.ProcessCriteria(this)
. В свою очередь, это заставит вас реализовать соответствующую ProcessCriteria
перегрузку в ICriteriaView
и MyForm
.
В результате мы достигли двух важных целей:
- Компилятор не позволит вам добавить новую реализацию
ICriteria
, не указав точно, как эта реализация должна взаимодействовать с ICriteriaView
.
- Легко узнать из исходного кода точно , что
MyView
делает, например, с. IChoices
при чтении кода для MultipleChoice
. Структура кода приводит вас к MyForm.SaveMultipleChoiceValues
«автоматически».
Примечания:
[1] Выбор добавления перегрузки для ICriteria
сам по себе или нет - это действительно компромисс:
Если вы делаете добавляете один из них, то код такой:
class MultipleChoice : IChoices {
public PerformProcessingOn(ICriteriaView view) {
view.ProcessCriteria(this);
}
}
всегда будет успешно компилироваться, потому что даже если нет перегрузки ICriteriaView.ProcessCriteria(IChoices)
, все равно будет перегрузка ICriteriaView.ProcessCriteria(ICriteria)
, которую может использовать компилятор.
Это означает, что при добавлении новой реализации субинтерфейса ICriteria
компилятор больше не будет заставлять проверять, действительно ли реализация ICriteriaView.ProcessCriteria(ICriteria)
работает правильно для вашего нового осуществление.
Если вы не добавляете его, то в момент написания view.ProcessCriteria(this);
компилятор заставит проверить и обновить (*) ICriteriaView
и MyForm
соответственно.
В этом сценарии и с учетом предоставленной вами информации я считаю, что соответствующий выбор будет последним.
[2] Как вы можете видеть выше, реализация ICriteria.PerformProcessingOn
внутри MultipleChoice
и SimpleInput
выглядит точно так же. Если эти два класса имеют общую базу (что на практике вполне возможно), у вас может возникнуть желание перенести «дублированный» код в эту базу. Не делай этого; это приведет к поломке решения.
Сложность в том, что внутри MultipleChoice
, когда вы делаете view.ProcessCriteria(this);
, компилятор может сделать вывод, что статический тип this
равен IChoices
- это то место, где происходит перенаправление!Если вы переместите вызов на ProcessCriteria
внутри гипотетического базового класса CriteriaBase : ICriteria
, тогда тип this
станет ICriteria
, и отправка вызова на соответствующую перегрузку ICriteriaView.ProcessCriteria
больше не будет работать.