.NET设计模式(24):访问者模式(Visitor Pattern)

察看下面的示例,假设我们将交通工具作为父类,汽车、船和飞机作为子类,结构如图

 

代码如下:

using  System;
using  System.Collections.Generic;
using  System.Text;

namespace  VisitorPattern.ex34_0
{
    
public class Vehicle
    
{
        
public virtual void Go()
        
{
            Console.WriteLine(
"交通工具走!");
        }

    }

}


using  System;
using  System.Collections.Generic;
using  System.Text;

namespace  VisitorPattern.ex34_0
{
    
public class Boat:Vehicle
    
{
        
public override void Go()
        
{
            Console.WriteLine(
"船在水上开!");
        }


        
public void Pull()
        
{
            Console.WriteLine(
"找个码头停船吧!");
        }

    }

}


using  System;
using  System.Collections.Generic;
using  System.Text;

namespace  VisitorPattern.ex34_0
{
    
public class Car:Vehicle
    
{
        
public override void Go()
        
{
            Console.WriteLine(
"汽车在路上走!");
        }


        
public void Park()
        
{
            Console.WriteLine(
"找个地方停车吧!");
        }

    }

}


using  System;
using  System.Collections.Generic;
using  System.Text;

namespace  VisitorPattern.ex34_0
{
    
public class Plane : Vehicle
    
{
        
public override void Go()
        
{
            Console.WriteLine(
"飞机在天上飞!");
        }


        
public void Land()
        
{
            Console.WriteLine(
"找个机场降落吧!");
        }

    }

}

 

交通工具都是可以“走”的,但不同类新的交通工具有各自的特点,如轮船要靠岸,飞机要降落等。因此在子类中各自有各自的方法,这时子雷扩充了父类。即子类具有比父类更丰富的功能,如何使用这些功能?

一方面我们面向同一的接口,同时希望使用子类扩展的功能。为此不得不判断类型,并且需要强制转换,下面是示例代码:


static   void  Main()
        
{
            ex34_0.Vehicle myCar 
= new ex34_0.Car();
            ex34_0.Vehicle myBoat 
= new ex34_0.Boat();
            ex34_0.Vehicle myPlane 
= new ex34_0.Plane();

            GoAndStop(myCar);
            GoAndStop(myBoat);
            GoAndStop(myPlane);
        }


        
static   void  GoAndStop(ex34_0.Vehicle v)
        
{
            v.Go();
            
if (v.GetType() == typeof(ex34_0.Car)) ((ex34_0.Car)v).Park();
            
if (v.GetType() == typeof(ex34_0.Boat)) ((ex34_0.Boat)v).Pull();
            
if (v.GetType() == typeof(ex34_0.Plane)) ((ex34_0.Plane)v).Land();
        }



由于GoAndStop的传入类型是Vehicle,因此需要判断具体的类型并且实行强制转换才能执行子类的功能。在GoAndStop中显然已经破坏了面向接口,因为必须针对美中实现执行特定的编码,有没有更好的解决方法呢?

 

意图

实现通过统一的接口访问不同类型元素的操作,并且通过这个接口可以增加新的操作而不改变元素的类。

使用场合

当很多对象的接口不同,而我们希望通过这些对象有依赖于具体对象的操作时,可以使用访问者模式。


结构


效果
首 先需要说明非常重要的采用访问者模式的第一个代价是使用模式适用于雷机构稳定的系统。结构稳定指类和子类一旦确定,就不需要经常修改,但其中也包括增加新 的子类。从上面的结构可以看出,访问者依赖于结构中的所有子类。一旦子类变化,访问及相应的子类就要发生变化,这显然是不划算俄,如果这样,还不如直接修 改类结构。

2个代价是有可能破坏封装性。访问者为对象增加了新的操作,与在对象内直接增加操作有很大不同。这些操作只能通过共有方法或属性访问对象,很多场合下这显然是不够的。

为了使访问者能够访问内部变量,对象有可能不得公开这些变量,从而可能破坏封装性。

访问者带来的好处如下。

可以很容易地为对象结构增加新的操作,通过定义一个新的访问者即可在对象机构不变的情况下增加新操作。

使对象结构的接口统一,通过将无关的操作分离,使类结构的接口更趋近统一。

通过相同的的访问者接口可以获得不同的操作,而且这些操作可以是动态增加的。

可以访问结构中任何层次的对象,甚至不具有相同父类的对象也可以用同一个访问者访问,这种情况可以通过定义一个访问接口实现。

实现
采用访问者模式重构交通工具的示例代码,使用访问者调用具体类的特定方法使这些方法不向客户暴露,如图。


Visitor的接口如下:

using  System;
using  System.Collections.Generic;
using  System.Text;

namespace  VisitorPattern.ex34_2
{
    
public abstract class VehicleVisitor
    
{
        
public abstract void VisitCar(Car c);
        
public abstract void VisitBoat(Boat b);
        
public abstract void VisitPlane(Plane p);
    }

}


具体Visitor的代码如下:

using  System;
using  System.Collections.Generic;
using  System.Text;

namespace  VisitorPattern.ex34_2
{
    
public class VehicleStopVisitor:VehicleVisitor
    
{
        
public override void VisitCar(Car c)
        
{
            c.Park();
        }


        
public override void VisitBoat(Boat b)
        
{
            b.Pull();
        }


        
public override void VisitPlane(Plane p)
        
{
            p.Land();
        }

    }
    
}


在原有的Vehicle中增加了接受访问者的方法:

using  System;
using  System.Collections.Generic;
using  System.Text;

namespace  VisitorPattern.ex34_2
{
    
public class Vehicle
    
{
        
public virtual void Go()
        
{
            Console.WriteLine(
"交通工具走!");
        }

        
public virtual void Accept(VehicleVisitor v) { }
    }

}

具体类决定调用访问者中的方法:

using  System;
using  System.Collections.Generic;
using  System.Text;

namespace  VisitorPattern.ex34_2
{
    
public class Boat:Vehicle
    
{
        
public override void Go()
        
{
            Console.WriteLine(
"船在水上开!");
        }


        
public void Pull()
        
{
            Console.WriteLine(
"找个码头停船吧!");
        }


        
public override void Accept(VehicleVisitor v)
        
{
            v.VisitBoat(
this);
        }

    }

}


using  System;
using  System.Collections.Generic;
using  System.Text;

namespace  VisitorPattern.ex34_0
{
    
public class Car:Vehicle
    
{
        
public override void Go()
        
{
            Console.WriteLine(
"汽车在路上走!");
        }


        
public void Park()
        
{
            Console.WriteLine(
"找个地方停车吧!");
        }

    }

}



using  System;
using  System.Collections.Generic;
using  System.Text;

namespace  VisitorPattern.ex34_0
{
    
public class Plane : Vehicle
    
{
        
public override void Go()
        
{
            Console.WriteLine(
"飞机在天上飞!");
        }


        
public void Land()
        
{
            Console.WriteLine(
"找个机场降落吧!");
        }

    }

}


使用代码

static   void  Main()
        
{
            VisitorPattern.ex34_2.Vehicle myCar 
= new VisitorPattern.ex34_2.Car();
            VisitorPattern.ex34_2.Vehicle myBoat 
= new VisitorPattern.ex34_2.Boat();
            VisitorPattern.ex34_2.Vehicle myPlane 
= new VisitorPattern.ex34_2.Plane();

            VisitorPattern.ex34_2.VehicleStopVisitor v 
= new VisitorPattern.ex34_2.VehicleStopVisitor();

            myCar.Accept(v);
            myBoat.Accept(v);
            myPlane.Accept(v);
        }



如果子类的方法需要扩展,可以引入新的Visitor类型。而客户端不用修改,这就是使用Visitor的好处。

相关模式:
组合模式:访问者可以访问采用组合模式定义的对象结构。
解释器模式:访问者经常用于解释器的实现。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值