避免在访问者模式中使用InstanceOf - Java

3
我在Stack Overflow上搜索了与这个主题相关的信息。我找到了以下解决方案:访问者设计模式的解释
现在,我的情况与此类似。然而,我需要避免使用 "instanceOf"。
我有一个名为MonkeyTowerCannonTowerOctoTower等的塔的游戏... 一些其他类使用instanceOf进行比较。
下面是一个使用instanceOf的类的示例:

BloonsTower.java

public void mousePressed(Point p) {
        Tower t = null;
        selectedTower = towerInfo[ insertTowerIdx ].getTower();

        if( selectedTower instanceof MonkeyTower )
            t = tCreator.createMonkey();
        else if( selectedTower instanceof OctoTower )
            t = tCreator.createOctogonal();
        else if( selectedTower instanceof CannonTower )
            t = tCreator.createCannon();
        else if( selectedTower instanceof MortarTower )
            t = tCreator.createMortar();
        setMoney( money - towerInfo[ insertTowerIdx ].getPrice() );
        t.setPosition( p );
        world.addTower(t);
        currentState = new SelectTowerState();
    }

ManipulatorCreator.java

if( t instanceof MonkeyTower )
        return null;
    else if( t instanceof OctoTower )
        return new OctoManipulator( t );
    else if( t instanceof CannonTower )
        return null;
    else if( t instanceof MortarTower )
        return new MortarManipulator( (MortarTower)t );
    return man;

而 GameWriter:

public void saveFile( File file, int round, int money, int lives, World m ) throws IOException {
    PrintWriter out = new PrintWriter( new BufferedWriter( new FileWriter( file) ) );

    out.println( round );
    out.println( money );
    out.println( lives );
    Tower []torres = m.getTowers();
    out.println( torres.length );   // escrever o nº de torres
    for( Tower t : torres ){
        Point p = t.getComponent().getPosicao();
        // escrever a posição e o tipo de torre
        out.print(p.x+"\t" + p.y+"\t" );
        if( t instanceof MonkeyTower )
            out.println( "macaco" );
        else if( t instanceof OctoTower )
            out.println( "octo" );
        else if( t instanceof CannonTower )
            out.println( "canhao" );
        else if( t instanceof MortarTower )
            out.println( "morteiro" );
    }

    out.close();
}

我创建的是一个为访问每个塔的访客所设计的类:
public class TowerVisitor implements Visitor{

    public void visit(MonkeyTower monkey) {
        // TODO Auto-generated method stub
    }

    public void visit(CannonTower cannon) {
        // TODO Auto-generated method stub
    }

    public void visit(MortarTower mortar) {
        // TODO Auto-generated method stub
    }

    public void visit(OctoTower octo) {
        // TODO Auto-generated method stub
    }
}

在我创建的每个塔中,都有一个名为accept的方法,它返回它自己。

现在,我困在了如何填写visit方法以及如何使用模式来切换所有的instanceOf之间。

谢谢。


3
为什么需要访问者模式呢?为什么不创建抽象方法:Tower.createTower()Tower.createManipulator()等呢?请你翻译此段内容。 - shmosel
你需要使用工厂方法模式而不是访问者模式。 - Ravindra babu
我认为我不需要工厂。所有的对象都是塔,无论它们是不同的塔,它们都实现了相同的接口。 - user1987003
3个回答

1
你应该使用核心 面向对象编程 模式,即继承,而不是访问者模式。你有几种不同类型的塔,以及类似的操作(创建、操作、toString 等),应该针对每种类型的塔进行不同的实现。这是继承的一个典型例子。
public abstract class Tower {
  public abstract Tower create();
  public abstract Manipulator manipulate();
}

---

public class MortarTower extends Tower {
  @Override
  public MortarTower create() {
    return new MortarTower();
  }

  @Override
  public MortarManipulator manipulate() {
    return new MortarManipulator(this);
  }

  @Override
  public String toString() {
    return "morteiro";
  }
}

---

public void mousePressed(Point p) {
  selectedTower = towerInfo[insertTowerIdx].getTower();
  setMoney(money - towerInfo[insertTowerIdx].getPrice());
  Tower t = selectedTower.create();
  t.setPosition(p);
  world.addTower(t);
  currentState = new SelectTowerState();
}

等等。


1
请注意,访问者模式确实是面向对象编程(OOP)的一部分。 - jaco0646
当然,你是正确的。虽然我相信你知道我的意思。你可以建议一个更好的措辞吗?“核心面向对象编程”?“基础面向对象编程”? - dimo414
如果意图是遵循@ravindra的评论并实现工厂方法模式,那么我会这样表述:“你应该使用工厂方法模式,而不是访问者模式。” - jaco0646
从我所看到的情况来看,我不认为需要工厂;我只是建议在子类中进行方法覆盖。 - dimo414
这是一个大学项目...我不能比较字符串来检查它是猴塔还是加农炮塔。该项目的主要目标是使用模式来避免一些代码。这是其中一个应该使用模式(或多个)的地方。 - user1987003
显示剩余2条评论

0

如果你想要创建一个独立于Tower的ManipulatorDactory,你必须使用FactoryMethod或AbstractFactory模式,而不是Visitor。

请查看Factory MethodAbstract Facotry以获取更多详细信息。

以下是一些示例代码:

public class FactoryMethodDemo{

    public FactoryMethodDemo(){

    }
    public Tower getTower(String type){
        if ( type.equalsIgnoreCase("monkey")){
            return new MonkeyTower();
        }else if ( type.equalsIgnoreCase("octo")){
            return new OctoTower();
        }else if ( type.equalsIgnoreCase("canon")){
            return new CannonTower();
        }else if ( type.equalsIgnoreCase("mortal")){
            return new MortarTower();
        }
        return null;
    }
    public static void main(String args[]){
        FactoryMethodDemo factory = new FactoryMethodDemo();
        Tower tower = factory.getTower(args[0]);
        System.out.println("Tower:"+tower);
    }
}
abstract class Tower{
    Manipulator manipulator = null;
    public Tower(){

    }
    public String toString(){
        return this.getClass().getSimpleName()+":manipulator:"+manipulator;
    }
    public Manipulator getManipulator(){
        return manipulator;
    }
}
class MonkeyTower extends Tower{
    public MonkeyTower(){

    }
}
class OctoTower extends Tower{
    public OctoTower(){
        manipulator = new OctoManipulator();
    }

}
class CannonTower extends Tower{
    public CannonTower(){

    }
}
class MortarTower  extends Tower{
    public MortarTower(){
        manipulator = new MortarManipulator();
    }

}
class Manipulator{

}
class OctoManipulator extends Manipulator{

}
class MortarManipulator extends Manipulator{

}

输出:

D:\Study\Java>java FactoryMethodDemo monkey
Tower:MonkeyTower:manipulator:null

D:\Study\Java>java FactoryMethodDemo mortal
Tower:MortarTower:manipulator:MortarManipulator@4187a8e0

D:\Study\Java>java FactoryMethodDemo octo
Tower:OctoTower:manipulator:OctoManipulator@46fb3d6

D:\Study\Java>java FactoryMethodDemo canon
Tower:CannonTower:manipulator:null

相关的SE问题:

工厂模式。何时使用工厂方法?

工厂模式和抽象工厂模式之间的基本区别是什么?


抽象工厂不应该用于创建不同类型的家族。我们只使用一种类型——塔。在这种情况下,我们需要返回新的塔对象,对吗?但并非所有情况都是如此。我正在考虑继续使用访问者模式,但对于需要返回新对象的情况,实现原型模式。 - user1987003
我的答案只实现了工厂方法模式。具体的塔正在返回正确的操纵器对象。我已经处理了你在问题中引用的条件。 - Ravindra babu

0
如果您想对访问者进行全部操作,您需要为每种情况创建访问者,即为塔和操纵器创建以及文件保存创建访问者。此外,在这种情况下,我认为您不需要为每个塔创建accept方法,但是您可以这样做。Accept方法将接收一些通用的Visitor,它所要做的就是调用visitor.visit方法。类似于:
    public class CannonTower {
    public void accept(CommonVisitor visitor) {
        visitor.visit(this);
    }
}

创建一些常见的访问者接口,其中包含空方法:
    public interface CommonVisitor {
    default void visit(MonkeyTower monkey) {
        //  nothing to do to not implement when it is not required at all
    }

    default void visit(CannonTower cannon) {
        //  nothing to do
    }

    default void visit(MortarTower mortar) {
        //  nothing to do
    }

    default void visit(OctoTower octo) {
        //  nothing to do
    }
}

让我们为BloonsTower.java创建一个访问者来创建塔(在我看来,您的代码看起来有点奇怪 - 您获取塔并创建相同的塔):

@Getter
public class TowerCreatorVisitor implements CommonVisitor {
    public TowerCreatorVisitor(TowerCreator tCreator) {
        this.tCreator = tCreator;
    }

    TowerCreator tCreator;
    Tower tower;

    public void visit(MonkeyTower monkey) {
        tower = tCreator.createMonkey();
    }

    public void visit(CannonTower cannon) {
        tower = tCreator.createCannon();
    }

    public void visit(MortarTower mortar) {
        tower = tCreator.createMortar();
    }

    public void visit(OctoTower octo) {
        tower = tCreator.createOcto();
    }
   }

BloonsTower.java 变成了:

  public void mousePressed(Point p) {
    Tower t = null;
    selectedTower = towerInfo[ insertTowerIdx ].getTower();

    TowerCreatorVisitor towerCreatorVisitor = new TowerCreatorVisitor(tCreator);
    selectedTower.accept(towerCreatorVisitor);
    // you may not use accept at all, then just 
    // towerCreatorVisitor.visit(selectedTower);
    // up to you)
    t = towerCreatorVisitor.getTower();

    setMoney( money - towerInfo[ insertTowerIdx ].getPrice() );
    t.setPosition( p );
    world.addTower(t);
    currentState = new SelectTowerState();
}

创建机械臂:
    @Getter
public class ManipulatorCreatorVisitor implements CommonVisitor {
    Manipulator manipulator = new Manipulator();


    public void visit(MonkeyTower monkey) {
        tower = null;
    }

        public void visit(CannonTower cannon) {
            tower = null;
        }
    
        public void visit(MortarTower mortar) {
            manipulator = new MortarManipulator(mortar);
        }
    
        public void visit(OctoTower octo) {
            manipulator = new OctoManipulator(octo);
        }
    }

ManipulatorCreator:

public Manipulator getManipulator(Tower t) {
    ManipulatorCreatorVisitor manipulatorCreatorVisitor = new ManipulatorCreatorVisitor();
    t.accept(manipulatorCreatorVisitor);
    return manipulatorCreatorVisitor.getManipulator();
}

保存文件:

  @AllArgsConstructor
public class FileWriterVisitor implements CommonVisitor {
    PrintWriter out;

    public void visit(MonkeyTower monkey) {
        out.println( "macaco" );
    }

    public void visit(CannonTower cannon) {
        out.println( "canhao" );
    }

    public void visit(MortarTower mortar) {
        out.println( "morteiro" );    }

    public void visit(OctoTower octo) {
        out.println( "octo" );    }
}

游戏编写者:

public void saveFile( File file, int round, int money, int lives, World m ) throws IOException {
        PrintWriter out = new PrintWriter( new BufferedWriter( new FileWriter( file) ) );

        out.println( round );
        out.println( money );
        out.println( lives );
        Tower []torres = m.getTowers();
        out.println( torres.length );   // escrever o nº de torres
        FileWriterVisitor fileWriterVisitor = new FileWriterVisitor(out);
        for( Tower t : torres ){
            Point p = t.getComponent().getPosicao();
            // escrever a posição e o tipo de torre
            out.print(p.x+"\t" + p.y+"\t" );
            fileWriterVisitor.visit(t);
            // or t.accept(fileWriterVisitor);
        }

        out.close();
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接