Атрибуты / переменные-члены в интерфейсах?

Я хотел бы знать, есть ли способ сделать обязательным для класса реализатора объявлять дескрипторы / примитивы объектов, как они делают с методами. например:

public interface Rectangle {    
    int height = 0;
    int width = 0;

    public int getHeight();
    public int getWidth();
    public void setHeight(int height);
    public void setWidth(int width);                
}


public class Tile implements Rectangle{
    @Override
    public int getHeight() {
        return 0;
    }

    @Override
    public int getWidth() {
        return 0;
    }

    @Override
    public void setHeight(int height) {
    }

    @Override
    public void setWidth(int width) {   
    }

}

В приведенном выше методе, как мы можем заставить класс Tile объявлять атрибуты высоты и ширины, используя интерфейс? Почему-то я хочу сделать это только с интерфейсом!

Я изначально думал об использовании его с наследованием. Но дело в том, что я имею дело с 3 классами!

  1. Прямоугольник
  2. Плитка
  3. JLabel.!

 class Tile extends JLabel implements Rectangle {}

должно сработать.!

но

class Tile extends JLabel extends Rectangle {}

не очень!

10 ответов

Решение

Смысл интерфейса заключается в указании публичного API. Интерфейс не имеет состояния. Любые переменные, которые вы создаете, действительно являются константами (поэтому будьте осторожны при создании изменяемых объектов в интерфейсах).

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

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

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

Короткий ответ - вы не можете делать то, что вы хотите, потому что это "неправильно" в Java.

Редактировать:

class Tile 
    implements Rectangle 
{
    private int height;
    private int width;

     @Override
    public int getHeight() {
        return height;
    }

    @Override
    public int getWidth() {
        return width;
    }

    @Override
    public void setHeight(int h) {
        height = h;
    }

    @Override
    public void setWidth(int w) { 
        width = w;  
    }
}

альтернативная версия будет:

abstract class AbstractRectangle 
    implements Rectangle 
{
    private int height;
    private int width;

     @Override
    public int getHeight() {
        return height;
    }

    @Override
    public int getWidth() {
        return width;
    }

    @Override
    public void setHeight(int h) {
        height = h;
    }

    @Override
    public void setWidth(int w) { 
        width = w;  
    }
}

class Tile 
    extends AbstractRectangle 
{
}

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

( Переменные могут быть определены в интерфейсах, но они не ведут себя так, как ожидалось: они рассматриваются как final static.)

Удачного кодирования.

Java 8 представила методы по умолчанию для интерфейсов, используя которые вы можете привязать методы. Согласно ООП интерфейсы должны действовать как контракт между двумя системами / сторонами.

Но все же я нашел способ добиться сохранения свойств в интерфейсе. Я признаю, что это довольно уродливая реализация.

   import java.util.Map;
   import java.util.WeakHashMap;

interface Rectangle
{

class Storage
{
    private static final Map<Rectangle, Integer> heightMap = new WeakHashMap<>();
    private static final Map<Rectangle, Integer> widthMap = new WeakHashMap<>();
}

default public int getHeight()
{
    return Storage.heightMap.get(this);
}

default public int getWidth()
{
    return Storage.widthMap.get(this);
}

default public void setHeight(int height)
{
    Storage.heightMap.put(this, height);
}

default public void setWidth(int width)
{
    Storage.widthMap.put(this, width);
}
}

Этот интерфейс ужасен. Для хранения простого свойства потребовалось два хеш-карты, и каждый хеш-файл по умолчанию создает 16 записей по умолчанию. Кроме того, когда реальный объект разыменовывается, JVM дополнительно необходимо удалить эту слабую ссылку.

Вы можете сделать это только с абстрактным классом, а не с интерфейсом.

декларировать Rectangle как abstract class вместо interface и объявить методы, которые должны быть реализованы подклассом как public abstract, Тогда класс Tile расширяет класс Rectangle и должны реализовать абстрактные методы из Rectangle,

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

(Как примечание - это не относится ко всем языкам, ActionScript допускает объявление псевдоатрибутов, и я верю, что C# тоже)

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

Но я следую этому ответу: /questions/15875583/atributyi-peremennyie-chlenyi-v-interfejsah/15875590#15875590 Вы можете использовать службу с WeakHashMap для хранения атрибутов.

Чтобы уменьшить количество карт, используйте такую ​​карту ключей:

В java 11:

      public interface Rectangle {

    class Storage {
        private static final Map<Rectangle, Map<String, Object>> attributes = new WeakHashMap<>();
    }

    private Map<String, Object> getAttributes(){
        return Storage.attributes.computeIfAbsent(this, k -> new HashMap<>());
    }

    default public int getHeight() {
        return (int) getAttributes().getOrDefault("height", 0);
    }

    default public int getWidth() {
        return (int) getAttributes().getOrDefault("width", 0);
    }

    default public void setHeight(int height) {
        getAttributes().put("height", height);
    }

    default public void setWidth(int width) {
        getAttributes().put("width", width);
    }
}

Поля в интерфейсах неявно public static final, (Также методы неявно общедоступны, так что вы можете удалить public ключевое слово.) Даже если вы используете абстрактный интерфейс вместо интерфейса, я настоятельно рекомендую сделать все неконстантным (public static final ссылки на примитивный или неизменный объект) private, В более общем смысле "предпочитаю композицию наследованию" - Tile это не Rectangle (конечно, вы можете играть в словесные игры с "is-a" и "has-a").

Том сказал что-то важное:

если вы используете концепцию has-a, вы избегаете этой проблемы.

Действительно, если вместо использования extends и реализует, вы определяете два атрибута, один из типа прямоугольник, один из типа JLabel в вашем Tile класс, то вы можете определить Rectangle быть или интерфейсом или классом.

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

Вспомогательный пункт:

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

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

Для интерфейсов с переменными-членами используйте абстрактный класс:

      public abstract class Rectangle {    
    int height = 0;
    int width = 0;

    public abstract int getHeight();
    public abstract int getWidth();
    public abstract void setHeight(int height);
    public abstract void setWidth(int width);                
}
Другие вопросы по тегам