访问者模式的函数式实现

栏目: 后端 · 发布时间: 5年前

内容简介:在面向对象的编程中,当需要向现有对象添加新操作时,通常使用访问者模式,但由于设计原因不可能修改对象本身并在实现中直接添加缺少的操作。为此,我们域中的每个对象都必须有一个接受访问者并将自己传递给该访问者的方法,然后必须实现如下所示的接口。此时,我们可以定义一个简单的业务域,并展示不同目的的不同访问者如何访问它。对于此示例,我们的域模型将由简单的几何形状构成。正如预期的那样,我们域的所有类都必须实现Element接口。他们以同样的方式这样做,将自己传递给访客。然后,我们可以定义一个Visitor接口,为每个要访

在面向对象的编程中,当需要向现有对象添加新操作时,通常使用访问者模式,但由于设计原因不可能修改对象本身并在实现中直接添加缺少的操作。为此,我们域中的每个对象都必须有一个接受访问者并将自己传递给该访问者的方法,然后必须实现如下所示的接口。

<b>interface</b> Element {
    <T> T accept(Visitor<T> visitor);
}

此时,我们可以定义一个简单的业务域,并展示不同目的的不同访问者如何访问它。对于此示例,我们的域模型将由简单的几何形状构成。

<b>public</b> <b>static</b> <b>class</b> Square implements Element {
    <b>public</b> <b>final</b> <b>double</b> side;
 
    <b>public</b> Square(<b>double</b> side) {
        <b>this</b>.side = side;
    }
 
    @Override
    <b>public</b> <T> T accept(Visitor<T> visitor) {
        <b>return</b> visitor.visit(<b>this</b>);
    }
}
 
<b>public</b> <b>static</b> <b>class</b> Circle implements Element {
    <b>public</b> <b>final</b> <b>double</b> radius;
 
    <b>public</b> Circle(<b>double</b> radius) {
        <b>this</b>.radius = radius;
    }
 
    @Override
    <b>public</b> <T> T accept(Visitor<T> visitor) {
        <b>return</b> visitor.visit(<b>this</b>);
    }
}
 
<b>public</b> <b>static</b> <b>class</b> Rectangle implements Element {
    <b>public</b> <b>final</b> <b>double</b> width;
    <b>public</b> <b>final</b> <b>double</b> height;
 
    <b>public</b> Rectangle( <b>double</b> width, <b>double</b> height ) {
        <b>this</b>.width = width;
        <b>this</b>.height = height;
    }
 
    @Override
    <b>public</b> <T> T accept(Visitor<T> visitor) {
        <b>return</b> visitor.visit(<b>this</b>);
    }
}

正如预期的那样,我们域的所有类都必须实现Element接口。他们以同样的方式这样做,将自己传递给访客。然后,我们可以定义一个Visitor接口,为每个要访问的对象类型声明一个抽象方法。

<b>interface</b> Visitor<T> {
    T visit(Square element);
    T visit(Circle element);
    T visit(Rectangle element);
}

这就是为什么,尽管我们域中所有对象的accept()方法具有完全相同的实现,我们不能在一个公共抽象类中概括它们,或者甚至更好地将它直接移动到Element接口中作为其默认方法之一。事实上,调用访问者的对象的编译时类型是必要的,以确定必须调用访问方法的不同重载版本中的哪一个。现在可以创建此Visitor接口的不同具体实现。例如,我们可以计算出不同形状的区域:

<b>public</b> <b>static</b> <b>class</b> AreaVisitor implements Visitor<Double> {
 
    @Override
    <b>public</b> Double visit( Square element ) {
        <b>return</b> element.side * element.side;
    }
 
    @Override
    <b>public</b> Double visit( Circle element ) {
        <b>return</b> Math.PI * element.radius * element.radius;
    }
 
    @Override
    <b>public</b> Double visit( Rectangle element ) {
        <b>return</b> element.height * element.width;
    }
}

另一个计算他们的周长:

<b>public</b> <b>static</b> <b>class</b> PerimeterVisitor implements Visitor<Double> {
 
    @Override
    <b>public</b> Double visit( Square element ) {
        <b>return</b> 4 * element.side ;
    }
 
    @Override
    <b>public</b> Double visit( Circle element ) {
        <b>return</b> 2 * Math.PI * element.radius;
    }
 
    @Override
    <b>public</b> Double visit( Rectangle element ) {
        <b>return</b> ( 2 * element.height + 2 * element.width );
    }
}

我们终于可以让这些访客在计算形状列表的面积和周长之和。

<b>public</b> <b>static</b> <b>void</b> main(String[] args) {
    List<Element> figures = Arrays.asList( <b>new</b> Circle( 4 ), <b>new</b> Square( 5 ), <b>new</b> Rectangle( 6, 7 ));
 
    <b>double</b> totalArea = 0.0;
    Visitor<Double> areaVisitor = <b>new</b> AreaVisitor();
    <b>for</b> (Element figure : figures) {
        totalArea += figure.accept( areaVisitor );
    }
    System.out.println(<font>"Total area = "</font><font> + totalArea);
 
    <b>double</b> totalPerimeter = 0.0;
    Visitor<Double> perimeterVisitor = <b>new</b> PerimeterVisitor();
    <b>for</b> (Element figure : figures) {
        totalPerimeter += figure.accept( perimeterVisitor );
    }
    System.out.println(</font><font>"Total perimeter = "</font><font> + totalPerimeter);
}
</font>

值得注意的是访问者实际上做了什么:它允许为每种类型的对象定义一个不同的方法。在函数式编程中,有一种更自然,更强大的习惯用法来实现相同的结果:模式匹配。实际上对于这个用例,它已经足够有一个在类上工作的switch语句,我真的很想知道为什么这在 Java 中是不可能的,而在Java 7中他们增加了切换String的可能性,在我看来它几乎是无用的在大多数情况下也是一种不好的做法。也就是说,可以实现一个简单的实用程序类,它可以让我们拥有类似的功能。

<b>public</b> <b>class</b> LambdaVisitor<A> implements Function<Object, A> {
    <b>private</b> Map<Class<?>, Function<Object, A>> fMap = <b>new</b> HashMap<>();
 
    <b>public</b> <B> Acceptor<A, B> on(Class<B> clazz) {
        <b>return</b> <b>new</b> Acceptor<>(<b>this</b>, clazz);
    }
 
    @Override
    <b>public</b> A apply( Object o ) {
        <b>return</b> fMap.get(o.getClass()).apply( o );
    }
 
    <b>static</b> <b>class</b> Acceptor<A, B> {
        <b>private</b> <b>final</b> LambdaVisitor visitor;
        <b>private</b> <b>final</b> Class<B> clazz;
 
        Acceptor( LambdaVisitor<A> visitor, Class<B> clazz ) {
            <b>this</b>.visitor = visitor;
            <b>this</b>.clazz = clazz;
        }
 
        <b>public</b> LambdaVisitor<A> then(Function<B, A> f) {
            visitor.fMap.put( clazz, f );
            <b>return</b> visitor;
        }
    }
}

LambdaVisitor类实现一个Function,然后转换泛型Object为类型A的结果。on()方法是我们可以通过它定义此Function的行为的方法。它接受一个Class 作为参数并返回其Acceptor内部类的实例。这个类只有一个方法,然后()接受一个函数 。换句话说,当函数应用于B的实例时,传递给on()方法的类会产生类型A的结果,这是LambdaVisitor函数应该返回的结果。Class  ,Function双双注册在 LambdaVisitor中的Map。最后,then()方法返回原始的LambdaVisitor实例,从而允许为另一个类流畅地注册另一个Function。

让我们尝试将其用于我们的原始任务。例如,我们可以定义一个函数,当应用于我们的域模型中的一个形状时,返回其区域。

<b>static</b> Function<Object, Double> areaCalculator = <b>new</b> LambdaVisitor<Double>()
        .on(Square.<b>class</b>).then( s -> s.side * s.side )
        .on(Circle.<b>class</b>).then( c -> Math.PI * c.radius * c.radius )
        .on(Rectangle.<b>class</b>).then( r -> r.height * r.width );

当此函数应用于形状时,LambdaVisitor选择为该对象的类定义的函数,并将其应用于对象本身。例如,当它与Square实例一起传递时,选择该函数

s -> s.side * s.side

并将其应用于该对象以返回Square的区域。请注意,由于类型推断,我们不必将类型Square重复到lambda的参数声明中:then()方法已经期望与调用on()方法的同一个Class的实例。类似地,我们可以定义计算不同形状的周长的第二个函数。

<b>static</b> Function<Object, Double> perimeterCalculator = <b>new</b> LambdaVisitor<Double>()
        .on(Square.<b>class</b>).then( s -> 4 * s.side )
        .on(Circle.<b>class</b>).then( c -> 2 * Math.PI * c.radius )
        .on(Rectangle.<b>class</b>).then( r -> 2 * r.height + 2 * r.width );

此时,可以直接使用这些函数计算前一个列表中所有形状的面积和周长之和。

<b>public</b> <b>static</b> <b>void</b> main( String[] args ) {
    List<Object> figures = Arrays.asList( <b>new</b> Circle( 4 ), <b>new</b> Square( 5 ), <b>new</b> Rectangle( 6, 7 ) );
 
    <b>double</b> totalArea = figures.stream().map( areaCalculator ).reduce( 0.0, (v1, v2) -> v1 + v2 );
    System.out.println(<font>"Total area = "</font><font> + totalArea);
 
    <b>double</b> totalPerimeter = figures.stream().map( perimeterCalculator ).reduce( 0.0, (v1, v2) -> v1 + v2 );
    System.out.println(</font><font>"Total perimeter = "</font><font> + totalPerimeter);
}
</font>

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

构建之法(第三版)

构建之法(第三版)

邹欣 / 人民邮电出版社 / 2017-6 / 69.00元

软件工程牵涉的范围很广, 同时也是一般院校的同学反映比较空洞乏味的课程。 但是,软件工程 的技术对于投身 IT 产业的学生来说是非常重要的。作者有在世界一流软件企业 20 年的一线软件开 发经验,他在数所高校进行了多年的软件工程教学实践,总结出了在 16 周的时间内让同学们通过 “做 中学 (Learning By Doing)” 掌握实用的软件工程技术的教学计划,并得到高校师生的积极反馈。在此 ......一起来看看 《构建之法(第三版)》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具