Абстрактный статический метод — как?

Уже есть несколько вопросов SO о том, почему нет абстрактного статического метода/поля как такового, но мне интересно, как можно реализовать следующий псевдокод:

class Animal {
    abstract static int getNumberOfLegs(); // not possible
}

class Chicken inherits Animal {
    static int getNumberOfLegs() { return 2; }


class Dog inherits Animal {
    static int getNumberOfLegs() { return 4; }

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

Без «абстрактного статического» метода/поля можно либо исключить метод из класса Animal, тогда существует риск того, что какой-то дочерний класс не будет иметь этого метода. Или можно сделать getNumberOfLegs методом экземпляра, но тогда нужно будет создать экземпляр класса, чтобы узнать, сколько ног у этого животного, хотя в этом нет необходимости.

Как обычно реализуют эту ситуацию?


РЕДАКТИРОВАТЬ: Вот как я могу это использовать. Предположим (теперь это смешно, но в любом случае...), что количество ног каждого животного уникально, поэтому у меня может быть что-то вроде:

Animal getModelAnimal(int numberOfLegs) {
   if (numberOfLegs == Chicken.getNumberOfLegs()) return new Chicken();
   else if (numberOfLegs == Dog.getNumberOfLegs()) return new Dog();
}

person polyglot    schedule 29.05.2010    source источник


Ответы (11)


Как обычно реализуют эту ситуацию?

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

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

Это категорически не очевидно! Мы не программируем идеальный мир, и в реальном мире четвероногие животные иногда имеют одну, две или три (или пять) ног.

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

class AnimalDefinition {
    public string getScientificName();
    public string getCommonName();
    public int    getNumberOfLegs();
    public bool   getIsAmphibious();
    // etc.
}

Затем инициализируйте набор из них в начале вашей программы — в идеале из базы данных или файла конфигурации, куда вы можете добавить определения животных без написания или компиляции еще одной строки кода. (И вы можете обойтись гораздо меньшим количеством типов.)

person Jeff Sternal    schedule 30.05.2010

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

«Абстрактный метод требует реализации для каждого экземпляра. Статические методы относятся ко всему классу. Статический метод в абстрактном классе принадлежит к абстрактному классу, а не к потенциальным реализациям. Поэтому не имеет смысла разрешать абстрактные статические методы. статические методы не могут быть переопределены, так что опять же, абстрактные статические методы были бы аномалией».

Из http://forums.sun.com/thread.jspa?threadID=597378

Также посмотрите Почему я не могу определить статический метод в интерфейсе Java?

person Christian Neverdal    schedule 29.05.2010
comment
-1: Вообще так не скажешь. абстрактный метод не всегда бессмыслен (см. мой пост). - person Simon; 30.05.2010
comment
Отредактировал мой ответ, чтобы отразить это. - person Christian Neverdal; 30.05.2010
comment
Мой вопрос не зависит от языка (раньше я сталкивался с аналогичной проблемой на С#). Я понимаю, почему в языке нет абстрактного статического модификатора, но мне больше интересно посмотреть, как можно чисто реализовать проблему, которую я указал в вопросе. - person polyglot; 30.05.2010

Это действительно хороший момент, и иногда abstract static действительно не хватает. Однако, поскольку в настоящее время память не является проблемой, вы, безусловно, можете реализовать метод getNumberLegs() в качестве метода экземпляра.

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

Также неверно утверждать, что static методы нельзя переопределить; Методы final нельзя переопределить. В таких языках, как Java и C#, static идет с final. Вот почему многие предполагают, что static означает «не переопределяемый».

Говоря о С# (прочитав ваши комментарии, я предполагаю, что вы «говорите» на С#), вы можете рассмотреть возможность использования дженериков и атрибутов (или дженериков и аннотаций в Java):

public class Animal
{
   public static int GetNumberOfLegs<T>() where T : Animal
   {
     //Get T's custom attribute "NumberOfLegs" and return its value 
   }

   //EDIT: Added runtime-version of GetNumberOfLegs.
   public static int GetNumberOfLegs(Type t)
   {
     //Get t's custom attribute "NumberOfLegs" and return its value 

   }
}

[NumberOfLegs(4)]
public class Cat { ... };

Это позволит вам получить количество ветвей каждого типа без его создания. Только не забудьте указать атрибут [NumberOfLegs(x)]. Вы также должны знать тип во время компиляции (для универсальной версии метода).

РЕДАКТИРОВАТЬ: я добавил исполняемую версию метода GetNumberOfLegs(), которому вы можете передать объект Type (должен быть Class для Java). В этом случае вам нужно будет выполнить проверку типа во время выполнения, то есть проверить, наследуется ли тип, представленный объектом Type-/Class, от Animal, а затем получить значение, переданное в атрибуте/аннотации.

Использование:

int numberOfLegs1 = Animal.GetNumberOfLegs<Cat>(); 
int numberOfLegs2 = Animal.GetNumberOfLegs(typeof(Cat)); //runtime version
person Simon    schedule 29.05.2010
comment
Это не сравнение... Я показываю, что абстрактная статика не бессмысленна. Я указываю на общую концепцию ООП. - person Simon; 30.05.2010
comment
извините, но сама по себе Абстрактная статика - это еще ерунда. Вам нужен настраиваемый атрибут, чтобы понять смысл этого варианта использования. - person John Saunders; 11.07.2010
comment
В таких языках, как Java и C#, static идет вместе с final. Вот почему многие предполагают, что статическое равно непереопределяемому. (@Simon) -- Это верно лишь отчасти, по крайней мере, для Java. Статические методы по умолчанию не являются окончательными, поэтому разрешен статический метод подкласса с той же сигнатурой. Статический метод в подклассе с той же сигнатурой, что и статический метод суперкласса скрывает метод суперкласса, но не переопределяет его. - person Christian Semrau; 29.08.2010

Как обычно поступают в такой ситуации?

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

abstract class Animal {
    private int numberOfLegs;

    public Animal(int numberOfLegs) {
        this.numberOfLegs = numberOfLegs;
    }

    public int getNumberOfLegs() {
        return numberOfLegs;
    }
}

class Chicken extends Animal {
    public Chicken() {
        super(2);
    }
}

class Dog extends Animal {
    public Dog() {
        super(4);
    }
}

Обновление: согласно вашему обновлению

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

Animal getModelAnimal(int numberOfLegs) {
   if (numberOfLegs == Chicken.getNumberOfLegs()) return new Chicken();
   else if (numberOfLegs == Dog.getNumberOfLegs()) return new Dog();
}

Это действительно смешно, для этого требуется, чтобы все эти конкретные животные были заранее известны в абстрактном фабричном методе. Вам нужно будет обновлять метод абстрактной фабрики каждый раз, когда добавляется новый конкретный тип животного. В чем тогда смысл абстрактной фабрики? Вы уже все знаете заранее? Нет, просто позвольте методу абстрактной фабрики принять полное имя класса в качестве идентификатора или около того, чтобы он мог попытаться загрузить из пути к классам (все еще говоря в терминах Java).

person BalusC    schedule 29.05.2010
comment
Но getNumberOfLegs() по-прежнему является методом экземпляра, и мне нужно создать экземпляры классов, чтобы узнать, сколько ног у животного... - person polyglot; 30.05.2010
comment
Однако метод getModelAnimal() не обязательно определен в классе Animal. - person polyglot; 30.05.2010

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

person John Saunders    schedule 29.05.2010
comment
@Downvoter: почему бы не рассказать людям, в чем проблема с моим ответом? - person John Saunders; 11.07.2010

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

Например, в приведенном вами варианте использования фабричный метод обращается к конкретным классам животных по имени; для каждого нового класса животных должен быть добавлен новый специальный код. Поэтому кажется, что «абстрактная» квалификация на самом деле не нужна. Достаточно соглашения о предоставлении статического метода getNumberLegs().

И вообще, объединение абстрактного и статического не имеет смысла (в Java), поскольку abstract подразумевает полиморфизм, а вызовы static вообще не полиморфны и работают с классами, известными в время компиляции.

person Eyal Schneider    schedule 29.05.2010

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

person grossvogel    schedule 30.05.2010

Абстрактные методы имеют смысл, если вы вызываете их в помощь базовому классу. Обратите внимание, что в вашем примере вы вообще не используете полиморфизм. В примере должно быть что-то вроде:

  (Animal)Dog.getNumberOfLegs() //cast Dog to Animal first

В любом случае PHP реализует так называемое «позднее статическое связывание», которое, вероятно, вы ищете.

http://php.net/manual/en/language.oop5.late-static-bindings.php

В C++ аналогичная функциональность может быть достигнута с помощью шаблонов и полиморфизма времени компиляции.

person doc    schedule 30.05.2010

abstract static имеют смысл только в языках, в которых переменные могут содержать фактические типы, а не только экземпляры. (Delphi — один из таких языков, C# — нет, и я не думаю, что вы сможете сделать это и на Java). Причина в том, что если вы точно знаете во время компиляции, какие классы вы используете (как в вашем примере), то нет причин для того, чтобы метод был abstract, вы могли бы просто иметь static методы в каждом классе с одинаковым именем вещи. Единственный способ, которым вы могли бы не знать, какие типы вы используете, - это если вы можете назначать типы переменной, так как тогда вы можете передавать их (точно так же, как экземпляры классов), и вдруг все действительно имеет смысл и полезно.

Я думаю, что большинство компиляторов/языков, которые поддерживают назначение типов (а также экземпляров типов) переменным, также могут поддерживать методы abstract static и virtual abstract с помощью магии компилятора, поэтому, если они действительно полезны в выбранном вами языке, их следует поддерживать.

person Donnie    schedule 30.05.2010
comment
Не только языки с переменными типами. Другой случай, когда такой абстрактный статический метод вызывается из другого метода. Допустим, один из методов Animal вызывает Animal.getNumberOfLegs(). Затем Dog может вызвать в одном из своих методов этот метод. Поскольку getNumberOfLegs() является абстрактным Animal.getNumberOfLegs() внутри тела метода Animal, его можно отправить в Dog.getNumberOfLegs(). - person doc; 30.05.2010
comment
Этот пример не имеет смысла. Как компилятор должен знать, что нужно отправить статический вызов другому типу на основе сигнатуры нестатического вызова, в котором вы сейчас находитесь? Это не то, как эти вещи работают. - person Donnie; 30.05.2010
comment
Я думаю, что дополнительная виртуальная таблица для статических вызовов должна работать. В любом случае, это зависит только от RTTI. PHP делает что-то подобное (хотя и не совсем так). - person doc; 30.05.2010
comment
Виртуальные статические методы имели бы смысл в языках .NET; если Foo имеет виртуальный статический метод Bar, то MakeThing<T>() where T:Foo должен иметь возможность использовать метод T.Bar(). Такая вещь может быть обработана с помощью существующего Framework, если T.Bar() преобразовать в Foo.VirtualStaticMethodHolder<T>.Methods.Bar(), а компилятор сгенерирует подходящие вложенные классы в Foo и производных; указанный универсальный класс будет использовать Reflection в первый раз, когда он будет отозван, а после этого кэшированную ссылку. - person supercat; 16.01.2014

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

abstract class AnimalFactory
{
    public abstract Animal CreateAnimal();
    public abstract int GetLegs();

}
abstract class Animal
{

}
internal class Chicken : Animal
{

}
class CreateChicken : AnimalFactory
{
    public override Animal CreateAnimal()
    {
        return new Chicken();
    }
    public override int GetLegs()
    {
        return 2;
    }

}
person Arseny    schedule 31.05.2010

На мгновение предположим, что "абстрактные статические методы" разрешены.

Затем, используя ваш код, я добавляю это:

Animal modelAnimal;
int numLegs;

modelAnimal = this.getModelAnimal(4); // Got a dog

numLegs = modelAnimal.getNumberOfLegs();

Я получаю сообщение об ошибке, так как modelAnimal, который является объектом Dog, попытается вызвать getNumberOfLegs в классе Animal, а не Класс Собака. Нет переопределения известных вам статических методов. Чтобы избежать этой ситуации, разработчики не включили абстрактные статические методы.

person abhinav    schedule 20.06.2011