十九、类型信息(5)

2023-11-01 22:12
文章标签 十九 类型信息

本文主要是介绍十九、类型信息(5),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

动态代理

_代理_是基本的设计模式之一。一个对象封装真实对象,代替其提供其他或不同的操作—这些操作通常涉及到与“真实”对象的通信,因此代理通常充当中间对象。这是一个简单的示例,显示代理的结构:

interface Interface {void doSomething();void somethingElse(String arg);
}class RealObject implements Interface {@Overridepublic void doSomething() {System.out.println("doSomething");}@Overridepublic void somethingElse(String arg) {System.out.println("somethingElse " + arg);}
}class SimpleProxy implements Interface {private Interface proxied;SimpleProxy(Interface proxied) {this.proxied = proxied;}@Overridepublic void doSomething() {System.out.println("SimpleProxy doSomething");proxied.doSomething();}@Overridepublic void somethingElse(String arg) {System.out.println("SimpleProxy somethingElse " + arg);proxied.somethingElse(arg);}
}class SimpleProxyDemo {public static void consumer(Interface iface) {iface.doSomething();iface.somethingElse("bonobo");}public static void main(String[] args) {consumer(new RealObject());consumer(new SimpleProxy(new RealObject()));}
}

输出结果:

在这里插入图片描述

因为 consumer() 接受 Interface,所以它不知道获得的是 RealObject 还是 SimpleProxy,因为两者都实现了 Interface
但是,在客户端和 RealObject 之间插入的 SimpleProxy 执行操作,然后在 RealObject 上调用相同的方法。

当你希望将额外的操作与“真实对象”做分离时,代理可能会有所帮助,尤其是当你想要轻松地启用额外的操作时,反之亦然(设计模式就是封装变更—所以你必须改变一些东西以证明模式的合理性)。例如,如果你想跟踪对 RealObject 中方法的调用,或衡量此类调用的开销,该怎么办?你不想这部分代码耦合到你的程序中,而代理能使你可以很轻松地添加或删除它。

Java 的_动态代理_更进一步,不仅动态创建代理对象而且动态处理对代理方法的调用。在动态代理上进行的所有调用都被重定向到单个_调用处理程序_,该处理程序负责发现调用的内容并决定如何处理。这是 SimpleProxyDemo.java 使用动态代理重写的例子:

import java.lang.reflect.*;class DynamicProxyHandler implements InvocationHandler {private Object proxied;DynamicProxyHandler(Object proxied) {this.proxied = proxied;}@Overridepublic Objectinvoke(Object proxy, Method method, Object[] args)throws Throwable {System.out.println("**** proxy: " + proxy.getClass() +", method: " + method + ", args: " + args);if (args != null) {for (Object arg : args) {System.out.println("  " + arg);}}return method.invoke(proxied, args);}
}class SimpleDynamicProxy {public static void consumer(Interface iface) {iface.doSomething();iface.somethingElse("bonobo");}public static void main(String[] args) {RealObject real = new RealObject();consumer(real);// Insert a proxy and call again:Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(),new Class[]{Interface.class},new DynamicProxyHandler(real));consumer(proxy);}
}

输出结果:

在这里插入图片描述

可以通过调用静态方法 Proxy.newProxyInstance() 来创建动态代理,该方法需要一个类加载器(通常可以从已加载的对象中获取),希望代理实现的接口列表(不是类或抽象类),以及接口 InvocationHandler 的一个实现。动态代理会将所有调用重定向到调用处理程序,因此通常为调用处理程序的构造函数提供对“真实”对象的引用,以便一旦执行中介任务便可以转发请求。

invoke() 方法被传递给代理对象,以防万一你必须区分请求的来源—但是在很多情况下都无需关心。但是,在 invoke() 内的代理上调用方法时要小心,因为接口的调用是通过代理重定向的。

通常执行代理操作,然后使用 Method.invoke() 将请求转发给被代理对象,并携带必要的参数。这在一开始看起来是有限制的,好像你只能执行一般的操作。但是,可以过滤某些方法调用,同时传递其他方法调用:

import java.lang.reflect.*;class MethodSelector implements InvocationHandler {private Object proxied;MethodSelector(Object proxied) {this.proxied = proxied;}@Overridepublic Objectinvoke(Object proxy, Method method, Object[] args)throws Throwable {if (method.getName().equals("interesting")) {System.out.println("Proxy detected the interesting method");}return method.invoke(proxied, args);}
}interface SomeMethods {void boring1();void boring2();void interesting(String arg);void boring3();
}class Implementation implements SomeMethods {@Overridepublic void boring1() {System.out.println("boring1");}@Overridepublic void boring2() {System.out.println("boring2");}@Overridepublic void interesting(String arg) {System.out.println("interesting " + arg);}@Overridepublic void boring3() {System.out.println("boring3");}
}class SelectingMethods {public static void main(String[] args) {SomeMethods proxy =(SomeMethods) Proxy.newProxyInstance(SomeMethods.class.getClassLoader(),new Class[]{SomeMethods.class},new MethodSelector(new Implementation()));proxy.boring1();proxy.boring2();proxy.interesting("bonobo");proxy.boring3();}
}

输出结果:

在这里插入图片描述

在这个示例里,我们只是在寻找方法名,但是你也可以寻找方法签名的其他方面,甚至可以搜索特定的参数值。

动态代理不是你每天都会使用的工具,但是它可以很好地解决某些类型的问题。你可以在 Erich Gamma 等人的_设计模式_中了解有关_代理_和其他设计模式的更多信息。

Optional类

如果你使用内置的 null 来表示没有对象,每次使用引用的时候就必须测试一下引用是否为 null,这显得有点枯燥,而且势必会产生相当乏味的代码。问题在于 null 没什么自己的行为,只会在你想用它执行任何操作的时候产生 NullPointExceptionjava.util.Optional(首次出现是在函数式编程这章)为 null 值提供了一个轻量级代理,Optional 对象可以防止你的代码直接抛出 NullPointException

虽然 Optional 是 Java 8 为了支持流式编程才引入的,但其实它是一个通用的工具。为了证明这点,在本节中,我们会把它用在普通的类中。因为涉及一些运行时检测,所以把这一小节放在了本章。

实际上,在所有地方都使用 Optional 是没有意义的,有时候检查一下是不是 null 也挺好的,或者有时我们可以合理地假设不会出现 null,甚至有时候检查 NullPointException 异常也是可以接受的。Optional 最有用武之地的是在那些“更接近数据”的地方,在问题空间中代表实体的对象上。举个简单的例子,很多系统中都有 Person 类型,代码中有些情况下你可能没有一个实际的 Person 对象(或者可能有,但是你还没用关于那个人的所有信息)。这时,在传统方法下,你会用到一个 null 引用,并且在使用的时候测试它是不是 null。而现在,我们可以使用 Optional

import java.util.*;class Person {public final Optional<String> first;public final Optional<String> last;public final Optional<String> address;// etc.public final Boolean empty;Person(String first, String last, String address) {this.first = Optional.ofNullable(first);this.last = Optional.ofNullable(last);this.address = Optional.ofNullable(address);empty = !this.first.isPresent()&& !this.last.isPresent()&& !this.address.isPresent();}Person(String first, String last) {this(first, last, null);}Person(String last) {this(null, last, null);}Person() {this(null, null, null);}@Overridepublic String toString() {if (empty) {return "<Empty>";}return (first.orElse("") +" " + last.orElse("") +" " + address.orElse("")).trim();}public static void main(String[] args) {System.out.println(new Person());System.out.println(new Person("Smith"));System.out.println(new Person("Bob", "Smith"));System.out.println(new Person("Bob", "Smith","11 Degree Lane, Frostbite Falls, MN"));}
}

输出结果:

在这里插入图片描述

Person 的设计有时候又叫“数据传输对象(DTO,data-transfer object)”。注意,所有字段都是 publicfinal 的,所以没有 gettersetter 方法。也就是说,Person 是不可变的,你只能通过构造器给它赋值,之后就只能读而不能修改它的值(字符串本身就是不可变的,因此你无法修改字符串的内容,也无法给它的字段重新赋值)。如果你想修改一个 Person,你只能用一个新的 Person 对象来替换它。empty 字段在对象创建的时候被赋值,用于快速判断这个 Person 对象是不是空对象。

如果想使用 Person,就必须使用 Optional 接口才能访问它的 String 字段,这样就不会意外触发 NullPointException 了。

现在假设你已经因你惊人的理念而获得了一大笔风险投资,现在你要招兵买马了,但是在虚位以待时,你可以将 Person Optional 对象放在每个 Position 上:

import java.util.*;class EmptyTitleException extends RuntimeException {
}class Position {private String title;private Person person;Position(String jobTitle, Person employee) {setTitle(jobTitle);setPerson(employee);}Position(String jobTitle) {this(jobTitle, null);}public String getTitle() {return title;}public void setTitle(String newTitle) {// Throws EmptyTitleException if newTitle is null:title = Optional.ofNullable(newTitle).orElseThrow(EmptyTitleException::new);}public Person getPerson() {return person;}public void setPerson(Person newPerson) {// Uses empty Person if newPerson is null:person = Optional.ofNullable(newPerson).orElse(new Person());}@Overridepublic String toString() {return "Position: " + title +", Employee: " + person;}public static void main(String[] args) {System.out.println(new Position("CEO"));System.out.println(new Position("Programmer",new Person("Arthur", "Fonzarelli")));try {new Position(null);} catch (Exception e) {System.out.println("caught " + e);}}
}

输出结果:

在这里插入图片描述

这里使用 Optional 的方式不太一样。请注意,titleperson 都是普通字段,不受 Optional 的保护。但是,修改这些字段的唯一途径是调用 setTitle()setPerson() 方法,这两个都借助 Optional 对字段进行了严格的限制。

同时,我们想保证 title 字段永远不会变成 null 值。为此,我们可以自己在 setTitle() 方法里边检查参数 newTitle 的值。但其实还有更好的做法,函数式编程一大优势就是可以让我们重用经过验证的功能(即便是个很小的功能),以减少自己手动编写代码可能产生的一些小错误。所以在这里,我们用 ofNullable()newTitle 转换一个 Optional(如果传入的值为 nullofNullable() 返回的将是 Optional.empty())。紧接着我们调用了 orElseThrow() 方法,所以如果 newTitle 的值是 null,你将会得到一个异常。这里我们并没有把 title 保存成 Optional,但通过应用 Optional 的功能,我们仍然如愿以偿地对这个字段施加了约束。

EmptyTitleException 是一个 RuntimeException,因为它意味着程序存在错误。在这个方案里边,你仍然可能会得到一个异常。但不同的是,在错误产生的那一刻(向 setTitle()null 值时)就会抛出异常,而不是发生在其它时刻,需要你通过调试才能发现问题所在。另外,使用 EmptyTitleException 还有助于定位 BUG。

Person 字段的限制又不太一样:如果你把它的值设为 null,程序会自动把将它赋值成一个空的 Person 对象。先前我们也用过类似的方法把字段转换成 Optional,但这里我们是在返回结果的时候使用 orElse(new Person()) 插入一个空的 Person 对象替代了 null

Position 里边,我们没有创建一个表示“空”的标志位或者方法,因为 person 字段的 Person 对象为空,就表示这个 Position 是个空缺位置。之后,你可能会发现你必须添加一个显式的表示“空位”的方法,但是正如 YAGNI (You Aren’t Going to Need It,你永远不需要它)所言,在初稿时“实现尽最大可能的简单”,直到程序在某些方面要求你为其添加一些额外的特性,而不是假设这是必要的。

请注意,虽然你清楚你使用了 Optional,可以免受 NullPointerExceptions 的困扰,但是 Staff 类却对此毫不知情。

import java.util.*;public class Staff extends ArrayList<Position> {public void add(String title, Person person) {add(new Position(title, person));}public void add(String... titles) {for (String title : titles) {add(new Position(title));}}public Staff(String... titles) {add(titles);}public Boolean positionAvailable(String title) {for (Position position : this) {if (position.getTitle().equals(title) &&position.getPerson().empty) {return true;}}return false;}public void fillPosition(String title, Person hire) {for (Position position : this) {if (position.getTitle().equals(title) &&position.getPerson().empty) {position.setPerson(hire);return;}}throw new RuntimeException("Position " + title + " not available");}public static void main(String[] args) {Staff staff = new Staff("President", "CTO","Marketing Manager", "Product Manager","Project Lead", "Software Engineer","Software Engineer", "Software Engineer","Software Engineer", "Test Engineer","Technical Writer");staff.fillPosition("President",new Person("Me", "Last", "The Top, Lonely At"));staff.fillPosition("Project Lead",new Person("Janet", "Planner", "The Burbs"));if (staff.positionAvailable("Software Engineer")) {staff.fillPosition("Software Engineer",new Person("Bob", "Coder", "Bright Light City"));}System.out.println(staff);}
}

输出结果:

在这里插入图片描述

注意,在有些地方你可能还是要测试引用是不是 Optional,这跟检查是否为 null 没什么不同。但是在其它地方(例如本例中的 toString() 转换),你就不必执行额外的测试了,而可以直接假设所有对象都是有效的。

标记接口

有时候使用一个标记接口来表示空值会更方便。标记接口里边什么都没有,你只要把它的名字当做标签来用就可以。

// onjava/Null.java
package onjava;
public interface Null {}

如果你用接口取代具体类,那么就可以使用 DynamicProxy 来自动地创建 Null 对象。假设我们有一个 Robot 接口,它定义了一个名字、一个模型和一个描述 Robot 行为能力的 List<Operation>

import java.util.*;public interface Robot {String name();String model();List<Operation> operations();static void test(Robot r) {if (r instanceof Null) {System.out.println("[Null Robot]");}System.out.println("Robot name: " + r.name());System.out.println("Robot model: " + r.model());for (Operation operation : r.operations()) {System.out.println(operation.description.get());operation.command.run();}}
}

你可以通过调用 operations() 来访问 Robot 的服务。Robot 里边还有一个 static 方法来执行测试。

Operation 包含一个描述和一个命令(这用到了命令模式)。它们被定义成函数式接口的引用,所以可以把 lambda 表达式或者方法的引用传给 Operation 的构造器:

import java.util.function.*;public class Operation {public final Supplier<String> description;public final Runnable command;public Operation(Supplier<String> descr, Runnable cmd) {description = descr;command = cmd;}
}

现在我们可以创建一个扫雪 Robot

import java.util.*;public class SnowRemovalRobot implements Robot {private String name;public SnowRemovalRobot(String name) {this.name = name;}@Overridepublic String name() {return name;}@Overridepublic String model() {return "SnowBot Series 11";}private List<Operation> ops = Arrays.asList(new Operation(() -> name + " can shovel snow",() -> System.out.println(name + " shoveling snow")),new Operation(() -> name + " can chip ice",() -> System.out.println(name + " chipping ice")),new Operation(() -> name + " can clear the roof",() -> System.out.println(name + " clearing roof")));@Overridepublic List<Operation> operations() {return ops;}public static void main(String[] args) {Robot.test(new SnowRemovalRobot("Slusher"));}
}

输出结果:

在这里插入图片描述

假设存在许多不同类型的 Robot,我们想让每种 Robot 都创建一个 Null 对象来执行一些特殊的操作——在本例中,即提供 Null 对象所代表 Robot 的确切类型信息。这些信息是通过动态代理捕获的:

import java.lang.reflect.*;
import java.util.*;
import java.util.stream.*;class NullRobotProxyHandler implements InvocationHandler {private String nullName;private Robot proxied = new NRobot();NullRobotProxyHandler(Class<? extends Robot> type) {nullName = type.getSimpleName() + " NullRobot";}private class NRobot implements Null, Robot {@Overridepublic String name() {return nullName;}@Overridepublic String model() {return nullName;}@Overridepublic List<Operation> operations() {return Collections.emptyList();}}@Overridepublic Objectinvoke(Object proxy, Method method, Object[] args)throws Throwable {return method.invoke(proxied, args);}
}public class NullRobot {public static RobotnewNullRobot(Class<? extends Robot> type) {return (Robot) Proxy.newProxyInstance(NullRobot.class.getClassLoader(),new Class[]{Null.class, Robot.class},new NullRobotProxyHandler(type));}public static void main(String[] args) {Stream.of(new SnowRemovalRobot("SnowBee"),newNullRobot(SnowRemovalRobot.class)).forEach(Robot::test);}
}

输出结果:

在这里插入图片描述

无论何时,如果你需要一个空 Robot 对象,只需要调用 newNullRobot(),并传递需要代理的 Robot 的类型。这个代理满足了 RobotNull 接口的需要,并提供了它所代理的类型的确切名字。

Mock 对象和桩

Mock 对象和 **桩(Stub)**在逻辑上都是 Optional 的变体。他们都是最终程序中所使用的“实际”对象的代理。不过,Mock 对象和桩都是假扮成那些可以传递实际信息的实际对象,而不是像 Optional 那样把包含潜在 null 值的对象隐藏。

Mock 对象和桩之间的的差别在于程度不同。Mock 对象往往是轻量级的,且用于自测试。通常,为了处理各种不同的测试场景,我们会创建出很多 Mock 对象。而桩只是返回桩数据,它通常是重量级的,并且经常在多个测试中被复用。桩可以根据它们被调用的方式,通过配置进行修改。因此,桩是一种复杂对象,它可以做很多事情。至于 Mock 对象,如果你要做很多事,通常会创建大量又小又简单的 Mock 对象。

这篇关于十九、类型信息(5)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/326120

相关文章

【重学 MySQL】十九、位运算符的使用

【重学 MySQL】十九、位运算符的使用 示例检查权限添加权限移除权限 在 MySQL 中,位运算符允许你直接在整数类型的列或表达式上进行位级操作。这些操作对于处理那些需要在二进制表示上进行直接修改或比较的场景特别有用,比如权限管理、状态标记等。 &(位与) 对两个数的二进制表示进行位与操作。只有两个相应的二进制位都为 1 时,结果的该位才为 1,否则为 0。 |(位

类型信息:反射-Class

在说反射前提一个概念:RTTI(在运行时,识别一个对象的类型) public class Shapes {public static void main(String[] args) {List<Shape> shapes = Arrays.asList(new Circle(), new Square(), new Triangle());for (Shape shape : shapes

Flink实例(十九):Flink 异步IO (四)实例 (二) MySQL

业务如下: 接收kafka数据,转为user对象,调用async,使用user.id 查询对应的phone,放回user对象,输出  主类: import com.alibaba.fastjson.JSON;import com.venn.common.Common;import org.apache.flink.formats.json.JsonNodeDeserializatio

【硬刚ES】ES基础(十九) Query Filtering 与多字符串多字段查询

本文是对《【硬刚大数据之学习路线篇】从零到大数据专家的学习指南(全面升级版)》的ES部分补充。

OCI编程高级篇(十九) 创建和使用OCI连接池

上一节介绍了连接池的概念和使用连接池的步骤,这一节看看具体的操作是怎样的,先看一下用到的函数原型和参数。 创建连接池函数OCIConnectionPoolCreate(),原型和参数如下。 sword OCIConnectionPoolCreate ( OCIEnv *envhp,     OCIError          *errhp,     OCICPool        *poolh

【Unity 3D】学习笔记十九:实例:游戏人物移动

结合学习笔记十八,来学习游戏中人物的基本移动(真的感觉好基础啊)。不多说,直接上代码。 例: //动画数组private var animUp: Object[] ;private var animDown: Object[] ;private var animLeft: Object[] ;private var animRight: Object[] ;//地图贴图priv

斗破C++编程入门系列之十九:C++程序设计必知:多文件结构和编译预处理命令(九星斗者)

斗破C++目录: 斗破C++编程入门系列之前言(斗之气三段) 斗破C++编程入门系列之二:Qt的使用介绍(斗之气三段) 斗破C++编程入门系列之三:数据结构(斗之气三段) 斗破C++编程入门系列之四:运算符和表达式(斗之气五段) 斗破C++编程入门系列之五:算法的基本控制结构之选择结构(斗之气八段) 斗破C++编程入门系列之六:算法的基本控制结构之循环结构(斗之气八段) 斗破C++编程入门系列之

Django REST Framework(十九)权限

Django REST framework (DRF) 的权限认证涉及以下几个方面:全局权限配置、局部权限配置、自定义权限类、以及自定义认证类。以下是关于这些方面的详细说明: 1. 全局权限配置 在 Django 项目的配置文件 settings.py 中,可以全局配置 DRF 的权限管理类。这种设置适用于整个项目中的所有视图。默认情况下,如果不做任何配置,DRF 会允许所有用户访问视图(Al

【从问题中去学习k8s】k8s中的常见面试题(夯实理论基础)(十九)

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》:python零基础入门学习 《python运维脚本》: python运维脚本实践 《shell》:shell学习 《terraform》持续更新中:terraform_Aws学习零基础入门到最佳实战 《k8》从问题中去学习k8s 《docker学习》暂未更新 《ceph学习》ceph日常问题解决分享 《日志收集》ELK

代谢组数据分析(十九):随机森林构建代谢组预后模型

介绍 建立胃癌(GC)预后模型时,从队列3中的181名患者中,使用右删失结果数据进行了随机分层抽样,分为训练数据集(n = 121)和测试数据集(n = 60)。训练了一个包含1000棵树的随机生存森林(RSF)模型,根据它们基于排列的特征重要性来选择突出的特征。通过再次训练随机生存模型,并选取28种代谢物,建立了显示出色预测能力(AUROC = 0.832,95% CI:0.697-0.9