Java: суперкласс для создания подкласса на определенных условиях, возможно? - PullRequest
7 голосов
/ 05 мая 2010

У меня есть это условие

public class A {
     public action() {
         System.out.println("Action done in A");
     }
}


public class B extends A {
     public action() {
         System.out.println("Action done in B");
     }
}

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

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

public class A {
     public A() {
         if ([condition]) {
            this = new B();
         }
     }

     public action() {
         System.out.println("Action done in A");
     }
}

A a = new A();
a.action();
// expect to see "Action done in B"...

возможно ли это как-то?

Ответы [ 5 ]

6 голосов
/ 05 мая 2010

Я бы сказал, что делает это:

this = new B();

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

Как говорится, если бы я столкнулся с этим сценарием:

проблема в том, что в моем проекте суперкласс A уже используется слишком много раз

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

Вариант 1: использовать шаблон проектирования завода.

public class AFactory
{
    private static count = 0;
    private static final MAX_COUNT = 100;

    public A newObject() {
        if (count < MAX_COUNT) {
            count++;
            return new A();
        } else {
            return new B();
        }
    }
}

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

A obj1 = factory.newObject();
A obj2 = factory.newObject();

Вариант 2: Статический счетчик + пробуй и лови

Используйте статический счетчик в вашем классе A, который отслеживает количество экземпляров A, созданных путем увеличения статической переменной на единицу в конструкторе. Если он достигает предела для максимального числа объектов типа A, добавьте InstantiationError в конструктор A.

Это будет означать, что всякий раз, когда вы создаете экземпляр A, вам нужно try..catch блок, чтобы перехватить InstantionError, а затем вместо этого создать новый объект типа B.

public class A {

    private static count = 0;
    private static final MAX_COUNT = 100;

    public A() {
        if (count > 100) {
            throw new InstationError();
        }
    }

}

А при генерации ваших объектов:

A obj1, obj2;
try {
    obj1 = new A();
} catch (InstantiationError ie) {
    obj1 = new B();
}
try {
    obj2 = new A();
} catch (InstantiationError ie) {
    obj2 = new B();
}

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

5 голосов
/ 05 мая 2010

Не напрямую, нет. Вызов new A() всегда создаст экземпляр A. Однако вы можете сделать конструктор A защищенным, а затем использовать статический метод:

public static A newInstance() {
  // Either create A or B here.
}

Затем преобразуйте все текущие вызовы конструктора в вызовы фабричного метода.

4 голосов
/ 05 мая 2010

Можно выбрать, использовать конструктор суперкласса или нет?

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

Исходя из вышесказанного, в Java есть требование, чтобы первая строка конструктора вызывала конструктор суперкласса - фактически, даже если явного вызова конструктора суперкласса нет, будет неявный вызов super():

public class X {
   public X() {
      // ...
   }

   public X(int i) {
      // ...
   }
}

public class Y extends X {
   public Y() {
     // Even if not written, there is actually a call to super() here.
     // ...
   }
}

Следует подчеркнуть, что невозможно вызвать конструктор суперкласса после выполнения чего-то еще:

public class Y extends X {
   public Y() {
     doSomething();  // Not allowed! A compiler error will occur.
     super();        // This *must* be the first line in this constructor.
   }
}

Альтернатива

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

public A getInstance() {
  if (condition) {
     return new B();
  } else {
     return new C();
  }
}

В приведенном выше коде, в зависимости от condition, метод может возвращать либо экземпляр B, либо C (при условии, что оба являются подклассом класса A).

Пример

Ниже приведен конкретный пример использования interface вместо class.

Пусть будут следующие интерфейсы и классы:

interface ActionPerformable {
  public void action();
}

class ActionPerformerA implements ActionPerformable {
  public void action() {
    // do something...
  }
}

class ActionPerformerB implements ActionPerformable {
  public void action() {
    // do something else...
  }
}

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

class ActionPeformerFactory {

  // Returns a class which implements the ActionPerformable interface.
  public ActionPeformable getInstance(boolean condition) {

    if (condition) {
      return new ActionPerformerA();
    } else {
      return new ActionPerformerB();
    }
  }
}

Затем класс, который использует вышеуказанный фабричный метод, который возвращает соответствующую реализацию в зависимости от условия:

class Main {
  public static void main(String[] args) {

    // Factory implementation will return ActionPerformerA
    ActionPerformable ap = ActionPerformerFactory.getInstance(true);

    // Invokes the action() method of ActionPerformable obtained from Factory.
    ap.action();    
  }
}
2 голосов
/ 05 мая 2010

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

1 голос
/ 05 мая 2010

Предполагая, что вы не желаете перемещать его в интерфейс или фабрику, которая выглядит некрасиво, но вы можете сохранить удаленную копию B в A и переписать свои методы, чтобы не вызывать делегат:

public class A{
  B delegate;

  public A(){
    if([condition]){
      delegate = new B()
      return;
    }
    ...//normal init
  }

  public void foo(){
    if(delegate != null){
      delegate.foo();
      return;
    }
    ...//normal A.foo()
  }

  public boolean bar(Object wuzzle){
    if(delegate != null){
      return delegate.bar(wuzzle);
    }
    ...//normal A.bar()
  }
  ...//other methods in A
}
...