3

So a simple example i have:

public interface IFollow{
    Transform Target {get;}
    void LateUpdate();
}

public A : Monobehaviour , IFollow {

   public Transform Target {get; set;}
   public void LateUpdate(){
      //follow the target
   }
}

public B : Monobehaviour , IFollow {

   public Transform Target {get; set;}
   public void LateUpdate(){
      //follow the target [duplicate code as seen in A]
}

When you have lots of different objects that follow a target with the same logic the implemented interface forces you to write a lot of duplicate code.

It also doesn't make much sense to do it via a base class since not all objects will follow.

What's a cleaner way to do this without so much duplication ?

Encase any one wondered, Monobehaviour is a Unity3D thing, though not overly relevant to the question but thought i'd mention it.

  • There's not enough information in your question to determine why you have a lot of code duplication. – Robert Harvey Jul 10 '18 at 1:19
  • Because every object that implements the interface will all have identical LateUpdate follow logic. I'm literally copy pasting the follow logic to each class that requires it. Its kinda silly. Not sure why you're confused by that. – WDUK Jul 10 '18 at 1:22
  • Then why aren't you using a base class to capture the common functionality? You can't have it both ways. Either you use a base class and put the common functionality there even though some of it won't be used, or you live with the duplication. Perhaps work with two or three more specific base classes? – Robert Harvey Jul 10 '18 at 2:25
  • Because if its in the base class then all objects follow but i said in the question only some do. An interface seems more logical to me. Thats entirely why i'm asking for advice here. Because i don't know the best approach. Also what would the base class even be when it would only have a follow behaviour in it? Doesn't feel like an is an setup for a base class then. – WDUK Jul 10 '18 at 2:39
  • Composition, then. The best approach is the one that most effectively meets your needs. – Robert Harvey Jul 10 '18 at 2:53
5

You have two options:

  • Implement in a(n) (abstract) base class
  • Implement in a helper adapter

Make a base class, or an abstract base class that implements the common behavior, and inherit from that. This way your code lives in one place and isn't duplicated.

If a base class doesn't work for some reason, you could put the code into a adapter (or strategy) object that each class uses to implement the behavior, again moving the duplicated code to one place.

| improve this answer | |
  • 1
    Do you have an example for the adaptor setup so i understand what you mean ? – WDUK Jul 10 '18 at 19:15
  • the adapter pattern is a has a relationship. You would make a class like Follower : IFollow that has an implementation that can be used to implement IFollow by wrapper/passthrough to the adapter or strategy (a very similar pattern) – esoterik Jul 10 '18 at 19:26
  • 1
    Base classes are appropriate in some cases, but do need to be considered carefully, especially in languages without multiple inheritence. There is a maxim that says favour composition over inheritance – tomRedox Jan 10 '19 at 9:21
2

You can first extract the common behavior(s) into simple classes which only implement IFollow:

public sealed class FollowTarget : IFollow
{
    public Transform Target {get; set;}
    public void LateUpdate() { //follow the target }
}

public sealed class FollowSomethingElse : IFollow
{
    public Transform Target {get; set;}
    public void LateUpdate() { //follow something else }
}

Then use the required implementation in A, B, C:

public sealed A : Monobehaviour , IFollow {
    private readonly IFollow _follow;
    public A() => _follow = new FollowTarget();
    public Transform Target {get => _follow.Target; set => _follow.Target = value; }
    public void LateUpdate() => _follow.LateUpdate();
}

public sealed B : Monobehaviour , IFollow {
    private readonly IFollow _follow;
    public B() => _follow = new FollowTarget();
    public Transform Target {get => _follow.Target; set => _follow.Target = value; }
    public void LateUpdate() => _follow.LateUpdate();
}

public sealed C : Monobehaviour , IFollow {
    private readonly IFollow _follow;
    public C() => _follow = new FollowSomethingElse();
    public Transform Target {get => _follow.Target; set => _follow.Target = value; }
    public void LateUpdate() => _follow.LateUpdate();
}

Then at this point you may see that A/C or B/C share the same implementation except that you don't instanciate the same _follow. In this case you can require an IFollow in the constructor and refactor (simplify) your code like this:

public sealed AorC : Monobehaviour , IFollow {
    private readonly IFollow _follow;
    public AorC(IFollow follow) => _follow = follow;
    public Transform Target {get => _follow.Target; set => _follow.Target = value; }
    public void LateUpdate() => _follow.LateUpdate();
}

public sealed B : Monobehaviour , IFollow {
    private readonly IFollow _follow;
    public B() => _follow = new FollowTarget();
    public Transform Target {get => _follow.Target; set => _follow.Target = value; }
    public void LateUpdate() => _follow.LateUpdate();
}

Usage beeing

var a = new AorC(new FollowTarget()); 
var c = new AorC(new FollowSomethingElse());

This approach is usually named Decorator and is a specialized form of composition which fosters a more composable code.

| improve this answer | |
1

You could remove the method (but not the interface) from the class, and move the logic to an extension method:

public interface IFollow
{
    Transform Target {get;}
}

static public class ExtensionMethods
{
     static public void LateUpdate<T>(this T source) where T : IFollow
     {
         var target = source.Target; //Works, due to the type constraint
         //Add code to follow the target here
     }
}

public A : Monobehaviour , IFollow
{
    public Transform Target {get; set;}
}

public B : Monobehaviour , IFollow
{
    public Transform Target {get; set;}
}

You'd still call it exactly the same way as before, only you need to remember to include the namespace of your extension method (if it is different):

A a = new A();
a.LateUpdate();

You could also combine these methods; for example, you could leave LateUpdate() in the interface, but write the implementation as a pass-through that only calls the extension method behind the scenes. This would be valuable if you need the method in the interface for some reason.

| improve this answer | |
  • What if LateUpdate has arguements i'm not sure of the syntax in that case? Some times it passes a delta time for example LateUpdate(Timer deltaTime); – WDUK Jul 10 '18 at 4:51
  • the extension method cant do everything a method could do – Ewan Jul 10 '18 at 6:40
  • OP said that "not all objects follows", which is his counterpoint to a base class, but an extension method would automatically apply to everything IFollow. Not exactly an improvement over a base class implementation. The "pass-through" suggestion is also questionable. If you put it in the interface, you'll be forced to implement it in every class, or use a base class anyway, and it will also cause you some confusion (now you have multiple methods matching the IFollow.LateUpdate() call, even if only the proper class method will ever be called). – BgrWorker Jul 10 '18 at 7:02
  • @BgrWorker I interpret the requirement as "allow a series of classes to expose IFollow without requiring duplicate implementation of LastUpdate()" which I believe this answer achieves. I wouldn't recommend adopting this as a common pattern, but maybe in a corner case it can help. The pass-through suggestion helps when the implementation is non-trivial or if there are several classes that expose IFollow and keeping their code in sync would be a chore.. – John Wu Jul 10 '18 at 7:29
1

C# 8 now supports default interfaces. So you could write it like this:

public interface IFollow
{
    Transform Target { get; }
    public void LateUpdate() {
        Console.WriteLine("Follow the target");
    }
}

public class A : Monobehaviour, IFollow
{
    public Transform Target { get; set; }       
}

public class B : Monobehaviour, IFollow
{

    public Transform Target { get; set; }   
}
| improve this answer | |
0

You would move the code to another component that you call.

public class RepeatingCode : MonoBehaviour
{
    void RepeatThis() { }
}
public A : Monobehaviour, IFollow {

   public Transform Target {get; set;}
   private RepeatingCode Repeating;

   void Awake() => Repeating = GetComponent<RepeatingCode>();

   public void LateUpdate()
   {
      Repeating.RepeatThis();
   }
}

public B : Monobehaviour, IFollow {

   public Transform Target {get; set;}
   public RepeatingCode Repeating; // Assigned using Inspector

   public void LateUpdate()
   {
      Repeating.RepeatThis();
   }
}
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.