I'm studying up on the unity containers and have a quick question on how to resolve a class's construction to multiple different implementations of an interface.
Here's my code:
public interface IRenderer
{
void DrawSquare(Square square);
void DrawCircle(Circle circle);
}
public interface IShape
{
void Draw(IRenderer renderer);
}
public class Dx11Renderer : IRenderer
{
public void DrawSquare(Square square)
{
}
public void DrawCircle(Circle circle)
{
}
}
public class GlRenderer : IRenderer
{
public void DrawSquare(Square square)
{
}
public void DrawCircle(Circle circle)
{
}
}
public class Circle : IShape
{
public void Draw(IRenderer renderer) { renderer.DrawCircle(this); }
}
public class Square
{
public void Draw(IRenderer renderer) { renderer.DrawSquare(this); }
}
public class Canvas
{
private readonly IRenderer _renderer;
private List<Circle> _circles = new List<Circle>();
private List<Square> _squares = new List<Square>();
public Canvas(IRenderer renderer)
{
_renderer = renderer;
}
public void Draw()
{
foreach (Circle c in _circles)
{
c.Draw(_renderer);
}
foreach (Square s in _squares)
{
s.Draw(_renderer);
}
}
}
and to register/resolve
// Create the container
var container = new UnityContainer();
// registration
container.RegisterType<IRenderer, GlRenderer>("GL");
container.RegisterType<IRenderer, Dx11Renderer>("DX11");
Canvas canvas = container.Resolve<Canvas>("GL");
This throws a "ResolutionFailedException" so I must be using this incorrectly.
Can someone explain if this is bad practice, or how I can achieve this.
Thanks
UPDATE:
So what I have done is registered Canvas twice with each type of dependencies like so:
// Canvas with an OpenGL Renderer
container.RegisterType<Canvas>("GLCanvas", new InjectionConstructor(new ResolvedParameter<IRenderer>("GL")));
// Canvas with a DirectX Renderer
container.RegisterType<Canvas>("DXCanvas", new InjectionConstructor(new ResolvedParameter<IRenderer>("DX11")));
Canvas canvas = container.Resolve<Canvas>("GLCanvas");
This works well for me!
The problem is that you are resolving Canvas with the name "GL", but you have not registered Canvas in that way. Unity doesn't propagate the name to dependency resolution, so it won't use the name "GL" when resolving IRenderer.
There are several options to solve this already answered: Resolving named dependencies with Unity
Your question is whether this is a bad practice, or how you can achieve the same results. In my experience, trying to register and resolve multiple instances of the same interface usually leads to messy code. One alternative would be to use the Factory pattern to create instances of Canvas.
Do you need to use your container to resolve Canvas? If you don't have a reason not to, you could simply Resolve your IRenderer and new up a Canvas yourself:
new Canvas(container.Resolve<IRenderer>("GL"));
Remember that Unity is just a tool, if it doesn't seem to be capable of doing what you need, you may need a different kind of tool.
There is a way to inject the right renderer in the canvas on startup time. If you know the render method on startup you can register only the right renderer like this:
var container = new UnityContainer();
container.RegisterType<ICanvas, Canvas>();
if (CheckIfItIsDx11)
{
container.RegisterType<IRenderer, Dx11Renderer>();
}
else
{
container.RegisterType<IRenderer, GlRenderer>();
}
when you want to resolve the canvas just use:
var canvas = container.Resolve<ICanvas>();
if you dont know the renderer on startup time there is a way to. Like this:
container.RegisterType<IRenderer, Dx11Renderer>("DX11");
container.RegisterType<IRenderer, GlRenderer>("GL");
var renderer = container.Resolve<IRenderer>("DX11");
var canvas = container.Resolve<ICanvas>(new ParameterOverride("renderer", renderer));
Canvas now has the right renderer injected. The canvas can use the renderer interface like this:
internal interface ICanvas
{
void Draw();
}
public class Canvas : ICanvas
{
private readonly IRenderer _renderer;
private readonly List<Circle> _circles = new List<Circle>();
private readonly List<Square> _squares = new List<Square>();
public Canvas(IRenderer renderer)
{
_renderer = renderer;
}
public void Draw()
{
foreach (var circle in _circles)
{
_renderer.Draw(circle);
}
foreach (var square in _squares)
{
_renderer.Draw(square);
}
}
}
Also the renderer should not be drawing the shape. The shape is responsible for drawing itself. This way you keep your code at the same spot. If you keep adding shapes the renderer file get huge. and you need to search for some shapes if you want to change code. Now everything is in the right place where it should be. The code now should look something like this:
public interface IRenderer
{
void Draw(IShape shape);
}
public interface IShape
{
void Draw(IRenderer renderer);
}
public class Dx11Renderer : IRenderer
{
public void Draw(IShape shape)
{
shape.Draw(this);
}
}
public class GlRenderer : IRenderer
{
public void Draw(IShape shape)
{
shape.Draw(this);
}
}
public class Circle : IShape
{
public void Draw(IRenderer renderer)
{
if (renderer.GetType() == typeof(Dx11Renderer))
{
Console.WriteLine("Draw circle with DX11");
}
if (renderer.GetType() == typeof(GlRenderer))
{
Console.WriteLine("Draw circle with GL");
}
}
}
public class Square : IShape
{
public void Draw(IRenderer renderer)
{
if (renderer.GetType() == typeof(Dx11Renderer))
{
Console.WriteLine("Draw square with DX11");
}
if (renderer.GetType() == typeof(GlRenderer))
{
Console.WriteLine("Draw square with GL");
}
}
}
Hope this will help.
Related
I am trying to use virtual and abstract methods to make my game architecture better.
I'm using C# and Unity for this example.
I use a ShipComponent as a base Class because I want all the child classes to do the same thing.
But sometimes I want a certain ShipComponent to do something else.
The code will make it a lot clearer:
ShipComponent.cs:
public abstract class ShipComponent : MonoBehaviour
{
[HideInInspector] public ShipControl shipControl;
public virtual void Init(ShipControl control)
{
this.shipControl = control;
}
public virtual void IsPlayer()
{
SetListeners();
}
public abstract void IsNotPlayer();
public abstract void ReEnable();
public abstract void SetListeners();
}
One of the many child classes that inherits from ShipComponent:
public class Rudder : ShipComponent
{
[Header("Settings")]
public Transform rudder;
[Header("Debug Info")]
[SerializeField] float rudderSpeed;
[SerializeField][Range(-45, 45)] int setRudderAngle = 0;
[SerializeField][Range(-45f, 45f)] float realRudderAngle = 0f;
public override void Init(ShipControl shipControl)
{
base.Init(shipControl);
rudder = transform.GetChild(0).GetChild(4);
StartCoroutine(SmoothRudderChange());
SetListeners();
}
public override void IsPlayer()
{
base.IsPlayer();
}
public override void IsNotPlayer()
{
PlayerShipControl.OnRudderChange -= SetRudder;
}
public override void ReEnable()
{
StartCoroutine(SmoothRudderChange());
SetListeners();
}
public override void SetListeners()
{
PlayerShipControl.OnRudderChange -= SetRudder;
if (!shipControl.shipWrapper.ship.IsPlayer) return;
PlayerShipControl.OnRudderChange += SetRudder;
}
void OnDisable()
{
PlayerShipControl.OnRudderChange -= SetRudder;
StopAllCoroutines();
}
The main draw back I experience with this, is that I have to copy paste all 5 or 6 methods everytime I create a new ShipComponent class.
It seems messy and theres a lot of repeating code, most of the time the only difference in each ShipComponent is the SetListeners part, and StartCoroutines if any.
Is there a way to dynamically set delegate listeners up?
So I could set them in the base class ShipComponent?
Instead of setting each component individually?
Another script that inherits from ShipComponent for completeness:
public class Guns : ShipComponent
{
IEnumerator mouseAimCycle;
public override void Init(ShipControl shipControl)
{
base.Init(shipControl);
InitCannons();
SetListeners();
}
public override void ReEnable()
{
SetListeners();
}
public override void IsPlayer()
{
base.IsPlayer();
mouseAimCycle = AimCycle();
StartCoroutine(mouseAimCycle);
SetListeners();
}
public override void SetListeners()
{
PlayerShipControl.OnFireGuns -= TryFire;
if (!shipControl.shipWrapper.ship.IsPlayer) return;
PlayerShipControl.OnFireGuns += TryFire;
}
public override void IsNotPlayer()
{
StopCoroutine(mouseAimCycle);
PlayerShipControl.OnFireGuns -= TryFire;
}
void OnDisable()
{
PlayerShipControl.OnFireGuns -= TryFire;
StopAllCoroutines();
}
Calling the ShipComponent virtual and abstract methods:
public class ShipControl : MonoBehaviour
{
// Contains Ship + Cargo + Crew and a ref to this ShipControl
public ShipWrapper shipWrapper { get; private set; }
ShipComponent[] shipComponents;
// Gather all ShipComponents and Initialize them.
public void Start()
{
shipComponents = transform.GetComponents<ShipComponent>();
foreach (ShipComponent comp in shipComponents)
{
comp.Init(this);
}
}
// Call this to check if this is players current ship and set the components accordingly.
public void UpdateIsPlayer()
{
if (!shipWrapper.ship.IsPlayer)
foreach (ShipComponent component in shipComponents)
component.IsNotPlayer();
else
foreach (ShipComponent component in shipComponents)
component.IsPlayer();
}
And PlayerShipControl, which I use for input, broadcasting the input through delegates, and the theory is that only the players currently controlled ship will be listening for this input:
public class PlayerShipControl : MonoBehaviour
{
public static event Action<Transform> SetCamToPlayerShip;
public static event Action SetShipPanelUI;
public static event Action<bool> ToggleAnchorIcon, ToggleFlagIcon, ToggleAutofireIcon, ToggleBoatsIcon;
public static event Action OnFireGuns;
public static event Action<int> OnRudderChange;
public static event Action<int> OnSailStateChange;
public static event Action<bool> OnAllAnchorsCommand;
public static event Action<bool> OnAllBoatsCommand;
bool anchor, flag, autofire, boats;
ShipControl shipControl;
void Update()
{
if (Input.GetKeyUp(KeyCode.W)) // Raise Sails SailState++
{
OnSailStateChange?.Invoke(1);
}
if (Input.GetKeyUp(KeyCode.S)) // Furl Sails SailState--
{
OnSailStateChange?.Invoke(-1);
}
if (Input.GetKey(KeyCode.D))
{
OnRudderChange?.Invoke(1);
}
if (Input.GetKey(KeyCode.A))
{
OnRudderChange?.Invoke(-1);
}
if (Input.GetKeyDown(KeyCode.M))
{
OnRudderChange?.Invoke(0);
}
// Drop All Anchors
if (Input.GetKeyDown(KeyCode.V))
{
anchor = true;
ToggleAnchorIcon?.Invoke(anchor);
OnAllAnchorsCommand?.Invoke(anchor);
}
// Haul All Anchors
if (Input.GetKeyDown(KeyCode.H))
{
anchor = false;
ToggleAnchorIcon?.Invoke(anchor);
OnAllAnchorsCommand?.Invoke(anchor);
}
// Drop All Boats
if (Input.GetKeyDown(KeyCode.B))
{
boats = true;
ToggleBoatsIcon?.Invoke(boats);
OnAllBoatsCommand?.Invoke(boats);
}
// Take In All Boats
if (Input.GetKeyDown(KeyCode.U))
{
OnAllBoatsCommand?.Invoke(false);
// TO DO When all boats are back on deck, boatIcon + boatsBoolFlag should be turned off again.
}
if (Input.GetKeyDown(KeyCode.Space))
{
OnFireGuns?.Invoke();
}
}
}
Its a long string of scripts sometimes though so I have left out all the managers and such.
Ship ship inside shipWrapper.ship is a custom data class that stores the info about the ship, not a Monobehaviour, but it holds a bool called IsPlayer aswell. Nothing else of interest I can think of.
The main draw back I experience with this, is that I have to copy paste all 5 or 6 methods every time I create a new ShipComponent class. It seems messy and there's a lot of repeating code, most of the time the only difference in each ShipComponent is the SetListeners part, and StartCoroutines if any.
In the show example you have more differences between implementations then ones described. Without seeing the full code it is hard to suggest something meaningful.
Few notes on the current code:
In Rudder you don't need to specify IsPlayer because the following:
public override void IsPlayer()
{
base.IsPlayer();
}
does not add anything extra, so you can just skip implementation in the derived class.
Based on provided examples it seems that ReEnable can be defined as virtual in base class with default implementation set to calling SetListeners (the same approach as you have with Init and IsPlayer).
PlayerShipControl.Update possibly can be improved by moving handlers to dictionary. Something along this lines:
public class PlayerShipControl : MonoBehaviour
{
// ...
Dictionary<KeyCode, Action> keyActions = new() // not sure about the type
{
{ KeyCode.W, () => OnSailStateChange?.Invoke(1) },
// ...
{ KeyCode.V, () =>
{
anchor = true;
ToggleAnchorIcon?.Invoke(anchor);
OnAllAnchorsCommand?.Invoke(anchor);
}
},
// ...
};
void Update()
{
foreach (var kvp in keyActions)
{
if (Input.GetKeyUp(kvp.Key))
{
kvp.Value();
break;
}
}
}
}
The following is attached to my player and would call upon whatever object is hit to use the objects function. Think the player controls the pointing and clicking, but the object controls whatever the object will do, such as turn on a light.
void Interact()
{
RaycastHit interactablehit;
if (Physics.Raycast(PlayerCamera.transform.position, PlayerCamera.transform.forward, out interactablehit, MaxDistance))
{
// if raycast hits, then it checks if it hit an object with the tag Interactable.
if (interactablehit.transform.tag == "Interactable")
{
object = interactablehit.transform.name;
interactablehit.transform.gameObject.GetComponent<interactablehit.transform.name>().ObjectInteract();
}
}
}
This is a job for either an interface or a common base class:
public interface IInteractable
{
void ObjectInteract();
}
and then have your different implementations like e.g.
public class ExampleInteractable : MonoBevahiour, IInteractable
{
public void ObjectInteract()
{
Debug.Log("Hello!");
}
}
or
public class ToggleLightInteractable : MonoBevahiour, IInteractable
{
[SerializeField] private Light light;
public void ObjectInteract()
{
light.enabled = !light.enabled;
}
}
and then simply do
void Interact()
{
if (Physics.Raycast(PlayerCamera.transform.position, PlayerCamera.transform.forward, out var interactablehit, MaxDistance))
{
var interactable = interactablehit.transform.GetComponent<IInteractable>();
if (interactable != null)
{
interactable.ObjctInteract();
}
}
}
Or the same with a common base class, usefull if you want/need some shared behaviour or default implementations
public class BaseInteractable
{
public virtual void ObjectInteract()
{
Debug.Log("Hello!");
// For demo reasons lets say per default this can be interacted with only once
Destroy(this);
}
}
and then have your different implementations like e.g.
public class ExampleInteractable : BaseInteractable
{
public override void ObjectInteract()
{
// completely override the default behaviour
Debug.Log("Hello World!");
// this can be interacted with forever since the default behavior is not executed
}
}
or
public class ToggleLightInteractable : BaseInteractable
{
[SerializeField] private Light light;
public override void ObjectInteract()
{
// keep the default behavior and only extend it with something additional
base.ObjectInteract();
light.enabled = !light.enabled;
}
}
and then do
void Interact()
{
if (Physics.Raycast(PlayerCamera.transform.position, PlayerCamera.transform.forward, out var interactablehit, MaxDistance))
{
if (interactablehit.transform.TryGetComponent<BaseInteractable>(out var interactable))
{
interactable.ObjctInteract();
}
}
}
If you really really for what reason ever (there shouldn't be any good one) need to stick to string you can (you shouldn't) use SendMessage like
void Interact()
{
if (Physics.Raycast(PlayerCamera.transform.position, PlayerCamera.transform.forward, out var interactablehit, MaxDistance))
{
interactablehit.transform.gameObject.SendMessage(interactablehit.transform.name, options: SendMessageOptions.DontRequireReceiver);
}
}
which would require your object to be called exactly like the method you want to call, regardless of how the component is called.
How do I access the interface implementations from an object?
interface IGraphicsObject
{
Draw();
Delete();
}
I create 3 classes: Square, Circle and Triangle, all implementing IGraphicsObject. Then I do something like
object Shape = Activator.CreateInstance("myShapes", "Square");
Then I want to be able to type:
Shape.Draw();
Shape.Delete();
etc.
How do I do that?
Cast to IGraphicsObject
IGraphicsObject Shape = (IGraphicsObject)Activator.CreateInstance("myShapes", "Square");
With the created instance now, you can invoke the interface methods
Shape.Draw();
Shape.Delete();
The simple answer is to typecast it, but you can do it a bit better by:
public interface IGraphicsObject
{
void Draw();
void Delete();
}
public class Square : IGraphicsObject
{
public Square(string mysharpes, string square)
{
}
// fill out...
}
public class Circle : IGraphicsObject
{
public Circle(string mysharpes, string square)
{
}
// fill out...
}
public class Triangle : IGraphicsObject
{
public Triangle(string mysharpes, string square)
{
}
// fill out...
}
public class Main
{
public IGraphicsObject CreateInstance<T>(string mysharpes, string square) where T : IGraphicsObject
{
return (IGraphicsObject) Activator.CreateInstance(typeof(T), mysharpes, square);
}
public void Run()
{
var shape1 = CreateInstance<Square>("mysharpes", "square");
var shape2 = CreateInstance<Circle>("mysharpes", "square");
var shape3 = CreateInstance<Triangle>("mysharpes", "square");
Draw(shape1,shape2,shape3);
}
public void Draw( params IGraphicsObject[] shapes )
{
foreach( var shape in shapes )
shape.Draw();
}
}
So you ensure that only types that implement the interface is allowed for the typecasting creation method.
I want to use Dependency Injection in my Test Project. I am using Unity Container version 3.0 to achieve this. The problem I am facing is the object is not getting created. Below is the sample code (dummy code) -
Registration Code -
var container = new UnityContainer();
container.RegisterType<IShape, Circle>();
container.Resolve<Circle>();
Test Class code -
[TestClass]
public class UnitTest
{
private Drawing drawing = new Drawing();
[TestMethod]
public void Test1()
{
this.drawing.Draw();
}
}
Class Drawing Code -
public class Drawing
{
private IShape shape;
[Dependency]
public IShape Shape
{
get { return this.shape; }
set { this.shape = value; }
}
public void Draw()
{
this.shape.Draw(); // Error - object reference not set to instance of any object.
}
}
It looks like the Drawing object does not have the reference of the Shape object created by Unity. Is there any way I can achieve this?
I would use the TestInitialize attribute to create and configure a container to be used for the specific test:
[TestClass]
public class UnitTest
{
private IUnityContainer container;
[TestInitialize]
public void TestInitialize()
{
container = new UnityContainer();
container.RegisterType<IShape, Circle>();
}
[TestMethod]
public void Test1()
{
var drawing = container.Resolve<Drawing>();
// Or Buildup works too:
//
// var drawing = new Drawing();
// container.BuildUp(drawing)
this.drawing.Draw();
}
}
I'd like to know if I follow the right path, because I feel that the following code is wrong. Sorry, I didn't know how to name properly this question.
I have a certain ShapeEntity class that is used for loading data from DB. There are other concrete classes for Shape (I can have many of them in the future) so I want to employ LSP to draw these shapes, that's why I use IShape abstraction. I instantiate concrete shape objects by using DB info which is provided by ShapeEntity.
So my concern lies inside Main() function where I create these Shapes just using simple if-else. Is this correct approach to create "unknown" objects using if-else block? Maybe I could carry out creation of Shape objects to some kind of ShapeService? How could it be solved the other way?
public class ShapeEntity
{
int idShape { get; set; }
}
public interface IShape
{
void Draw();
}
public class Square : IShape
{
public void Draw() { }
}
public class Rectangle : IShape
{
public void Draw() { }
}
public class Canvas()
{
public static void Main()
{
List<IShape> Shapes = new List<IShape>();
foreach(ShapeEntity ShapeItem in ShapeRepository.GetAll())
{
if(ShapeItem.idShape == 1)
{
Shapes.Add(new Square());
}
else if(ShapeItem.idShape == 2)
{
Shapes.Add(new Rectangle());
}
}
}
public void DrawShapesOnCanvas(IList<IShape> Shapes)
{
foreach(IShape Shape in Shapes)
{
Shape.Draw();
}
}
}
You should consider using Factory pattern and instead using Id you should use enum
Example:
public class ShapeFactory
{
public static IShape GetShape(ShapeType shapeType)
{
switch (shapeType)
{
case ShapeType.Square:
return new Square();
case ShapeType.Rectangle:
return new Rectangle();
default:
break;
}
return null;
}
}
public enum ShapeType
{
Square,
Rectangle
}