nevermind18
nevermind18

Reputation: 31

How to make a specific order for calling methods?

For example, I have a class with interface, which has few methods. What is the best way to call methods always only in a specific order in the class?

public class SomeClass
{
    void Start(ISomeInterface testClass)
    {
        testClass.Method1();
        testClass.Method2();
        testClass.Method3();
    }
}

public interface ISomeInterface
{
    void Method1();//should run 2nd

    void Method2();// 1st

    void Method3();// 3rd

}

Upvotes: 1

Views: 1863

Answers (5)

Sjoerd
Sjoerd

Reputation: 75619

Only expose the callable methods in an interface, and return a new interface when the method has been called.

interface IMethod1 {
    IMethod2 Method1();
}

interface IMethod2 {
    IMethod3 Method2();
}

Initially, you return a IMethod1. This only exposes Method1(), so it's not possible to call Method2 out of order. When calling Method1(), it returns an IMethod2 that exposes Method2(), so that can be called.

These interfaces can be implemented by the same class, which exposes only some methods at a time through the various interfaces.

Edit: I wrote a blog post about this: Enforcing object lifecycles through interfaces

Upvotes: 1

CSDev
CSDev

Reputation: 3235

As far as I see, Template methodis not what you are looking for. (Unless you are one of those unpleasant people using answers without accepting ones ;)) If you'd like to give an illusion of freedom to a user and to punish one for using it wrong way, it could be done the following way.

Define an attribute:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class OrderAttribute : Attribute
{
    public int Order { get; }
    public OrderAttribute(int order) => Order = order;
}

Then define an interface:

public interface IObeyOrder
{
    [Order(2)]
    [Order(4)]
    void Method1(); // should run 2nd or 4th

    [Order(1)]
    void Method2(); // 1st

    [Order(3)]
    void Method3(); // 3rd

    void Method4(); // order doesn't matter
}

And implement it on a class, calling CheckOrder() first in each method:

public partial class ObeyOrder : IObeyOrder
{
    public void Method1()
    {
        CheckOrder();
        Console.WriteLine("Method1");
    }

    public void Method2()
    {
        CheckOrder();
        Console.WriteLine("Method2");
    }

    public void Method3()
    {
        CheckOrder();
        Console.WriteLine("Method3");
    }

    public void Method4()
    {
        CheckOrder();
        Console.WriteLine("Method4");
    }

    public void Method5() // non-interface
    {
        CheckOrder();
        Console.WriteLine("Method5");
    }
}

where CheckOrder() is:

public partial class ObeyOrder : IObeyOrder
{
    private static readonly Dictionary<string, int[]> orderedMethods = OrderHelper<IObeyOrder>.OrderedMethods;

    private readonly Queue<int> orders = new Queue<int>(orderedMethods.Values.SelectMany(i => i).OrderBy(i => i));

    private void CheckOrder([CallerMemberName] string methodName = "")
    {
        if (!orderedMethods.TryGetValue(methodName, out var methodOrders))
            return;

        var order = orders.Peek();
        if (!methodOrders.Contains(order))
            throw new Exception($"Wrong method call order. Method '{methodName}' with orders [{string.Join(", ", methodOrders)}]. Expected order {order}.");

        orders.Enqueue(orders.Dequeue());
    }
}

Of course, you can do it in a non-partial class.

public static class OrderHelper<T>
{
    public static Dictionary<string, int[]> OrderedMethods { get; } = typeof(T)
        .GetMethods()
        .Select(method => new
        {
            Method = method.Name,
            Orders = method.GetCustomAttributes(typeof(OrderAttribute), false)
                           .Cast<OrderAttribute>()
                           .Select(attribute => attribute.Order)
                           .ToArray()
        })
        .Where(method => method.Orders.Length > 0)
        .ToDictionary(method => method.Method, method => method.Orders);
}

Usage:

var obeyOrder = new ObeyOrder();
obeyOrder.Method2(); // should go 1st
obeyOrder.Method4(); // can go whenever, since there is no order attribute
obeyOrder.Method1(); // should go 2nd or 4th
obeyOrder.Method5(); // can go whenever, since it's non-interface
obeyOrder.Method3(); // should go 3rd
obeyOrder.Method1(); // should go 2nd or 4th
obeyOrder.Method2(); // should go 1st (after the last had been already called)

works fine, but

var obeyOrder = new ObeyOrder();
obeyOrder.Method2(); // should go 1st
obeyOrder.Method4(); // can go whenever, since there is no order attribute
obeyOrder.Method1(); // should go 2nd or 4th
obeyOrder.Method5(); // can go whenever, since it's non-interface
obeyOrder.Method3(); // should go 3rd
obeyOrder.Method1(); // should go 2nd or 4th
obeyOrder.Method2(); // should go 1st (after the last had been already called)
obeyOrder.Method2(); // should throw since the 2nd (obeyOrder.Method1()) is expected

throws

Wrong method call order. Method 'Method2' with orders [1]. Expected order 2.

Upvotes: 2

Jonatan Dragon
Jonatan Dragon

Reputation: 5017

Take a look at Template Method Design Pattern

The intent of Template Method Design Pattern is to define the skeleton of an algorithm in an operation, deferring some steps to client subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.

abstract class SomeClass : ISomeInterface
{
    public abstract void Method1();

    public abstract void Method2();

    public abstract void Method3();

    // The template method
    public void Start()
    {
        testClass.Method1();
        testClass.Method2();
        testClass.Method3();
    }
}

class ImplementationClass : SomeClass
{
    public override void Method1()
    {
        ...
    }

    public override void Method2()
    {
        ...
    }

    public override void Method3()
    {
        ...
    }
}

// Usage
var implementationClass = new ImplementationClass();
implementationClass.Start();

Upvotes: 4

Scott Hannen
Scott Hannen

Reputation: 29222

It's normal to write code so that methods are expected to run in certain order. But in that case we wouldn't want to just expose all of the methods and expect the caller to just "know" to run them in a certain order. If something is required then we must somehow enforce it.

If the methods of an interface can be executed in any order but in one specific case we want to run them in a particular order, that's easy. We just do them in the order we want:

    testClass.Method2();
    testClass.Method1();
    testClass.Method3();

If methods must always be executed in a particular order then it doesn't make sense to expose an interface that allows us to execute them in just any order. The interface should describe how we want the class to be used. In that case this would make more sense:

public interface IDoesSomething
{
    void DoSomething();
}

public class DoesSomething : IDoesSomething
{
    public void DoSomething()
    {
        DoAnotherThing();
        DoOneThing();
        SomethingElse();
    }

    private void DoOneThing(){}
    private void DoAnotherThing(){}
    private void SomethingElse(){}
}

Now the interface tells other classes how to interact with it, but the details of how that gets done, which includes a particular sequence of steps, is encapsulated (hidden) inside the implementation of that class.

We're still doing the same thing - breaking a process into steps - but choosing how much of it we expose outside the class. We're making it easier to use our class correctly by making it impossible to use it incorrectly.

Upvotes: 3

nalnpir
nalnpir

Reputation: 1197

First of all i think you got some concepts mixed up, a class implements an interface, you cannot have an interface class. What you do by implementing an interface is ensure that the consumer class of the interface has to implement that method signature in his code.

Secondly, there is no way to execute methods in a certain order if they re in an interface, this is because interface methods (not the code itself from each method, an interface does NOT HAVE ANY LOGIC on it). Probably what you are looking for here is class (can be abstract not sure why do you need an interface though), and you could have this 3 methods as private members of it and have a public method that executes the 3 of them. Like this:

public class Example
{

    private void MethodA()
    {
        //logic from methodA
    }

    private void MethodB()
    {
        //logic from methodB
    }

    private void MethodC()
    {
        //logic from methodC
    }

    public void MethodA()
    {
        MethodB();
        MethodA();
        MethodC();
    }
}

Upvotes: 1

Related Questions