Scala: инициализация унаследованного члена интерфейса - PullRequest
1 голос
/ 08 февраля 2012

У меня есть следующий код, частично на Java, частично на Scala:

Java:

public interface ISubject {
  public void a()
  //...
}

public class CSubject implements ISubject {
  public void a() { /*...*/ }
  public void b() { /*...*/ }
  //...
}

public abstract class AbstractTest {
  ISubject subject;
  //...

  public CSubject generateParticularSubject() {
    return new CSubject();
  }
}

Scala:

object Test extends AbstractTest {

  override val subject: CSubject = generateParticularSubject() /*1*/

  subject.b() /*2*/
}

Проблема: еслистрока с пометкой «1» такая же, как и в коде выше, компилятор жалуется, что overriding variable subject in class AbstractTest of type ISubject; value subject has incompatible type.Если я удаляю тип annotaion : CSubject в строке '1' и ключевые слова override val, эта ошибка исчезает, но в строке '2' появляется другая: value b is not a member of ISubject.

Я понимаю, что говорят эти ошибкии способ, которым компилятор думает, получая эти проблемы.Что я не понимаю, так это как получить поведение, такое же, как в Java, где при реализации члена интерфейса класса можно инициализировать его любым классом, реализующим интерфейс.Как это сделать в Scala?

ОБНОВЛЕНИЕ: Правда ли, что в Scala невозможно реализовать такую ​​конструкцию?Причина, по которой я хочу это сделать, заключается в том, что AbstractTest содержит методы, которые имеют дело с ISubject стороной субъекта, и дочерние классы, которые должны переопределить subject и определять его как более узкий экземпляр класса, должны реализовывать методы, специфичные для этого.учебный класс.В то же время я хочу, чтобы AbstractTest продолжал обрабатывать все, что касается subject, и может обрабатываться, пока он обрабатывается через интерфейс ISubject.

Ответы [ 4 ]

3 голосов
/ 08 февраля 2012

Поле Java subject является изменяемым, поэтому оно не может быть переопределено в Scala с помощью val. Кроме того, будучи назначаемым, было бы небезопасно изменять его одновременно.

Вы можете исправить свой код, сделав subject абстрактный метод. Это может быть переопределено ковариантно с val, как вы делаете в своем коде Scala.

 abstract class AbstractTest {
   public abstract ISubject subject();

   //...

   public CSubject generateParticularSubject() {
     return new CSubject();
   }
 }
3 голосов
/ 08 февраля 2012

Не ясно, чего вы пытаетесь достичь в вашем Test объекте.Если вам просто нужно работать с результатом метода generateParticularSubject() в качестве экземпляра CSubject, просто используйте другое имя переменной, то есть

object Test extends AbstractTest {
  subj = generateParticularSubject()
  subj.b()
}

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

То, на что вы ссылаетесьздесь это называется "ковариантными типами возврата" и действительно поддерживается в Java начиная с версии 5 (посредством чего метод, переопределенный в подклассе, может возвращать более узкий тип).Однако то, что вы пытаетесь сделать в коде Scala, не имеет ничего общего с ковариантными типами возврата, вместо этого вы пытаетесь переопределить поле члена базового класса, что на самом деле не очень хорошая идея.Поле объявлено как имеющее тип ISubject в базовом классе и должно оставаться таковым.Вот почему вы можете написать:

object Test extends AbstractTest {
  subject = generateParticularSubject()
  // ...
}

, но только тогда сможете вызывать методы, определенные в ISubject (поскольку это статический тип субъекта, независимо от типа, возвращаемого функцией generateParticularSubject ())

2 голосов
/ 08 февраля 2012

Я думаю, у вас есть два варианта.

Самый простой:

object Test extends AbstractTest {

  val csubject: CSubject = generateParticularSubject() 
  override val subject = csubject

  csubject.b() 
}

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

присваивает AbstractTest параметр типа T, который расширяет ISubject, делает субъект типа T и в Test привязывает этот параметр к CSubject.

1 голос
/ 08 февраля 2012

То, что вы просите, невозможно в Scala или Java. Конечно, Python или Ruby позволят вам сделать это, и они будут ошибаться (но, опять же, они вообще не проверяют, что вы делаете).

Проблема в том, что вы можете делать то, что хотите:

class DSubject extends ISubject
Test.subject = new DSubject

Вы не можете запретить это назначение, потому что Test реализует AbstractTest, а AbstractTest имеет открытое поле. Отключение присваивания приведет к нарушению договора AbstractTest.

На вашем объекте Test любой доступ к члену CSubject приведет к ошибке, поскольку subject теперь содержит DSubject.

Попробуйте что-нибудь еще, например, агрегацию вместо наследования или прокси-класс.

...