Как принудительно использовать подписи конструкторов и статические методы?

StackOverflow https://stackoverflow.com/questions/161231

Вопрос

Есть ли способ заставить (дочерний) класс иметь конструкторы с определенными сигнатурами или определенными статическими методами в C# или Java?

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

Исключения
Все они должны иметь четыре канонических конструктора, но невозможно обеспечить их соблюдение.Чтобы их обнаружить, вам придется полагаться на такой инструмент, как FxCop (случай C#).

Операторы
Не существует контракта, указывающего, что два класса могут быть суммированы (с помощью оператора+ в C#).

Есть ли какой-либо шаблон проектирования, позволяющий обойти это ограничение?Какую конструкцию можно добавить в язык преодолеть это ограничение в будущих версиях C# или Java?

Это было полезно?

Решение

Не применяется во время компиляции, но я потратил много времени на изучение подобных проблем;а математическая библиотека с поддержкой обобщенных функций, и эффективный (не по умолчанию) API ctor доступны в РазноеUtil.Однако они проверяются только при первом использовании во время выполнения.На самом деле это не большая проблема — ваши модульные тесты должны очень быстро найти недостающий оператор/ктор.Но это работает, и очень быстро...

Другие советы

Используя дженерики, вы можете заставить аргумент типа иметь конструктор без параметров, но это почти предел.

За исключением дженериков, было бы сложно на самом деле использовать эти ограничения, даже если они существовали, но иногда могут быть полезны для параметров/аргументов типа.Разрешение статических членов в интерфейсах (или, возможно, статических интерфейсах) также может помочь решить проблему «общего числового оператора».

я написал об этом некоторое время назад, когда столкнулся с подобной проблемой.

Вы можете использовать шаблон Factory.

interface Fruit{}

interface FruitFactory<F extends Fruit>{
   F newFruit(String color,double weight);

   Cocktail mixFruits(F f1,F f2);
}

Затем вы можете создавать классы для любого типа фруктов.

class Apple implements Fruit{}
class AppleFactory implements FruitFactory<Apple>{
   public Apple newFruit(String color, double weight){
       // create an instance
   }
   public Cocktail mixFruits(Apple f1,Apple f2){
       // implementation
   }
}

Это не означает, что вы не можете создать экземпляр другим способом, кроме использования Фабрики, но, по крайней мере, вы можете указать, какие методы вы будете запрашивать у Фабрики.

Конструкторы Силы

Вы не можете.Самое близкое, что вы можете сделать, — это сделать конструктор по умолчанию закрытым, а затем предоставить конструктор с параметрами.Но в нем все еще есть лазейки.

class Base
{
  private Base() { }
  public Base(int x) {}
}

class Derived : Base
{
  //public Derived() { } won't compile because Base() is private
  public Derived(int x) :base(x) {}
  public Derived() : base (0) {} // still works because you are giving a value to base
}

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

Статические методы — это просто глобальные методы с пространством имен, они на самом деле не «принадлежат» классу, в котором они определены (хорошо, у них есть доступ к частным (статическим) методам в классе, но это все).

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

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

Вот я бы решил эту проблему, если бы был языковым дизайнером.

Разрешить интерфейсам включать статические методы, операторы и конструкторы.

interface IFoo  
{  
  IFoo(int gottaHaveThis);  
  static Bar();  
}

interface ISummable
{
      operator+(ISummable a, ISummable b);
}

Не разрешать соответствующие new IFoo(someInt) или IFoo.Bar()

Разрешить наследование конструкторов (как и статических методов).

class Foo: IFoo
{
  Foo(int gottaHaveThis) {};
  static Bar() {};
}

class SonOfFoo: Foo 
{
  // SonOfFoo(int gottaHaveThis): base(gottaHaveThis); is implicitly defined
}

class DaughterOfFoo: Foo
{
  DaughhterOfFoo (int gottaHaveThis) {};
}

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

ISummable PassedFirstGrade = (ISummable) 10; 

К сожалению, вы не можете этого сделать на C#.Но вот удар по этому поводу:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(Foo.Instance.GetHelloWorld());
        Console.ReadLine();
    }
}

public class Foo : FooStaticContract<FooFactory>
{
    public Foo() // Non-static ctor.
    {
    }

    internal Foo(bool st) // Overloaded, parameter not used.
    {
    }

    public override string GetHelloWorld()
    {
        return "Hello World";
    }
}

public class FooFactory : IStaticContractFactory<Foo>
{
    #region StaticContractFactory<Foo> Members

    public Foo CreateInstance()
    {
        return new Foo(true); // Call static ctor.
    }

    #endregion
}

public interface IStaticContractFactory<T>
{
    T CreateInstance();
}

public abstract class StaticContract<T, Factory>
    where Factory : IStaticContractFactory<T>, new() 
    where T : class
{
    private static Factory _factory = new Factory();

    private static T _instance;
    /// <summary>
    /// Gets an instance of this class. 
    /// </summary>
    public static T Instance
    {
        get
        {
            // Scary.
            if (Interlocked.CompareExchange(ref _instance, null, null) == null)
            {
                T instance = _factory.CreateInstance();
                Interlocked.CompareExchange(ref _instance, instance, null);
            }
            return _instance;
        }
    }
}

public abstract class FooStaticContract<Factory>
    : StaticContract<Foo, Factory>
    where Factory : IStaticContractFactory<Foo>, new() 
{
    public abstract string GetHelloWorld();
}

Что ж, из формулировки вашего вопроса я знаю, что вы ищете соблюдение правил во время компиляции.Если у кого-то другого нет блестящего предложения/хака, который позволит вам сделать это так, как вы предполагаете, должен делать компилятор, я бы посоветовал вам написать специальную задачу MSbuild, которая делала бы это.Фреймворк АОП, такой как PostSharp, может помочь вам добиться этого во время компиляции, используя модель задачи сборки.

Но что не так с анализом кода или соблюдением требований во время выполнения?Возможно, это просто предпочтения, и я уважаю это, но лично у меня нет проблем с тем, чтобы CA/FXCop проверял эти вещи...и если вы действительно хотите заставить нижестоящих разработчиков ваших классов иметь подписи конструктора, вы всегда можете добавить проверку времени выполнения правил в конструктор базового класса с помощью отражения.

Ричард

Я не уверен, чего вы пытаетесь достичь, не могли бы вы рассказать поподробнее?Единственная причина использования определенного конструктора или статического метода в разных классах — попытаться выполнить их динамически во время выполнения, правильно ли это?

Конструктор предназначен для конкретного класса, поскольку он предназначен для инициализации конкретных потребностей класса.Насколько я понимаю, причина, по которой вы хотели бы применить что-то в иерархии классов или интерфейсе, заключается в том, что это действие/операция, относящаяся к выполняемому процессу, но может варьироваться в зависимости от обстоятельств.Я считаю, что это предполагаемое преимущество полиморфизма, которого невозможно достичь с помощью статических методов.

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top