Анонимное или реальное определение класса при использовании шаблона посетителя? - PullRequest
2 голосов
/ 30 августа 2011

Когда вы используете шаблон Visitor и вам нужно получить переменную внутри метода посетителя, как вам поступить?

Я вижу два подхода.Первый использует анонимный класс:

// need a wrapper to get the result (which is just a String)
final StringBuild result = new StringBuilder();
final String concat = "Hello ";

myObject.accept(new MyVisitor() {

    @Override
    public void visit(ClassA o)
    {
        // this concatenation is expected here because I've simplified the example
        // normally, the concat var is a complex object (like hashtable) 
        // used to create the result variable 
        // (I know that concatenation using StringBuilder is ugly, but this is an example !)
        result.append(concat + "A");
    }

    @Override
    public void visit(ClassB o)
    {
        result.append(concat + "B");
    }
});

System.out.println(result.toString());

Плюсы и минусы:

  • Плюсы: вам не нужно создавать файл класса для этого небольшого поведения
  • Минусы: мне не нравится ключевое слово "final" в этом случае: анонимный класс менее читабелен, потому что он вызывает внешние переменные, и вам нужно использовать обертку для получения запрошенного значения (потому что с ключевым словом final вы не можетепереназначить переменную)

Еще один способ сделать это - сделать класс внешнего посетителя:

public class MyVisitor
{
    private String result;
    private String concat;

    public MyVisitor(String concat)
    {
        this.concat = concat;
    }

    @Override
    public void visit(ClassA o)
    {
        result = concat + "A";
    }

    @Override
    public void visit(ClassB o)
    {
        result = concat + "B";
    }

    public String getResult()
    {
        return result;
    }
}

MyVisitor visitor = new MyVisitor("Hello ");
myObject.accept(visitor);
System.out.println(visitor.getResult());

Плюсы и минусы:

  • Плюсы:все переменные определены в чистой области видимости, вам не нужна оболочка для инкапсуляции запрошенной переменной
  • Минусы: нужен внешний файл, метод getResult () должен вызываться после метода accept, это довольнонекрасиво, потому что вам нужно знать порядок вызова функций, чтобы правильно использовать посетителя

Вы, каков ваш подход в этом случае?Предпочитаемый метод?другая идея?

Ответы [ 5 ]

3 голосов
/ 30 августа 2011

Хорошо, оба подхода действительны и imo, это действительно зависит от того, хотите ли вы повторно использовать код или нет. Кстати, ваша последняя точка «Con» не является полностью допустимой, так как вам не нужен «внешний файл» для объявления класса. Это вполне может быть внутренний класс ...

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

public interface IVisitor<T extends Object> {
    public T visit(ClassA element) throws VisitorException;
    public T visit(ClassB element) throws VisitorException;
}

public interface IVisitable {
    public <T extends Object> T accept(final IVisitor<T> visitor) throws VisitorException;
}

public class MyVisitor implements IVisitor<String> {
    private String concat;

    public MyVisitor(String concat) {
        this.concat = concat;
    }

    public String visit(ClassA classA) throws VisitorException {
        return this.concat + "A";
    }

    public String visit(ClassB classB) throws VisitorException {
        return this.concat + "B";
    }
}

public class ClassA implements IVisitable {
    public <T> T accept(final IVisitor<T> visitor) throws VisitorException {
        return visitor.visit(this);
    }
}

public class ClassB implements IVisitable {
    public <T> T accept(final IVisitor<T> visitor) throws VisitorException {
        return visitor.visit(this);
    }
}

// no return value needed?
public class MyOtherVisitor implements IVisitor<Void> {
    public Void visit(ClassA classA) throws VisitorException {
        return null;
    }

    public Void visit(ClassB classB) throws VisitorException {
        return null;
    }
}

Таким образом, посещенные объекты не знают, что посетитель хочет с ними сделать, но они возвращают все, что хочет вернуть посетитель. Ваш посетитель может даже «потерпеть неудачу», создав исключение.

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

Отказ от ответственности : Я только что взломал это вместе, качество (или даже компиляция) не гарантировано. Но вы поняли ...:)

1 голос
/ 30 августа 2011

Я бы рекомендовал использовать второй подход.Наличие посетителя в его полноценном классе также служит цели документации и чистого кода.Я не согласен с минусами, которые вы упомянули при подходе.Скажем, у вас есть arraylist, и вы не добавляете к нему какой-либо элемент и не выполняете get, конечно, вы получите null, но это не значит, что это обязательно неправильно.

1 голос
/ 30 августа 2011

С точки зрения конструкции Cleaner , второй подход предпочтительнее по тем же точным причинам, которые вы уже указали.

В обычном цикле TDD я бы начал санонимный класс и реорганизовал его чуть позже.Однако, если посетитель понадобится только в этом одном месте, а его сложность будет соответствовать сложности, которую вы указали в примере (т. Е. не сложно ), я бы оставил его висеть и рефакторинг дляпозже отдельный класс при необходимости (например, появился другой вариант использования, повышена сложность посетителя / окружающего класса).

1 голос
/ 30 августа 2011

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

Это поможет как в примере 1, так и в 2. Вам не понадобится оболочка в 1, потому что вы можетеопределите метод getResult(), чтобы получить желаемый результат.В примере 2, поскольку getResult() является частью вашего интерфейса, нет ни одной функции, которую вы «должны знать».

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

0 голосов
/ 30 августа 2011

Одной из точек шаблона посетителя является включение нескольких типов посетителей. Если вы создаете анонимный класс, вы как бы нарушаете шаблон.

Вам следует изменить accept метод на

public void accept(Visitor visitor) {
   visitor.visit(this);
}

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

...