Смешивание композиции и наследования в Java - PullRequest
0 голосов
/ 26 апреля 2018

Можно ли смешивать композицию и наследование в Java? Во-первых, у меня есть несколько родовых классов - это отношения HAS-A (или HAS-MANY) (композиция).

Общие классы:

Структура, Тип A, Тип B, Тип C и т. Д., Где Структура находится в отношении HAS-A с Типом A, Типом B и Типом C.

Но тогда у меня также есть несколько наборов подклассов, которые наследуются от общих классов (Наследование) и находятся в одинаковых отношениях.

Наборы подклассов:

Набор 1:

Structure1, TypeA1, TypeB1, TypeC1 и т. Д., Где Structure1 находится в том же отношении HAS-A, что и TypeA1, TypeB1 и TypeC1, точно так же, как общие классы.

И: Structure1 расширяет Structure, TypeA1 расширяет TypeA, ..., TypeC1 расширяет TypeC и т. Д.

... до Задания X:

StructureX, TypeAX, TypeBX, TypeCX и т. Д.

Каждая специальная структура имеет ОДНО конкретный подтип A, Sub-TypeB и Sub-TypeC и т. Д. Общие классы определяют некоторый код (атрибуты и методы), который я хотел бы не использовать при работе со специальными структурами. Проблемы, с которыми я сталкиваюсь, лучше всего объясняются приведенным ниже кодом . (Я не знаю, можно ли это как-то решить с помощью «дженериков» Java, но я думаю, что нет.)

/***********************************************************************************************
/ Generic Structure of Structure-Class with Instances of Generic other classes (Composition)
/***********************************************************************************************/
class Structure {

    // Substructures
    TypeA instanceA;
    TypeB instanceB;

    // Defining many methods using the instances of other generic classes like TypeA
    void setGenericAttributeOfA(int value) {
        instanceA.genericAttribute = value;
    }
    void setGenericAttributeOfB(int value) {
        instanceB.genericAttribute = value;
    }
}

class TypeA {
    int genericAttribute;
}
class TypeB {
    int genericAttribute;
}

/***********************************************************************************************
/ Specific implementation of a Structure-Class with specific implementation of the other classes (Inheritance)
/***********************************************************************************************/

// In the specific implementations I want to use the generic methods, because I do not want to 
// rewrite the code for each and every specific implementation. But they should 

class Structure1 extends Structure {

    // This will create an additional attribute instanceA of specific TypeA1, so I will end up with two instances:
    // (1) TypeA super.instanceA and (2) TypeA1 this.instanceA. But what I would like is to have only
    // one instanceA of type TypeA1 that can also be used by the global methods of the generic Structure.
    TypeA1 instanceA;

    Structure1() {
        // This creates an instance of type TypeA1, but it cannot be used in the generic methods
        // because it is hold in a separate "local" variable that is not known to the generic Structure methods
        instanceA = new TypeA1();
        // This creates an instance of type TypeB1, but it cannot access the "local" specific attributes,
        // because it is hold in a varable which is statically types as TypeB
        instanceB = new TypeB1(); 
    }

    void specificMethod() {
        setGenericAttributeOfA(42); // would fail, because instanceA of type generic TypeA is null
        instanceA.specificAttribute = 13; // works only for "local" specific attributes

        setGenericAttributeOfB(42); // works only for generic attributes
        instanceB.specificAttribute = 13; // would fail, because instanceB is statically typed as generic TypeB which does not have this attribute
        ((TypeB1)instanceB).specificAttribute = 13; // works but is an ugly work-around and over-complicated if to be used many times
    }
}

class TypeA1 extends TypeA {
    int specificAttribute;
}
class TypeB1 extends TypeB {
    int specificAttribute;
}

Ответы [ 3 ]

0 голосов
/ 26 апреля 2018

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

Очки, которые вы можете рассмотреть, чтобы изменить ваши типы:

  1. Type и TypeB идентичны, по крайней мере, в отношении поста. Если у каждого из этих классов нет определенного поведения, вы можете сделать так, чтобы они объединились:

    // Replacement for TypeA and TypeB
    class Type {
        int genericAttribute;
    }
    

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

  2. После этого TypeA1 и TypeB1 могут расширять одного и того же родителя, Type:

    class TypeA1 extends Type {
        int specificAttribute;
    }
    class TypeB1 extends Type {
        int specificAttribute;
    }
    
  3. Относительно класса Structure: этот класс вам нужен только потому, что вы хотите реализовать "общее" поведение, связанное с TypeA и TypeB. Это различие больше не нужно, поскольку эти два слиты в Type, так что вы можете обойтись без этого класса и получить данные для работы. И данные могут перемещаться в Structure1 (не было бы необходимости в общем, отдельном месте для этих полей, потому что Structure1 в любом случае имеет оба конкретных типа):

    //this doesn't need a parent any more
    class Structure1 {
    
        TypeA1 instanceA;
        TypeB1 instanceB;
    
        Structure1() {
            instanceA = new TypeA1();
            instanceB = new TypeB1(); 
        }
    
        //This doesn't have to be implemented like this
        //I kept it to show the relation to your original
        //design. I would simply add 
        //setGenericAttribute(int) to Type
        void setGenericAttribute(Type type, int value) {
            type.genericAttribute = value;
        }
    
        void specificMethod() {
            setGenericAttribute(instanceA, 42);
            instanceA.specificAttribute = 13;
    
            setGenericAttribute(instanceB, 42);
            instanceB.specificAttribute = 13;
        }
    }
    
0 голосов
/ 27 апреля 2018

TL; DR Используйте переадресацию методов на ваших типах, и если это работает, потому что все слишком сложно, то найдите принципы SOLID и используйте шаблоны проектирования, чтобы ваши типы и классы следовали этому.

  1. Добавьте методы получения и установки для каждого типа и никогда не обращайтесь к полям напрямую. Если вы хотите получить доступ к полям напрямую, используйте внутренний класс. С геттерами и сеттерами у вас может быть инфо-объект, который может переносить значения, которые могут быть переданы в нужное вам поле. Или вы можете передать структуру данных, или вы используете varargs. В любом случае у вас есть гибкость.
  2. Я не могу сказать, пытаетесь ли вы решить проблемы структурного проектирования или проблемы с поведенческим дизайном, поэтому у вас возникают проблемы. Посмотрите принципы SOLID. Ваш Structure класс пытается служить двум целям. Вы пытаетесь создать объект данных, который отвечает за создание и хранение объектов, а также пытаетесь обработать данные для объектов. В основном вам нужно выбрать два шаблона проектирования, чтобы справиться с вашими намерениями. Я бы предложил шаблон Bridge и Builder. Создайте класс builder, который строит объекты Type, а затем заставьте свой структурный класс иметь только поля верхнего класса. Тогда структурный класс не должен обращаться к специфическим для типа методам или полям. Тем не менее, сборщик сделает это, поэтому вам нужно подумать о лучшем способе создания SOLID для разных типов. Вам может понадобиться конструктор для каждого типа или каждого класса, в зависимости от того, насколько он сложен, но если у вас есть методы получения и установки, это не так уж плохо. Также вы можете подумать об использовании фабрики или абстрактной фабрики со сборщиком, чтобы фабрика определяла, какой тип (и какой дочерний / родительский класс) будет объектом, и сборщик использует эту информацию для создания объекта этого определенного класса с все конкретные поля заполнены.
  3. Переадресация методов - ваш друг. Кажется, у вас уже была эта идея, но она использовалась не так часто, как могла. Если вы хотите создать общий метод, который устанавливает значения для различных объектов, то сделайте так, чтобы классы имели переопределенные / перегруженные методы, которые выполняют эту работу за вас. Объяснить это проще в следующем примере:

    class TypeA {
    
        int genericAttribute;
    
        setAttributes(int genericAttribute){
            this.genericAttribute = genericAttribute;
        }
    
    }
    
    class TypeB { 
    
        int genericAttribute;
    
        setAttributes(int genericAttribute){
            this.genericAttribute = genericAttribute;
         }
    }
    
    class TypeA1 extends TypeA {
    
        int specificAttribute;
    
        setAttributes(int genericAttribute, int specificAttribute){
            setAttributes(genericAttribute);
            this.specificAttribute = specificAttribute;
        }
    }
    
    class TypeB1 extends TypeB {
    
        int specificAttribute;
    
        setAttributes(int genericAttribute, int specificAttribute){
            setAttributes(genericAttribute);
            this.specificAttribute = specificAttribute;
        }
    }
    

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

0 голосов
/ 26 апреля 2018

Может быть Дженерики могут помочь?

Объявить Structure как универсальный:

class Structure<T1 extends TypeA, T2 extends TypeB, T3 extends TypeC> {
    T1 instanceA;
    T2 instanceB;
    T3 instanceC;
}

А ваши конкретные структуры будут просто наследоваться от общего Structure, указав аргументы типа:

class Structure1 extends Structure<TypeA1, TypeB1, TypeC1> {
    // here instanceA will be of type TypeA1, instanceB will be TypeB1 etc.
}
...