Проблема множественного наследования в Java - PullRequest
8 голосов
/ 27 января 2010

Как вы справляетесь с наличием только одного наследования в Java? Вот моя конкретная проблема:

У меня есть три (упрощенных) класса:

public abstract class AbstractWord{
    String kind;    // eg noun, verb, etc

    public String getKind(){ return kind; }

}

public class Word extends AbstractWord{
    public final String word;

    ctor...

    public void setKind(){
        // based on the variable word calculate kind..
    }
}

public class WordDescriptor extends AbstractWord{

    ctor..

    public void setKind(String kind){this.kind = kind;}

}

Это то, что я считаю своей основной реализацией, но я хочу сделать другие реализации.

Допустим, я хочу добавить новую переменную, скажем, wordLength, но я хочу добавить ее, используя наследование. Это значит, что я НЕ хочу изменять этот оригинальный класс AbstractWord. Т.е. что-то вроде этого:

public class Length{
    private int length;

    public int getLength(){return length};
}

public class BetterWord extends AbstractWord AND Length{

    public void setLength(){
        // based on the variable word calculate Length..
    }
}

public class BetterWordDescriptor extends AbstractWord AND length{

    public void setLength(int length){this.length = length;}
}

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

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

Есть ли шаблон проектирования, который решает эту проблему? У меня есть некоторые потенциальные решения, но я хотел посмотреть, было ли что-то, чего мне не хватало.

спасибо, Джейк

Обновление: длина относится к числу слогов в слове (извините за отсутствие ясности)

Ответы [ 7 ]

9 голосов
/ 27 января 2010

Пользу композиции над наследством.

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

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

.

public class WordLength {
    private int length = 0;
    public int getLength(){return length};
    public void setLength(int length){this.length = length};
}

.

public interface WordLengthSupport {
    public WordLength getWordLength();
}

.

public class BetterWord extends AbstractWord 
        implements WordLengthSupport {
    WordLength wordLength;
    public WordLength getWordLength() {
        if(wordLength==null) {
            // each time word changes 
            // make sure to set wordLength to null
            calculateWordLength(); 
        }
        return wordLength;
    }
    private void calculateWordLength() {
        // This method should be 
        //    called in constructor 
        //    or each time word changes 
        int length = // based on the variable word calculate Length..
        this.wordLength = new WordLength();
        this.wordLength.setLength(length);
    }
}

.

public class BetterWordDescriptor extends AbstractWord  
        implements WordLengthSupport {
    WordLength wordLength;
    public WordLength getWordLength(return wordLength);
    public void setWordLength(WordLength wordLength) {
        // Use this to populate WordLength of respective word
        this.wordLength = wordLength;
    }
}

.

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

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

3 голосов
/ 27 января 2010

Просто используйте композицию вместо наследования:

a BetterWord is-an AbstractWord, что has-a Length:

public class BetterWord extends AbstractWord {

    private Length length;

    public void setLength(int value){
        length.setLength(value);
    }
}

EDIT

Если API нужен объект типа Length, просто добавьте геттер:

public class BetterWord extends AbstractWord {

    private Length length;

    public void setLength(int value){
        length.setLength(value);
    }

    public Length getLength() {
        return length
    }
}

Или переименуйте реализацию Length в LengthImpl и определите интерфейс Length, поскольку класс может реализовывать несколько интерфейсов.

2 голосов
/ 27 января 2010

Глядя на это, я впервые чувствую, что ваша модель немного сложна.

В слове есть строка, описывающая само слово, которое хранится в объекте Word, вместе с классом, чтобы сказать, что это существительное, глагол, прилагательное и т. Д. Другим свойством Word является длина строки, хранящейся в Word. объект.

Думайте о вещах с точки зрения отношений "есть" и "есть", и вы можете устранить большую сложность.

Например, зачем вам WordDescriptor, расширяющий AbstractWord? Будет ли слово меняться с глагола на прилагательное? Я думал бы, что тип слова был установлен, когда объект был создан, и не изменился бы во время жизни объекта Word. Если у вас есть объект Word для слова «Австралия», то вид слова не изменится в течение срока службы объекта.

Хм. Возможно, у вас может быть объект Word, представляющий слово «кора» после создания экземпляра объекта с типом «глагол» для описания звука, издаваемого собакой. Затем вы понимаете, что вам действительно нужно иметь объект Word для представления существительного, описывающего покрытие дерева. Возможно, но может существовать как кора собаки, так и кора дерева.

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

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

Когда я говорю, что класс B расширяет класс A, могу ли я сказать, что класс B "есть-a", класс A и что я специализируюсь на его поведении?

Например, базовый класс Animal может быть расширен для предоставления специализированного класса Kangaroo. Тогда вы можете сказать, что «кенгуру» - это «животное». Вы специализируете поведение.

Затем посмотрите на атрибуты, у кенгуру есть атрибут Location, чтобы описать, где он находится. Тогда вы можете сказать, что у кенгуру есть «есть». Расположение кенгуру "is-a" не имеет смысла.

Точно так же длина слова "имеет-а". И утверждение «длина слова» просто не имеет смысла.

Кстати, все австралийские ссылки в этом посте являются намеренными, чтобы отметить День Австралии, который сегодня 26 января!

НТН

2 голосов
/ 27 января 2010

В вашем конкретном примере вы можете использовать шаблон decorator в сочетании с интерфейсами, чтобы дополнить ваш класс Word дополнительными функциями; например,

// *Optional* interface, useful if we wish to reference Words along with
// other classes that support the concept of "length".
public interface Length {
  int getLength();
}

// Decorator class that wraps another Word and provides additional
// functionality.  Could add any additional fields here too.
public class WordExt extends AbstractWord implements Length {
  private final Word word;

  public class(Word word) {
    this.word = word;
  }

  public int getLength() {
    return word.getKind().length();
  }
}

Кроме того, стоит отметить, что отсутствие множественного наследования в Java на самом деле не является проблемой; это скорее случай переделки вашего дизайна. В целом считается чрезмерной практикой чрезмерное использование наследования, поскольку иерархии глубокого наследования трудно интерпретировать / поддерживать.

0 голосов
/ 04 сентября 2015

/ ** * Первый пример * /

class FieldsOfClassA {
    public int field1;
    public char field2;
}

interface IClassA {
    public FieldsOfClassA getFieldsA();
}

class CClassA implements IClassA {
    private FieldsOfClassA fields;

    @Override
    public FieldsOfClassA getFieldsA() {
        return fields;
    }
}

/**
 * seems ok for now
 * but let's inherit this sht
 */


class FieldsOfClassB {

    public int field3;
    public char field4;
}

interface IClassB extends IClassA {

    public FieldsOfClassA getFieldsA();
    public FieldsOfClassB getFieldsB();
}

class CClassB implements IClassB {

    private FieldsOfClassA fieldsA;
    private FieldsOfClassB fieldsB;

    @Override
    public FieldsOfClassA getFieldsA() {
        return fieldsA;
    }

    @Override
    public FieldsOfClassB getFieldsB() {
        return fieldsB;
    }
}

/ **

  • Ух ты, этот монстр стал больше

  • представьте, что вам понадобится 4 уровня наследования

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

  • Я даже не говорю, что пользователь этого iface подумает

  • какие поля мне понадобятся поляA поляB поляC или другое

Так что композиция здесь не работает и ваши жалкие попытки бесполезны

Когда вы думаете об объектно-ориентированном программировании

вам нужны БОЛЬШИЕ модели с 6-7 уровнями множественного наследования

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

Если ваши модели требуют 2 уровня наследства, прекратите притворяться, что используете OO

U может легко реализовать его на любом языке, даже процедурном, например, C или Basic. * /

0 голосов
/ 27 января 2010

Проблема не в том, «как бороться с единичным наследованием». То, что вам не хватает, - это не шаблон проектирования, а умение разрабатывать API отдельно от реализации.

Я бы реализовал это так:

public interface WordDescriptor {
    void getKind();
    Word getWord();
}

public interface Word {
    String getWord();
}

public class SimpleWord implements Word {
    private String word;

    public SimpleWord(String word) { this.word = word; }
    public String getWord() { return word; }
}

public class SimpleWordDescriptor implements WordDescriptor {
    private Word word;
    private String kind;

    public SimpleWordDescriptor(Word word, String kind) {
        this.word = word;
        this.kind = kind; // even better if WordDescriptor can figure it out internally
    }

    public Word getWord() { return word; }

    public String getKind() { return kind; }
}

При этой базовой настройке, когда вы хотите ввести свойство длины, все, что вам нужно сделать, это:

public interface LengthDescriptor {
    int getLength();
}

public class BetterWordDescriptor extends SimpleWordDescriptor
                                  implements LengthDescriptor {
    public BetterWordDescriptor(Word word, String kind) {
        super(word, kind);
    }

    public int getLength() { getWord().length(); }        
}

Другие ответы, в которых используется состав свойств, а также шаблон Decorator, также являются полностью допустимыми решениями вашей проблемы. Вам просто нужно определить, какие у вас объекты и насколько они «компонуемы», и как они должны использоваться - следовательно, сначала нужно разработать API.

0 голосов
/ 27 января 2010

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

Это не нарушит совместимость с исходным кодом. Нет, если вы не делаете что-то действительно сумасшедшее в своих методах равных.

И переименование ваших классов, как правило, не способ справиться с двоичной совместимостью.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...