我的域模型有几个NewsType,它们是NewsItem的子类,如下所示(简化):
public abstract class NewsItem : Entity
{
public virtual Account Account { get; set; }
public virtual DateTime DateTime { get; set; }
}
以下是NewsItem的几个子类:
public class NewsItemJoiner : NewsItem
{
public virtual Account AccountJoined { get; set; }
}
public class NewsItemStatus : NewsItem
{
public virtual string Status { get; set; }
}
在我的MVC应用程序中,我想返回一个Newsitem集合,它可能包含许多不同的NewsItem子类。我现在需要做的是遍历每个新闻项并从相关类中为该特定类型的NewsItem调用Render函数...代码可能会更容易解释它:
public interface IRenderer<T> where T : NewsItem
{
string Render(T item);
}
public class JoinedRenderer : IRenderer<NewsItemJoiner>
{
public string Render(NewsItemJoiner item)
{
return String.Format("{0} has just joined our music network.", item.AccountJoined.ArtistName);
}
}
public class StatusUpdateRenderer : IRenderer<NewsItemStatus>
{
public string Render(NewsItemStatus item)
{
return String.Format("<span class='statusupdate'>{0}<span>", item.Status);
}
}
我需要以某种方式调用正确的类Render函数,具体取决于NewsItem的类型。
答案 0 :(得分:3)
对于虚拟函数来说,这似乎是一个相当明显的例子......
public abstract class RenderableNewsItem : NewsItem
{
abstract public string Render();
}
public class NewsItemStatus : RenderableNewsItem
{
public virtual string Status { get; set; }
public string Render()
{
return String.Format("<span class='statusupdate'>{0}<span>", this.Status);
}
}
答案 1 :(得分:2)
您可以创建一个使用NewsItem类型作为键的字典,并将Render函数用作值。或者,您可以使用Render函数或仅使用所有Render函数维护所有类的列表,并使用Reflection来确定应使用哪种方法。但是,在我看来,您应该考虑重新设计应用程序,以便NewsItem抽象类本身具有虚拟渲染功能,而不是执行任何操作。这将大大简化您的任务。
编辑:以前认为NewsItem是一个界面。
答案 2 :(得分:1)
一种可能性:在启动时(即在与渲染代码相关的静态构造函数中),遍历程序集中的类并实例化并存储Dictionary<Type, object>
IRenderer<T>
- 实现映射到他们渲染的类型。
(这个建议假设渲染器对象是线程安全的,因为你可能最终一次从多个请求线程调用Render
方法。如果它们不是线程安全的,那么你' d需要将字典更改为<Type, Type>
并为每次使用实例化渲染器。)
例如:
public class RenderUtil
{
static Dictionary<Type, object> s_renderers;
static RenderUtil()
{
s_renderers = new Dictionary<Type, object>();
foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
{
var renderInterface = type.GetInterfaces().FirstOrDefault(
i => i.IsGenericType &&
i.GetGenericTypeDefinition() == typeof(IRenderer<>));
if (renderInterface != null)
{
s_renderers.Add(
renderInterface.GetGenericArguments()[0],
Activator.CreateInstance(type));
}
}
}
public static string Render<T>(T item)
{
IRenderer<T> renderer = null;
try
{
// no need to synchronize readonly access
renderer = (IRenderer<T>)s_renderers[item.GetType()];
}
catch
{
throw new ArgumentException("No renderer for type " + item.GetType().Name);
}
return renderer.Render(item);
}
}
用法:
var newsItem = new NewsItemStatus();
// in your example code, ends up calling StatusUpdateRenderer.Render:
var rendered = RenderUtil.Render(newsItem);
请注意,如果给定类型有多个呈现器,RenderUtil
类将在首次使用时通过DuplicateKeyException
抛出TypeInitializationException
。
答案 3 :(得分:1)
我要做的是使用多个部分视图来渲染不同的NewsItem
子类。然后,我会在子类和部分视图名称之间进行某种映射。这有两种方法:
NewsItem
可以有一个虚拟字符串属性/方法,它返回与之关联的局部视图的名称。我只推荐这个,如果NewsItem是一个专门用于传递给视图的模型类,而不是它是一个ORM类或类似的。Dictionary<Type, string>
)。完成此设置后,您的视图可能如下所示:
<% foreach (var newsItem in Model.NewsItems) { %>
<%= Html.RenderPartial(newsItem.PartialViewName, newsItem) %>
<% } >
答案 4 :(得分:0)
考虑反转控制逻辑并在NewsItem中提供虚拟Render()方法。 E.g。
abstract class NewsItem {
// ...
public virtual string Render() { return string.Empty; }
}
然后您的子类可以根据需要实现:
public class NewsItemJoiner : NewsItem
{
// ...
public override string Render() {
return String.Format("{0} has just joined our music network.", this.AccountJoined.ArtistName);
}
}
编辑:
替代技术
关注其他人的意见重新分离关注点。我不知道你是否已经设置了IRenderer&lt; T&gt;由于其他原因,但如果你不是,那么还有另一种技术不需要使用反射。您可以改为使用访客模式。
首先声明一个NewsItemVisitor类:
public abstract class NewsItemVisitor
{
public abstract void Visit(NewsItemJoiner joiner);
public abstract void Visit(NewsItemStatus status);
}
接下来,向NewsItem添加一个虚拟的Accept()方法(对于这个例子,我已经将数据类型更改为字符串而不是Account,Status等):
public abstract class NewsItem
{
public virtual string Account { get; set; }
public virtual DateTime DateTime { get; set; }
public abstract void Accept(NewsItemVisitor visitor);
}
public class NewsItemJoiner : NewsItem
{
public virtual string AccountJoined { get; set; }
public override void Accept(NewsItemVisitor visitor)
{
visitor.Visit(this);
}
}
public class NewsItemStatus : NewsItem
{
public virtual string Status { get; set; }
public override void Accept(NewsItemVisitor visitor)
{
visitor.Visit(this);
}
}
现在您可以创建一个具体的访客,这是我们的渲染器:
public class NewsItemListRenderer : NewsItemVisitor
{
private readonly List<NewsItem> itemList;
private string renderedList = string.Empty;
public NewsItemListRenderer(List<NewsItem> itemList)
{
this.itemList = itemList;
}
public string Render()
{
foreach (var item in itemList)
{
item.Accept(this);
}
return renderedList;
}
public override void Visit(NewsItemJoiner joiner)
{
renderedList += "joiner: " + joiner.AccountJoined + Environment.NewLine;
}
public override void Visit(NewsItemStatus status)
{
renderedList += "status: " + status.Status + Environment.NewLine;
}
}
如何呈现NewsItem实例列表的示例代码:
List<NewsItem> itemList = new List<NewsItem>();
itemList.Add(new NewsItemJoiner { AccountJoined = "fred" });
itemList.Add(new NewsItemJoiner { AccountJoined = "pete" });
itemList.Add(new NewsItemStatus { Status = "active" });
itemList.Add(new NewsItemJoiner { AccountJoined = "jim" });
itemList.Add(new NewsItemStatus { Status = "inactive" });
NewsItemListRenderer renderer = new NewsItemListRenderer(itemList);
Console.WriteLine(renderer.Render());
运行此命令会提供以下输出:
joiner: fred
joiner: pete
status: active
joiner: jim
status: inactive
答案 5 :(得分:0)
我刚刚做了类似的事情,但我没有方便的代码。它使用了Reflection,看起来如下:
List<GenericContainer> gcList = new List<GenericContainer>();
// GenericContainer can be a Jug, Bottle, Barrel, or just a GenericContainer type
// [..fill it..]
GenericContainer gc = gcList[i];
Object returnvalue = gc.GetType()
.GetMethod("Pour", BindingFlags.Instance).Invoke(gc, null);