Как сделать определенный заказ для вызова методов? - PullRequest
1 голос
/ 13 мая 2019

Например, у меня есть класс с интерфейсом, который имеет несколько методов. Каков наилучший способ вызывать методы всегда только в определенном порядке в классе?

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

}

Ответы [ 4 ]

3 голосов
/ 13 мая 2019

Взгляните на Шаблон разработки метода шаблона

Цель шаблона разработки метода шаблона состоит в определении каркаса алгоритма в операции, откладывая некоторые шаги для клиентаподклассы.Шаблонный метод позволяет подклассам переопределять определенные шаги алгоритма без изменения структуры алгоритма.

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();
2 голосов
/ 13 мая 2019

Нормально писать код, поэтому методы должны запускаться в определенном порядке. Но в этом случае мы бы не хотели просто показывать все методы и ожидать, что вызывающая сторона просто «знает», чтобы запустить их в определенном порядке. Если что-то требуется, то мы должны как-то обеспечить это.

Если методы интерфейса могут быть выполнены в любом порядке, но в одном конкретном случае мы хотим запустить их в определенном порядке, это просто. Мы просто делаем их в нужном нам порядке:

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

Если методы всегда должны выполняться в определенном порядке, то нет смысла выставлять интерфейс, который позволяет нам выполнять их в любом порядке. Интерфейс должен описывать, как мы хотим использовать класс. В этом случае это будет иметь больше смысла:

public interface IDoesSomething
{
    void DoSomething();
}

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

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

Теперь интерфейс сообщает другим классам, как с ним взаимодействовать, но детали как , который выполняется, который включает в себя определенную последовательность шагов, инкапсулированы (скрыты) внутри реализации этого класса.

Мы все еще делаем то же самое - разбивая процесс на шаги - но выбирая, сколько из этого мы выставляем вне класса. Мы облегчаем правильное использование нашего класса, делая невозможным его неправильное использование.

1 голос
/ 14 мая 2019

Насколько я понимаю, Template method это не то, что вы ищете. (Если вы не один из тех неприятных людей, которые используют ответы, не принимая их ;) ) Если вы хотите создать иллюзию свободы для пользователя и наказать ее за неправильное использование, это можно сделать следующим образом.

Определить атрибут:

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

Затем определите интерфейс:

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
}

И реализовать его в классе, вызывая сначала CheckOrder() в каждом методе:

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");
    }
}

, где CheckOrder():

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());
    }
}

Конечно, вы можете сделать это в неполном классе.

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)

отлично работает, но

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

бросает

Неправильный порядок вызова метода. Метод 'Method2' с заказами [1]. Ожидаемый заказ 2.

0 голосов
/ 13 мая 2019

Прежде всего, я думаю, что вы перепутали некоторые концепции, класс реализует интерфейс, у вас не может быть класса интерфейса.Реализуя интерфейс, вы гарантируете, что потребительский класс интерфейса должен реализовать сигнатуру этого метода в своем коде.

Во-вторых, невозможно выполнить методы в определенном порядке, если они находятся винтерфейс, это потому, что методы интерфейса (не сам код каждого метода, интерфейс не имеет никакой логики).Вероятно, то, что вы ищете здесь, это класс (может быть абстрактным, но не уверен, зачем вам нужен интерфейс), и вы можете иметь эти 3 метода в качестве приватных членов и иметь открытый метод, который выполняет 3 из них.Как это:

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();
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...