C
C#17mo ago
Althos

❔ Is there a better way to implement this "pattern"?

Hi, I'm trying to implement a pattern where I have a non-generic interface that exposes certain functions and can be used by any other class without the need for a generic, and then a generic version of the interface that helps restrict types properly when creating concrete implementations, here is an example
internal interface IVisualElement
{
IEnumerable<IVisualElement> Children { get; }
void Draw(IVisualElement parent);
}

internal interface IVisualElement<in T> : IVisualElement where T : IVisualElement
{
void DrawGeneric(T parent);
}

internal class ConcreteVisualElement : IVisualElement<Node>
{
public IEnumerable<IVisualElement> Children { get; } = new List<IVisualElement>();

public void DrawGeneric(Node parent)
{
throw new System.NotImplementedException();
}

public void Draw(IVisualElement parent)
{
DrawGeneric(parent as Node);
}
}
internal interface IVisualElement
{
IEnumerable<IVisualElement> Children { get; }
void Draw(IVisualElement parent);
}

internal interface IVisualElement<in T> : IVisualElement where T : IVisualElement
{
void DrawGeneric(T parent);
}

internal class ConcreteVisualElement : IVisualElement<Node>
{
public IEnumerable<IVisualElement> Children { get; } = new List<IVisualElement>();

public void DrawGeneric(Node parent)
{
throw new System.NotImplementedException();
}

public void Draw(IVisualElement parent)
{
DrawGeneric(parent as Node);
}
}
Is there a name for what I'm trying to do, and is there a better way to implement it? Thanks a lot for your help!
5 Replies
Relevant
Relevant17mo ago
This may be off topic to what you're asking, but I'm curious why Draw needs any argument. Wouldn't it just draw this?
Althos
Althos17mo ago
For hierarchical structures, but it's mostly an example of the pattern
Relevant
Relevant17mo ago
Yeah, that's what I figured I don't see anything glaringly wrong with this I've never come across any situation where I've needed a generic and non-generic option, but if I did, this would likely be similar to how I'd implement it, too. Not sure if there's a fancy name for it
Anton
Anton17mo ago
The "proper" way, as in "the way that doesn't cheat the type system" is to wrap the node in a generic class that knows how to draw that node, while exposing a non-generic method that takes no arguments. then you call the draw method via this wrapper. you get the wrapper/provider from a factory that maps nodes to these wrappers. I'm not saying it's a good way, it just doesn't cheat the type system, but it does make a lot of objects. by the same logic, you can capture nodes in delegates that internally are aware of the types and know how to draw the node, but can be called without arguments
interface A
{
void Draw();
}
class B : A
{
Node1 node;
void Draw()=>DrawNode1(node);
}
class C : A
{
Node2 node;
void Draw()=>DrawNode2(node);
}
interface A
{
void Draw();
}
class B : A
{
Node1 node;
void Draw()=>DrawNode1(node);
}
class C : A
{
Node2 node;
void Draw()=>DrawNode2(node);
}
B and C can exploit static polymorphism in their implementations, because they know the node type they will be used for Another way that wouldn't really cheat the type system would be to map types to generic interface implementations via a dictionary actually, you'll still have to cast like you do in that case this option is better, but requires the casts I'd wrap them so that the impl have to cast less
interface Impl<T>
{
void Draw(T node);
}
interface Impl
{
void Draw(BaseNode node);
}
class Wrapper<T> : Impl
{
Impl<T> _impl;
void Draw(BaseNode node)=> _impl.Draw((T) BaseNode);
}
class Impl1 : Impl<Node1>
{
void Draw(Node1 node){}
}

var map = new Dictionary<Type, Impl>
{
{ typeof(Node1), new Wrapper<Node1>(new Impl1()) }
}
//...
void Draw(BaseNode node)
{
map[node.GetType()].Draw(node);
}
interface Impl<T>
{
void Draw(T node);
}
interface Impl
{
void Draw(BaseNode node);
}
class Wrapper<T> : Impl
{
Impl<T> _impl;
void Draw(BaseNode node)=> _impl.Draw((T) BaseNode);
}
class Impl1 : Impl<Node1>
{
void Draw(Node1 node){}
}

var map = new Dictionary<Type, Impl>
{
{ typeof(Node1), new Wrapper<Node1>(new Impl1()) }
}
//...
void Draw(BaseNode node)
{
map[node.GetType()].Draw(node);
}
it's one more object per impl, and it's one more virtual call, but I think it's more maintainable
Accord
Accord17mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.