#java #oop
#java #ооп
Вопрос:
Запуск этого фрагмента кода приведет к печати null
public class Weird {
static class Collaborator {
private final String someText;
public Collaborator(String text) {
this.someText = text;
}
public String asText() {
return this.someText;
}
}
static class SuperClass {
Collaborator collaborator;
public SuperClass() {
initializeCollaborator();
}
protected void initializeCollaborator() {
this.collaborator = new Collaborator("whatever");
}
public String asText() {
return this.collaborator.asText();
}
}
static class SubClass extends SuperClass {
String someText = "something";
@Override
protected void initializeCollaborator() {
this.collaborator = new Collaborator(this.someText);
}
}
public static void main(String[] arguments) {
System.out.println(new Weird.SubClass().asText());
}
}
(Здесь также приведена суть GitHub)
Теперь я знаю, почему это происходит (это потому, что инициализируется поле суперкласса, а затем вызывается конструктор суперкласса, прежде чем инициализируется поле подкласса)
Вопросы:
- В чем здесь проблема с дизайном? Что не так с этим дизайном, с точки зрения ООП, так что результат выглядит странно для программиста? Какие принципы ООП нарушаются в этом дизайне?
- Как провести рефакторинг, чтобы он не работал «странно» и был правильным кодом ООП?
Ответ №1:
Что не так с дизайном: вы вызываете метод экземпляра из конструктора, а затем переопределяете его в подклассе. Не делайте этого. В идеале вызывать только частные или конечные методы экземпляра или статические методы из тел конструктора. Вы также предоставляете поле (которое является деталью реализации) снаружи SuperClass
, что не очень хорошая идея, но написание защищенного setCollaborator
метода и вызов его из initializeCollaborator
приведет к той же проблеме.
Что касается того, как это исправить — не совсем понятно, чего вы пытаетесь достичь. Зачем вам вообще нужен этот initializeCollaborator
метод? Могут быть различные способы решения этой проблемы, но они действительно зависят от точного знания того, чего вы пытаетесь достичь. (Черт возьми, в некоторых случаях лучшим решением является не использовать наследование в первую очередь. Предпочитайте композицию наследованию и все такое 🙂
Комментарии:
1. @Belun: На самом деле этой информации недостаточно, чтобы помочь вам найти наилучшее решение.
2. я имею в виду, что большая часть этого кода (который я вам показал) уже существует. это устаревший код. что мне нужно сделать, так это настроить Collaborator для подкласса, оставив его таким же, каким я был для суперкласса (и мое решение привело к этой проблеме)
3. @Belun: Ну, это в значительной степени исключает ваш вопрос 2, если вы не хотите действительно реорганизовывать его до здравомыслия 🙂 Можете ли вы инициализировать своего сотрудника в теле конструктора подкласса вместо переопределенного вызова метода? В этот момент ваш подкласс будет «более» инициализирован, так что вы будете в лучшем положении.
4. конечно, я постараюсь провести рефакторинг как можно реже
Ответ №2:
Моя проблема с дизайном заключается someText
в том, что строка должна быть либо явной зависимостью (или «соавтором») объекта Collaborator, либо подкласса, либо явно частью глобального контекста (то есть константой или свойством общего контекстного объекта).
В идеале, либо Collaborator должен отвечать за извлечение своих зависимостей; или, если за это отвечает подкласс, он должен иметь someText
зависимость (даже если он всегда инициализируется одним и тем же значением) и инициализировать Collaborator только тогда, когда установлен someText.
Концептуально говоря, отношение зависимости между объектами в проекте налагает частичный порядок инициализации. Механизм реализации этого порядка всегда должен быть явным в вашем проекте, вместо того, чтобы полагаться на детали реализации языка Java.
(Переработанный) пример:
interface ICollaboratorTextLocator {
String getCollaboratorText();
}
class ConstantCollaboratorTextLocator implements ICollaboratorTextLocator {
String text;
ConstantCollaboratorTextLocator(String text) {
this.text = text;
}
}
class SuperClass {
Collaborator collaborator;
public setCollaboratorTextLocator(ICollaboratorTextLocator locator) {
collaborator = new Collaborator(locator.getCollaboratorText());
}
SuperClass() {
setCollaboratorTextLocator(new ConstantCollaboratorTextLocator("whatever"));
}
}
class SubClass {
String text = "something";
SubClass() {
setCollaboratorTextLocator(new ConstantCollaboratorTextLocator(text));
}
}
(Теперь извините, мне нужно встать под водопад после написания чего-то, что называется ConstantCollaboratorTextLocator
.)
Комментарии:
1. Одна вещь, которая ускользала от меня раньше, и я поймал ее при перечитывании: если подкласс зависит от Collaborator , а Collaborator зависит от поля подкласса, результатом является тонкая циклическая зависимость. Это объясняет, почему он подвержен ошибке порядка инициализации.
2. я пометил ваш ответ как правильный, потому что мое решение было ближе всего к одному из ваших предложений («если за это отвечает подкласс, он должен иметь someText в качестве зависимости (даже если он всегда инициализируется одним и тем же значением»). мое решение довольно жестко запрограммировано: в подклассе просто создал new Collaborator («что-то»);
Ответ №3:
Вместо использования метода intializeCollaborator сделайте Collaborator параметром в конструкторе и вызовите его с помощью super(new Collaborator(..)) из дочернего элемента.