Как вы заставляете подписи конструктора и статические методы? - PullRequest
24 голосов
/ 02 октября 2008

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

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

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

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

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

Ответы [ 9 ]

10 голосов
/ 02 октября 2008

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

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

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

8 голосов
/ 02 октября 2008

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

6 голосов
/ 02 октября 2008

Вы можете использовать шаблон 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
   }
}

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

4 голосов
/ 02 октября 2008

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

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

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
}
1 голос
/ 02 октября 2008

К сожалению, вы не можете в 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();
}
1 голос
/ 02 октября 2008

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

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

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; 
1 голос
/ 02 октября 2008

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

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

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

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

0 голосов
/ 02 октября 2008

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

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

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

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

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

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

0 голосов
/ 02 октября 2008

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

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

Richard

...