Как вы заставляете подписи конструктора и статические методы?
Есть ли способ заставить (дочерний) класс иметь конструкторы с конкретными сигнатурами или определенными статическими методами в C# или Java?
Очевидно, вы не можете использовать интерфейсы для этого, и я знаю, что он будет иметь ограниченное использование. Один случай, в котором я нахожу это полезным, - это когда вы хотите применить некоторые рекомендации по проектированию, например:
Исключения
У них всех должны быть четыре канонических конструктора, но нет способа обеспечить их соблюдение. Вы должны положиться на такой инструмент, как FxCop (C# case), чтобы поймать их.
операторы
Нет контракта, в котором указано, что два класса могут быть суммированы (с оператором + в C#)
Есть ли шаблон дизайна, чтобы обойти это ограничение? Какую конструкцию можно добавить в язык, чтобы преодолеть это ограничение в будущих версиях C# или Java?
9 ответов
Не применяется во время компиляции, но я потратил много времени на изучение подобных проблем; в MiscUtil доступны универсальная математическая библиотека с поддержкой и эффективный (не по умолчанию) API-интерфейс ctor. Однако они проверяются только при первом использовании во время выполнения. На самом деле это не большая проблема - ваши юнит-тесты должны очень быстро найти пропавшего оператора / ctor. Но это работает и очень быстро...
Используя дженерики, вы можете заставить аргумент типа иметь конструктор без параметров - но это предел.
За исключением обобщений, было бы сложно использовать эти ограничения, даже если они существуют, но иногда это может быть полезно для параметров / аргументов типа. Разрешение статических членов в интерфейсах (или, возможно, статических интерфейсов) может также помочь с проблемой "универсального числового оператора".
Я писал об этом недавно, когда столкнулся с подобной проблемой.
Вы можете использовать шаблон 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.
Силовые Конструкторы
Ты не можешь Самое близкое, что вы можете сделать, - это сделать конструктор по умолчанию закрытым, а затем предоставить конструктор с параметрами. Но у этого все еще есть лазейки.
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, которая сделала это. Фреймворк AOP, такой как PostSharp, может помочь вам сделать это во время компиляции, поддерживая свою модель задач сборки.
Но что не так с анализом кода или принудительного исполнения? Может быть, это просто предпочтение, и я уважаю это, но лично у меня нет проблем с тем, чтобы CA/FXCop проверял эти вещи... и если вы действительно хотите заставить нижестоящие реализации ваших классов иметь сигнатуры конструктора, вы всегда можете добавить правила run- проверка времени в конструкторе базового класса с использованием отражения.
Ричард
Я не уверен, что вы пытаетесь достичь, не могли бы вы уточнить? Единственная причина для принудительного использования определенного конструктора или статического метода в разных классах - это попытка их динамического выполнения во время выполнения, это правильно?
Конструктор предназначен для конкретного класса, так как он предназначен для инициализации конкретных потребностей класса. Насколько я понимаю, причина, по которой вы хотите применить что-то в иерархии классов или интерфейсе, заключается в том, что это действие / операция, относящаяся к выполняемому процессу, но может варьироваться в разных обстоятельствах. Я полагаю, что это предполагаемое преимущество полиморфизма, которого невозможно достичь с помощью статических методов.
Это также потребовало бы знания конкретного типа класса, для которого вы хотите вызвать статический метод, что сломало бы все полиморфное сокрытие различий в поведении, которого пытается достичь интерфейс или абстрактный класс.
Если поведение, представляемое конструктором, предназначено для того, чтобы быть частью контракта между клиентом этих классов, я бы добавил его явно в интерфейс.
Если к иерархии классов предъявляются аналогичные требования инициализации, я бы использовал абстрактный базовый класс, однако именно наследующим классам следует доказать, как они находят параметр для этого конструктора, что может включать в себя экспонирование аналогичного или идентичного конструктора.
Если это предназначено для того, чтобы позволить вам создавать разные экземпляры во время выполнения, я бы рекомендовал использовать статический метод в абстрактном базовом классе, который знает различные потребности всех конкретных классов (для этого можно использовать внедрение зависимостей).