Компилятор жалуется на ваш код, потому что, как вы указали, в OutputFactory
нет класса GenerateOutput(SuperA)
, и разрешение вызовов методов происходит при типе компиляции, а не во время выполнения, и поэтому основано на типе ссылки.(item
является ссылкой с типом SuperA
), а не с типом экземпляра среды выполнения.
Вы можете попробовать разные подходы:
- , если вы обнаружите, что это делаетв смысле, вы можете попытаться переместить полиморфное поведение (генерируемый выходной текст) в
SuperA
иерархию классов, добавив абстрактный метод или свойство к SuperA
и реализовав его по-другому в подклассах SuperA
class SuperA {
public abstract string Content { get; }
}
class B : SuperA {
public string Content => "B";
}
class C : SuperA {
public string Content => "C";
}
class Test {
public void TestMethod() {
// ...
foreach (SuperA item in list) {
Console.WriteLine(item.Content);
}
}
Очень просто, но не очень хорошо работает, когда SuperA
, B, and
C classes are out of your control or when the different desired behaviours you should provide for
A and
B classes does not belong to
B and
C` классов.
вы можете использовать подход, который мне нравится называть
набором ответственности : это что-то вроде паттерна GoF
цепочка ответственности но без цепочки ;-);вы можете переписать ваш
TestMethod
следующим образом:
public void TestMethod() {
var list = new List<SuperA> {new B(), new C()};
var compositeHandler = new CompositeHandler(new Handler[] {
new BHandler(),
new CHandler()
});
foreach (SuperA item in list) {
compositeHandler.Handle(item);
}
}
Поэтому вам нужно определить интерфейс Handler
и его реализации следующим образом:
interface Handler {
bool CanHandle(SuperA item);
void Handle(SuperA item);
}
class BHandler : Handler {
bool CanHandle(SuperA item) => item is B;
void Handle(SuperA item) {
var b = (B)item; // cast here is safe due to previous check in `CanHandle()`
DoSomethingUsingB(b);
}
}
class CHandler : Handler {
bool CanHandle(SuperA item) => item is C;
void Handle(SuperA item) {
var c = (C)item; // cast here is safe due to previous check in `CanHandle()`
DoSomethingUsingC(c);
}
}
class CompositeHandler {
private readonly IEnumerable<handler> handlers;
public CompositeHandler(IEnumerable<handler> handlers) {
this.handlers = handlers;
}
public void Handle(SuperA item) {
handlers.FirstOrDefault(h => h.CanHandle(item))?.Handle(item);
}
}
Этот подход использует типпроверяет (item is B
), но скрывает их за интерфейсом (в частности, каждая реализация интерфейса должна обеспечивать проверку типа для выбора экземпляров, которые он может обрабатывать): если вам нужно добавить третий D extends SuperA
подкласс вашей иерархииКорневой класс: вам нужно только добавить третью реализацию DHandler : Handler
интерфейса Handler
, без изменения ни предоставленных реализаций, ни CompositeHelper
class;единственное изменение, которое вы должны применить к существующему коду, - это регистрация новой реализации handler
в списке, который вы предоставляете конструктору CompositeHelper
, но это можно легко перенести на вас IoC container
Конфигурация или внешний файл конфигурации.Мне нравится этот подход, потому что он позволяет превратить алгоритм на основе проверки типа в полиморфный.
Я писал об этой теме в недавнем посте в моем техническом блоге: https://javapeanuts.blogspot.com/2018/10/set-of-responsibility.html.
Вы можете подойти к вопросу с помощью шаблона GoF
посетитель , который немного сложнее, чем мой предложенный подход, но был задуман именно для таких случаев Вы можете принять
dynamic
основанный на подходе, как предложено в другом ответе
Я надеюсь, что это может помочь вам!