部分内容引用:

https://blog.csdn.net/shulianghan/article/details/119798155

一、定义

1.1定义

对于现实生活中的代理,大家非常好理解。我们需要代理,主要因为几个原因:

  • 太忙-例如房产中介、代购
  • 目前对象不是自身可以直接接触的-例如托人办事、例如掏钱购买某种服务都可以理解为代理
  • 自己不方便出面的-例如找帮手干活

但在计算机中,这些不是太好理解。

因为我们设计程序主要满足性能和扩展、维护的要求,那么代理又可以给我们带来什么?

看看某些地方对这个定义:(来自于百度百科https://baike.baidu.com/item/%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/8374046?fr=ge_ala)

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用

著名的代理模式例子为引用计数(英语:reference counting)指针对象。

当一个复杂对象的多份副本须存在时,代理模式可以结合享元模式以减少存储器用量。典型作法是创建一个复杂对象及多个代理者,每个代理者会引用到原本的复杂对象。

而作用在代理者的运算会转送到原本对象。一旦所有的代理者都不存在时,复杂对象会被移除。 [1]

组成:

抽象角色:通过接口或抽象类声明真实角色实现的业务方法。

代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用
 

也就是说在程序设计中,代理起到的作用和现实生活是类似的,使用的原因也是类似的:不方便或者不能,并且可以基于代理实现一些稍微复杂的功能。

1.2优点

(1).职责清晰

真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。
(2).代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
(3).高扩展性 [1]
 
分离目标对象 : 代理模式 能将 代理对象 与 真实被调用的 目标对象 分离 ;
降低耦合 : 在一定程度上 , 降低了系统耦合性 , 扩展性好 ;
保护目标对象 : 代理类 代理目标对象的业务逻辑 , 客户端 直接与 代理类 进行交互 , 客户端 与 实际的目标对象之间没有关联 ;
增强目标对象 : 代理类 可以 在 目标对象基础上 , 添加新的功能 ;

1.3缺点

  • 类个数增加 : 代理模式 会 造成 系统中 类的个数 增加 , 比不使用代理模式增加了代理类 , 系统的复杂度增加 ; ( 所有的设计模式都有这个缺点 )
  • 性能降低 : 在 客户端 和 目标对象 之间 , 增加了一个代理对象 , 造成 请求处理速度变慢 ;

二、代码

2.1先来看经典代理的实现例子

通过接口和工厂实现

接口(抽象类)--销售代理接口(客户的需求)

package study.base.designPattern.proxy.normal;

public interface Saler {
public void sale(String thing);
}

具体代理(实现客户需求/接口)--买书代理人

package study.base.designPattern.proxy.normal;

public class BookSaler implements Saler {

    @Override
public void sale(String thing) {
System.out.println("........,嗯嗯,啊啊,汪汪!来一来,看一看["+thing+"],一次销售,终生保用"); } }

客户调用代理

package study.base.designPattern.proxy.normal;

/**
* 体力有限的销售代码
* @author lzfto
*
*/
public class SalerProxy implements Saler { private Saler saler; private int power; public SalerProxy() {
saler=new BookSaler();
power=100;
} private void rest() {
System.out.println("体力不济,累了。请下次再来!");
this.power+=5;
if (power>100) {
power=100;
}
} @Override
public void sale(String thing) {
if (power<30) {
this.rest();
}
else {
saler.sale(thing);
power--;
}
} public static void main(String[] args) {
SalerProxy proxy=new SalerProxy();
for (int i=0;i<100;i++) {
proxy.sale("大米");
}
} }

这几个代码实现了代理的根本思路:客户呼叫一个特定代理,由代理实现具体的功能(卖书)。

这种实现起来其实很像适配器、装饰器模式。

关于这个问题,其实也是很多人的疑惑:https://zhuanlan.zhihu.com/p/296319765

但这个问题,这个链接说得好像也不是太清晰,但这个都不是重点。重点是实际应用的时候,再仔细分析下即可。

---

这个代码例子并不能说服我们一定要去使用代理模式去间接调用SalerProxy,我们肯定有疑问:为什么不能直接使用BookSaler了?

所以,这个经典的代码中只能得到这样的知识:代理代码编写方式,优点类似装饰器或者适配器模式。 暂时还没有看到它独有的优点。

2.2 java代理功能实现的例子

java本身提供了代理工具来帮助实现稍微复杂一些的功能,这个工具就是InvocationHandler+Proxy

package study.base.designPattern.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; public class ItManageProxy implements InvocationHandler { private Object itManage; public ItManageProxy(Object itManage){
this.itManage=itManage;
} public ItManageProxy(){
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (itManage==null){
itManage=new ItManageImpl();
}
Object obj=method.invoke(itManage, args);
return obj;
} }
package study.base.designPattern.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy; public class ItManageTest { public static void main(String[] args) {
ItManage itManage = new ItManageImpl();
InvocationHandler handler = new ItManageProxy(itManage);
ItManage test = (ItManage) Proxy.newProxyInstance(handler.getClass().getClassLoader(),
itManage.getClass().getInterfaces(), handler);
test.encourage("新成员", "认真工作");
String poet = test.getName();
System.out.println(poet);
} }

这个工具的好处在于,不要我们自己分析接口方法的参数类型,因为这个newProxyInstance可以自动分析。

这个例子只能看到一个好处:newProxy简化了代理编码。 依然没有看到什么独有的优点。

2.3 spring的Aop代理

网络上有非常棒的文章,关于aop实现部分的源码:

https://juejin.cn/post/7153214385236738055

所以本小节不再班门弄斧,仅仅是做个搬砖工。

aop工作流程

这个过程和spring的bean工厂、服务器分发器的实现没有太大的区别。特别注意的是,必须和spring的benn工厂结合起来使用。

对于其它网文的关注到上图为止,以下是本人的需要强调的代理实现。

-----------------------------------------------------------------------------------------------------------------------------------------------

--

---------------------------------------------------------------------------

由于本文主要是讨论设计模式,所以这里只需要关注代理有关部分的代码即可。

如前,我们知道aop的实现根据目标对象的不同而有不同的实现:jdk代理实现和cglib创建实例实现。 而代理发生再jdk代理中。

无论哪一种aop被代理对象的实现,都是基于 org.springframework.aop.framework.AopProxy

以下是AopProxy的类层次图:

看下org.springframework.aop.framework.JdkDynamicAopProxy代码:

/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ package org.springframework.aop.framework; import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List; import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.springframework.aop.AopInvocationException;
import org.springframework.aop.RawTargetAccess;
import org.springframework.aop.TargetSource;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.DecoratingProxy;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; /**
* JDK-based {@link AopProxy} implementation for the Spring AOP framework,
* based on JDK {@link java.lang.reflect.Proxy dynamic proxies}.
*
* <p>Creates a dynamic proxy, implementing the interfaces exposed by
* the AopProxy. Dynamic proxies <i>cannot</i> be used to proxy methods
* defined in classes, rather than interfaces.
*
* <p>Objects of this type should be obtained through proxy factories,
* configured by an {@link AdvisedSupport} class. This class is internal
* to Spring's AOP framework and need not be used directly by client code.
*
* <p>Proxies created using this class will be thread-safe if the
* underlying (target) class is thread-safe.
*
* <p>Proxies are serializable so long as all Advisors (including Advices
* and Pointcuts) and the TargetSource are serializable.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Rob Harrop
* @author Dave Syer
* @author Sergey Tsypanov
* @see java.lang.reflect.Proxy
* @see AdvisedSupport
* @see ProxyFactory
*/
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { /** use serialVersionUID from Spring 1.2 for interoperability. */
private static final long serialVersionUID = 5531744639992436476L; /*
* NOTE: We could avoid the code duplication between this class and the CGLIB
* proxies by refactoring "invoke" into a template method. However, this approach
* adds at least 10% performance overhead versus a copy-paste solution, so we sacrifice
* elegance for performance. (We have a good test suite to ensure that the different
* proxies behave the same :-)
* This way, we can also more easily take advantage of minor optimizations in each class.
*/ /** We use a static Log to avoid serialization issues. */
private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class); /** Config used to configure this proxy. */
private final AdvisedSupport advised; private final Class<?>[] proxiedInterfaces; /**
* Is the {@link #equals} method defined on the proxied interfaces?
*/
private boolean equalsDefined; /**
* Is the {@link #hashCode} method defined on the proxied interfaces?
*/
private boolean hashCodeDefined; /**
* Construct a new JdkDynamicAopProxy for the given AOP configuration.
* @param config the AOP configuration as AdvisedSupport object
* @throws AopConfigException if the config is invalid. We try to throw an informative
* exception in this case, rather than let a mysterious failure happen later.
*/
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
Assert.notNull(config, "AdvisedSupport must not be null");
if (config.getAdvisorCount() == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
throw new AopConfigException("No advisors and no TargetSource specified");
}
this.advised = config;
this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(this.proxiedInterfaces);
} @Override
public Object getProxy() {
return getProxy(ClassUtils.getDefaultClassLoader());
} @Override
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
}
return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
} /**
* Finds any {@link #equals} or {@link #hashCode} method that may be defined
* on the supplied set of interfaces.
* @param proxiedInterfaces the interfaces to introspect
*/
private void findDefinedEqualsAndHashCodeMethods(Class<?>[] proxiedInterfaces) {
for (Class<?> proxiedInterface : proxiedInterfaces) {
Method[] methods = proxiedInterface.getDeclaredMethods();
for (Method method : methods) {
if (AopUtils.isEqualsMethod(method)) {
this.equalsDefined = true;
}
if (AopUtils.isHashCodeMethod(method)) {
this.hashCodeDefined = true;
}
if (this.equalsDefined && this.hashCodeDefined) {
return;
}
}
}
} /**
* Implementation of {@code InvocationHandler.invoke}.
* <p>Callers will see exactly the exception thrown by the target,
* unless a hook method throws an exception.
*/
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource;
Object target = null; try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
return hashCode();
}
else if (method.getDeclaringClass() == DecoratingProxy.class) {
// There is only getDecoratedClass() declared -> dispatch to proxy config.
return AopProxyUtils.ultimateTargetClass(this.advised);
}
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
} Object retVal; if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
} // Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null); // Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); // Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// We need to create a method invocation...
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
} // Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target &&
returnType != Object.class && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this" and the return type of the method
// is type-compatible. Note that we can't help if the target sets
// a reference to itself in another returned object.
retVal = proxy;
}
else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
} /**
* Equality means interfaces, advisors and TargetSource are equal.
* <p>The compared object may be a JdkDynamicAopProxy instance itself
* or a dynamic proxy wrapping a JdkDynamicAopProxy instance.
*/
@Override
public boolean equals(@Nullable Object other) {
if (other == this) {
return true;
}
if (other == null) {
return false;
} JdkDynamicAopProxy otherProxy;
if (other instanceof JdkDynamicAopProxy) {
otherProxy = (JdkDynamicAopProxy) other;
}
else if (Proxy.isProxyClass(other.getClass())) {
InvocationHandler ih = Proxy.getInvocationHandler(other);
if (!(ih instanceof JdkDynamicAopProxy)) {
return false;
}
otherProxy = (JdkDynamicAopProxy) ih;
}
else {
// Not a valid comparison...
return false;
} // If we get here, otherProxy is the other AopProxy.
return AopProxyUtils.equalsInProxy(this.advised, otherProxy.advised);
} /**
* Proxy uses the hash code of the TargetSource.
*/
@Override
public int hashCode() {
return JdkDynamicAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode();
} }

重点就是:

 @Override
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
}
return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}

重点语句:Proxy.newProxyInstance

再看看执行aop中原来的方法的有关代码:

以下是org.springframework.aop.framework.ReflectiveMethodInvocation的proceed()方法,执行目标对象原方法。

@Override
@Nullable
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
} Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher dm) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}

2.4 aop为什么用代理实现

前面我们提到设计的时候为什么要代理,这一般是因为:

不方便接触目标、保持灵活、增强

而代理可以实现aop的几个要求:

  • aop通过代理可以动态访问成千上万的对象,而不需要写无数的if else之类的直接接触目标
  • aop能够访问所有符合规范的目标对象,足够灵活,只要设计者按照规范来设计代码
  • 增强-aop是典型的增强(通产是增强)

如果不用代理,能不能实现aop呢?可以的,例如Cglib,但这可以看作另外一种代理。

小结:spring的伟大是基于bean工厂,基于ioc。

在bean工厂的基础商,通过设置无数的门卡实现各种各样的功能。

基于java的注解,反射和设计模式。

话说回来,如果不会反射进行适当的提升,那么spring的aop的想能还是很一般般的。

所以,如果基于spring的设计程序,那么尽量不要用于需要太高性能的环节,此外应该尽量不要用aop。

想象下每辆在高速路商行驶的车都要停下来检查下,车能开得快吗?

spring与设计模式之三代理模式的更多相关文章

  1. Java设计模式之代理模式(静态代理和JDK、CGLib动态代理)以及应用场景

    我做了个例子 ,需要可以下载源码:代理模式 1.前言: Spring 的AOP 面向切面编程,是通过动态代理实现的, 由两部分组成:(a) 如果有接口的话 通过 JDK 接口级别的代理 (b) 如果没 ...

  2. java设计模式6——代理模式

    java设计模式6--代理模式 1.代理模式介绍: 1.1.为什么要学习代理模式?因为这就是Spring Aop的底层!(SpringAop 和 SpringMvc) 1.2.代理模式的分类: 静态代 ...

  3. C#设计模式(13)——代理模式(Proxy Pattern)

    一.引言 在软件开发过程中,有些对象有时候会由于网络或其他的障碍,以至于不能够或者不能直接访问到这些对象,如果直接访问对象给系统带来不必要的复杂性,这时候可以在客户端和目标对象之间增加一层中间层,让代 ...

  4. 乐在其中设计模式(C#) - 代理模式(Proxy Pattern)

    原文:乐在其中设计模式(C#) - 代理模式(Proxy Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 代理模式(Proxy Pattern) 作者:webabcd 介绍 为 ...

  5. 设计模式之代理模式之二(Proxy)

    from://http://www.cnblogs.com/xwdreamer/archive/2012/05/23/2515306.html 设计模式之代理模式之二(Proxy)   0.前言 在前 ...

  6. 夜话JAVA设计模式之代理模式(Proxy)

    代理模式定义:为另一个对象提供一个替身或者占位符以控制对这个对象的访问.---<Head First 设计模式> 代理模式换句话说就是给某一个对象创建一个代理对象,由这个代理对象控制对原对 ...

  7. GOF23设计模式之代理模式

    GOF23设计模式之代理模式 核心作用:通过代理,控制对对象的访问.可以详细控制访问某个(某类)对象的方法,在调用这个方法前做前置处理,调用这个方法后做后置处理(即:AOP的微观实现) AOP(Asp ...

  8. C#设计模式:代理模式(Proxy Pattern)

    一,什么是C#设计模式? 代理模式(Proxy Pattern):为其他对象提供一种代理以控制对这个对象的访问 二,代码如下: using System; using System.Collectio ...

  9. js设计模式——1.代理模式

    js设计模式——1.代理模式 以下是代码示例 /*js设计模式——代理模式*/ class ReadImg { constructor(fileName) { this.fileName = file ...

  10. spring设计模式_代理模式

    代理模式应该是Spring核心设计模式之一了 先说下代理模式特性: 1.有代理人和被代理人 2.对于被代理的人来说,这件事情是一定要做的,但是我又不想做,所有就找代理人来做. 3.需要获取到被代理人的 ...

随机推荐

  1. [FAQ] Argument 3 passed to Lcobucci\JWT\Signer\Hmac::doVerify() must be an instance of Lcobucci\JWT\Signer\Key, null given

    出现这个错误,说明没有找到 key,在使用 laravel-jwt 之前需要生成加密 key,使用: $ php artisan jwt:secret Link:https://www.cnblogs ...

  2. GitHub Action 新上线 WPF .NET Core 自动构建模板

    在很土豪的微软免费给大家提供 GitHub 的构建服务器受到了小伙伴们的一堆好评之后,微软最近推出了 WPF 的 .NET Core 版本的模板,可以快速上手 WPF 项目的自动构建,支持自动进行单元 ...

  3. 2018-11-14-git无法pull仓库refusing-to-merge-unrelated-histories

    title author date CreateTime categories git无法pull仓库refusing to merge unrelated histories lindexi 201 ...

  4. Raft 共识算法2-领导者选举

    Raft 共识算法2-领导者选举 Raft算法中译版地址:https://object.redisant.com/doc/raft中译版-2023年4月23日.pdf 英原论文地址:https://r ...

  5. CPU是什么?

    在程序是怎样跑起来的这本书中我们首先被询问的一个问题是"程序是什么?它是有什么组成的?而CPU又与程序有什么关系呢?",若我们能知道前两个,其实更容易将你带入讨论"CPU ...

  6. 【转载】超级系统工具Sysdig,比 strace、tcpdump、lsof 加起来还强大

    可以用sysdig命令做很多很酷的事情 网络 查看占用网络带宽最多的进程 sysdig -c topprocs_net 显示主机192.168.0.1的网络传输数据 as binary: sysdig ...

  7. C#库dll配置文件App.config数据库连接项connectionStrings

    原文地址:https://www.zhaimaojun.top/Note/5464967 网上一大堆的都是在说怎么修改项目文件,试过了不行,因为里面涉及到vs版本和安装目录等问题,不同的设备配置是不同 ...

  8. 在Biwen.QuickApi中整合一个极简的发布订阅(事件总线)

    闲来无聊在我的Biwen.QuickApi中实现一下极简的事件总线,其实代码还是蛮简单的,对于初学者可能有些帮助 就贴出来,有什么不足的地方也欢迎板砖交流~ 首先定义一个事件约定的空接口 public ...

  9. log4j的配置详解

    参考文章:https://www.jianshu.com/p/ccafda45bcea 引入log4j: 在项目中单独使用log4j进行日志的输出: maven依赖: <dependency&g ...

  10. 用 C 语言开发一门编程语言 — 交互式解释器

    目录 文章目录 目录 前言 环境 编译型 vs 解释型 实现交互式解释器 使用 GNU Readline 函数库 前言 通过开发一门类 Lisp 的编程语言来理解编程语言的设计思想,本实践来自著名的& ...