Предотвратяване на подкласове от добавяне на методи

Това може да изглежда като странно нещо, но има ли начин в Java да спре подкласовете да добавят нови методи (включително конструктори), като същевременно позволява на подкласовете да заменят методите?

Действителната ситуация е, когато имаме abstract клас с някои абстрактни методи и конструктор

abstract class A {
  abstract A doX();
  abstract boolean isY();
  public A(String s){ ... };
}

и искаме всички конкретни подкласове на този клас да заменят само тези методи и конструктор.

Става въпрос за налагане на определен стил в нашия код, т.е. спиране на други хора, работещи върху кода, да добавят неща. Можем просто да им кажем да не го правят, но това рядко работи, така че се чудехме дали има програмен начин да постигнем това.

Очевидно класът не може да бъде final. Ефективността не е от първостепенно значение - по-изчистеният код е по-важен.

Актуализация - динамичен подход

Както беше посочено в отговорите, няма начин да направите това статично, тъй като единственият начин да предотвратите създаването на подкласове е използването на final, което няма да работи. Но бих могъл да използвам динамичен подход, така че настоящото ми решение е да добавя това aspect към проекта (който вече използва AspectJ).

   public aspect WatchA{
      before() : execute(* A.*()) || execute(* A.*(..)) {
         String methodCalled = joinPoint.getSignature().getName();
         Class<?> c = Class.forName(args[0])
         Method[] allMethods = c.getDeclaredMethods();
         boolean found = false;
         for(Method m : allMethods)
           found |= m.getName().equals(methodCalled);
         if(!found) 
           throw new RuntimeException("Do not add method "+methodCalled+" to A");
      }
   }

Което ще доведе до провал на тестовете им, ако използват някой от тези нови методи.


person selig    schedule 17.06.2013    source източник
comment
Java не позволява това. По-добре да се въведе строга политика за преглед на кода.   -  person Steve McLeod    schedule 17.06.2013
comment
От любопитство, защо финалът е забранен?   -  person fge    schedule 17.06.2013
comment
Искам подкласовете да могат да заместват методите, но не и да ги добавят - така че трябва да можем да създаваме подкласове. Разбирам, че всичко това е много прекомерно абонирано и честно казано сега се интересувам главно дали може да се направи - мисля, че в крайна сметка ще отидем с това да им кажем да не се доближават.   -  person selig    schedule 17.06.2013
comment
съгласен съм, че просто трябва да им кажете да не го правят. Не бих искал да бъда в този отбор, ако бях принуден да не науча основно OO наследяване.   -  person Oliver Watkins    schedule 17.06.2013
comment
Само като странична бележка, ако API е завършен, внедряванията не трябва да виждат необходимост да добавят нещо самостоятелно, нали?   -  person fge    schedule 17.06.2013
comment
Защо има значение дали добавят допълнителни методи? Предполагам, че във вашата част от приложението ги наричате екземпляри на родителския клас? Ако имат други методи, вашата част от приложението никога няма да разбере. (Не казвам, че е неразумно да искаме това, но знанието защо може да хвърли светлина върху това как)   -  person Richard Tingle    schedule 17.06.2013
comment
Също; за да осигури функционалността за замяна на някои от родителските класове, дъщерният клас може да иска някои (вероятно частни) методи, които разбиват заменения метод на части (или да бъдат повторно използвани между няколко заменени метода). Това също ли трябва да се забранява?   -  person Richard Tingle    schedule 17.06.2013


Отговори (4)


Ако наистина не искате да правите това... един от начините би бил програмно да проверите в конструктора на абстрактния клас дали методите, дефинирани в класа, са тези, които са разрешени.

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public abstract class Base {

    private static final Set<String> allowedMethodNames = new HashSet<>(Arrays.asList("doThis", "wait", "wait", "wait", "equals", "toString", "hashCode", "getClass", "notify", "notifyAll"));

    public Base() {
        Set<String> allMethods = new HashSet<>();
        for (Method aMethod : getClass().getMethods()) {
            allMethods.add(aMethod.getName());
        }
        if (!allowedMethodNames.equals(allMethods)) {
            allMethods.removeAll(allowedMethodNames);
            throw new IllegalStateException("Following methods not allowed <" + allMethods + ">");
        }
    }

    public abstract void doThis();
}

public class Disallowed extends Base {

    @Override
    public void doThis() {
        System.out.println("dooooooo");
    }

    public void doSomethingElse() {
        System.out.println("not allowed");
    }

    public static void main(String[] args) {
            new Allowed().doThis();
        new Disallowed();
    }

}

public class Allowed extends Base {

    @Override
    public void doThis() {
        System.out.println("doing this");
    }


}

Когато някой се опитва да създаде екземпляр на „Disallowed“, това ще се провали. Въпреки това „new Allowed().doThis()“ ще работи добре.

По-изящен начин да направите това би бил да въведете персонализирана анотация + процесор за анотация и да направите същата проверка по време на компилацията.

person Dev Blanked    schedule 17.06.2013

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

Можете също така да направите методи окончателни (дори в абстрактни класове), така че отмяната им да е забранена.

Най-добре е да създадете интерфейс с видими всички методи, които искате, и да принудите всички потребители на вашия API да имат достъп до обектите чрез този интерфейс. По този начин, дори ако реализациите добавят свои собствени неща, тези неща няма да бъдат видими.

Едно решение за това е да се внедри фабрика за връщане на конкретните класове; за "добавена сигурност", бихте могли да поставите всички реализации в същия пакет като тази фабрика и да направите пакета на конструкторите локален (но това често не е практично):

public final class MyFactory
{
    // ....
    public MyInterface getConcrete()
    {
        return new MyInterfaceImpl();
    }
    // etc etc -- getStones(), getTar(), getFeathers() and so on
}

Имайте предвид, че строителите могат да се използват и за това.

person fge    schedule 17.06.2013
comment
Как е това отговор за спиране на подкласовете да добавят нови методи? освен това OP не иска да попречи на подкласовете да заменят родителските методи. Той иска те да не добавят свои собствени методи, различни от това, което е дефинирано в родител. Момчета фенове на Java навсякъде - person srikanth yaradla; 17.06.2013
comment
@srikanthyaradla Това е за спиране на подкласовете да добавят нови методи -- първите 4 думи от това: Не можете да направите това. И тогава предлагам алтернатива. Какъв е проблемът? - person fge; 17.06.2013
comment
Можеше просто да спреш дотук. Но вие казахте, че едно решение за това е да се внедри фабрика, която да върне конкретните класове, което не пречи на внедряващите класове да добавят свои собствени методи. - person srikanth yaradla; 17.06.2013
comment
@srikantthyaradla прочете публикацията отново. Това позволява на потребителите на API да имат достъп само до това, което е необходимо. Реализациите могат да добавят всички глупости, които искат; те дори могат да се обадят на Юпитер за всичко, което има значение. - person fge; 17.06.2013
comment
Това никъде не отива. Реализациите могат да добавят всички глупости, които искат; те дори могат да се обадят на Юпитер за всичко, което има значение, но OP не иска това. Бихте могли да направите същото, като използвате родителска абстрактна препратка. Това не е по-различно от Използване на интерфейс. Сега нека не продължаваме да обсъждаме абстрактно срещу интерфейс - person srikanth yaradla; 17.06.2013
comment
Е, разбира се, че не води до никъде, но първоначалният ви коментар доведе до това. - person fge; 17.06.2013

Няма такъв начин. Защо искате да наложите такъв стил на кодиране?

Ако наистина трябва да наложите такъв стил, можете да създадете "прилагане на правила", което проверява вашия класов път и сравнява методите на вашите абстрактни родителски класове с техните подкласове.

person Michael    schedule 17.06.2013

Това е Java, което означава гъвкавост. Така че java ви дава по-удобно използване на абстрактните методи и отмяната им от вашите подкласове. Също така човек трябва да има идея за добавяне на нови методи към тези подкласове. Дори Java не може да промени това. Ако това стане, тогава цялата Java общност се срива. Невъзможно е да предотвратите добавянето на методи към техните подкласове. Само вие можете да ги спрете да разширяват вашите класове и да заменят вашите методи.

person Murali    schedule 17.06.2013