装饰器模式java_Java 8的装饰器模式

装饰器模式java

在最近的一篇文章中,我描述了装饰器模式如何挽救了我的一天。 我给出了一个小代码段,其中包含创建装饰器的最简单方法,但承诺Java 8会有更好的方法。

这里是:

用Java 8装饰

HyperlinkListener listener = this::changeHtmlViewBackgroundColor;
listener = DecoratingHyperlinkListener.from(listener).onHoverMakeVisible(urlLabel).onHoverSetUrlOn(urlLabel).logEvents().decorate(l -> new OnActivateHighlightComponent(l, urlLabel)).decorate(OnEnterLogUrl::new);

我将在文章的其余部分中说明如何到达那里。

我在GitHub上创建了一个小示例项目 ,我将从这里重复引用。 我只建议您检查一下它,因为它提供了更多详细信息。 它是公共领域 ,因此可以不受限制地使用该代码。

为了继续我的上一篇文章,它使用Swing的HyperlinkListener作为装饰的基础。 由于该接口不是通用接口,并且仅具有一个仅带有一个参数的方法(对于lambda表达式而言非常好!),因此具有使接口保持简单的其他优点。

总览

像其他帖子一样,该帖子也没有尝试教授模式本身。 (不过,我找到了另一个很好的解释 。)相反,它推荐了一种在Java 8中实现它的方法,以使其使用起来非常方便。 因此,该帖子严重依赖Java 8功能,尤其是默认方法和lambda表达式 。

这些图只是草图,并省略了许多细节。 更完整的是容易找到的 。

香草

装饰图案

在模式的通常实现中,存在一个接口(上面称为Component ),该接口将通过“常规”类以及所有装饰器以常规方式实现。

抽象装饰器类

装饰器通常从中间抽象基类( AbstractDecorator )继承,从而简化了实现。 它使用另一个组件作为构造函数参数,并通过将所有调用转发给接口来实现接口本身。 因此,装饰组件的行为不变。

现在由子类实际更改它。 他们通过有选择地重写那些他们想改变其行为的方法来做到这一点。 这通常包括对装饰组件的调用。

装饰器的创建

通常,不使用特殊技术来创建装饰器。 只是简单的构造函数。 使用复杂的装饰器,您甚至可以使用工厂。

我是静态构造方法的忠实拥护者,因此我使用它们并将构造方法设为私有。 为了使这些方法的调用者不了解任何细节,我将这些方法的返回类型声明为Component ,而不是装饰器的更详细类型。 例如,这可以在LogEventsToConsole中看到。

我的建议改变了装饰器的创建方式。

使用Java 8

装饰器模式Java8

要使用Java 8的所有功能,我建议为所有装饰器添加一个特殊的接口DecoratingComponent 。 装饰器的抽象超类实现了该接口,但像以前一样,仅保留对Component的引用。

重要的是要注意,由于新接口的定义(请参见下文),混凝土装饰器没有任何变化。 它们在模式的两种实现中都是完全相同的。 抽象类实际上也没有任何变化(请参见下文),因此切换到该解决方案不会产生明显的成本。

新介面

新接口DecoratingComponent扩展了基本组件接口,并为装饰器提供了工厂方法。 这些是静态或默认/防御方法(因此它们已经实现,如果可以的话将是最终方法),并且不应声明任何抽象方法。 这样,新接口不会在继承树后面的实现上增加额外的负担。

关于以下代码示例:通用示例仅是为该帖子创建的。 涉及超链接侦听器的对象来自演示应用程序 。 最值得注意的是DecoratingHyperlinkListener ( 到源文件的链接 ),它扩展了Swing的HyperlinkListener 。

方法

接口本身实际上非常简单,由三种类型的方法组成。

适配器

若要快速从Component移至DecoratingComponent ,接口应具有静态方法,该方法采用第一个方法,然后返回后者。 由于DecoratingComponent扩展了Component且未添加抽象方法,因此这很简单。 只需创建一个匿名实现,并将所有调用转发到已适配的component

通用方法如下所示:

静态适配器方法

static DecoratingComponent from(Component component) {DecoratingComponent adapted = new DecoratingComponent() {@Overridepublic SomeReturn someMethod(SomeArgument argument) {return component.someMethod(argument);}// ... more methods here ...};return adapted;
}

在使用DecoratingHyperlinkListener情况下,它要容易得多,因为它是一个功能接口,因此可以使用lambda表达式:

'DecoratingHyperlinkListener'中的静态适配器方法

static DecoratingHyperlinkListener from(HyperlinkListener listener) {return event -> listener.hyperlinkUpdate(event);
}

通用装饰

这是接口的基本方法:

default DecoratingComponent decorate(Function<? super DecoratingComponent, ? extends DecoratingComponent>decorator) {return decorator.apply(this);
}

它从一个装饰组件到另一个装饰组件接受一个函数作为参数。 它将功能应用于自身以创建装饰实例,然后将其返回。

可以在整个代码中使用此方法,以一种简单易读的方式装饰任何组件:

用'DecoratingComponent'装饰

Component some = ...;
DecoratingComponent decorated = DecoratingComponent// create an instance of 'DecoratingComponent' from the 'Component'.from(some)// now decorate it.decorate(component -> new MyCoolComponentDecorator(component, ...));// if you already have an instance of 'DecoratingComponent', it get's easier
decorated = decorated.decorate(component -> new MyBestComponentDecorator(component, ...));// constructor references are even clearer (but cannot always be used)
decorated = decorated.decorate(MyBestComponentDecorator::new);

混凝土装饰

您还可以添加使用具体装饰器装饰实例的方法:

“ DecoratingHyperlinkListener”中的混凝土装饰

default DecoratingHyperlinkListener logEvents() {return LogEventsToConsole.decorate(this);
}default DecoratingHyperlinkListener onHoverMakeVisible(JComponent component) {return OnHoverMakeComponentVisible.decorate(this, component);
}

它们使装饰非常简洁易读:

用'DecoratingComponent'装饰

DecoratingComponent decorated = ...
decorated = decorated.logEvents();

但是,是否应真正添加这些方法仍有待商de。 尽管它们非常方便,但是当它们创建循环依赖关系时,可以对它们进行强烈的争论。 装饰者不仅知道接口(它们是通过抽象超类间接实现的),现在接口也知道其实现。 通常,这是刺激性的代码气味。

最终召集尚未结束,但我建议采取务实的中间方式。 我让该接口知道存在于同一包中的实现。 这将是通用的,因为它们没有引用其余代码中的任何具体内容。 但是我不会让我知道在系统深处创建的每个疯狂装饰器。 (当然,除非将其称为the_kraken,否则我不会将所有这些装饰器都添加到同一包中。)

为什么需要额外的接口?

是的,是的,所有那些Java 8功能都非常不错,但是您不能简单地将这些方法添加到AbstractDecorator吗? 好问题!

当然,我可以在这里添加它们。 但由于两个原因,我不喜欢这种解决方案。

单一责任原则

首先,这将模糊类的职责。 新接口负责装饰Component实例,抽象超类负责启用装饰器的轻松实现。

这些不是相同的事物,它们不会因相同的原因而改变。 每当必须包含新的装饰器时,新的界面可能就会更改。 每当Component更改时,抽象类都会更改。

类型层次结构

如果将这些方法添加到AbstractDecorator ,则只能在此类实例上调用它们。 因此,所有装饰器都必须从该类继承,这限制了将来实现的范围。 谁知道,也许出现了一些非常好的理由,为什么另一个类不能成为AbstractDecorator

更糟糕的是,所有装饰器都必须公开一个事实,即它们是AbstractDecorator 。 突然有一个抽象类,它只是为了简化实现而创建的,它遍及整个代码库。

其他差异

除了引入新界面之外,这种模式的变化不会有太大变化。

对抽象装饰器类的更改

如果可以访问该类,则应让它实现DecoratingComponent而不是Component 。 由于没有引入新的抽象方法,因此不需要进一步的更改。 上面的UML图中显示了这一点。

如果您不能更改类,则装饰器将仅实现Component 。 这将使您避免使用其构造函数来创建将组件映射到装饰组件的函数。 由于需要将该函数作为decorate方法的参数,因此必须将该方法更改为如下所示:

通用装饰

// note the more general second type of the 'Function' interface
default DecoratingComponent decorate(Function<? super DecoratingComponent, ? extends Component> decorator) {// create the decorated instance as beforeComponent decorated = decorator.apply(this);// since it is no 'DecoratingComponent' use 'from' to turn it into onereturn from(decorated);
}

装饰者的变化

无需更改这些类。 当然,除非您是使用静态工厂方法的那些疯狂的人之一。 比您必须确保它们将其返回类型声明为DecoratingComponent否则您将处于与抽象超类无法实现新接口的情况相同的情况。 如果您不能更改装饰器类,则此处使用相同的解决方案。

因此,让我们从上方再次查看代码段:

用Java 8装饰

// create a 'HyperlinkListener' with a method reference
HyperlinkListener listener = this::changeHtmlViewBackgroundColor;
// decorate that instance with different behaviors
// (note that each call actually returns a new instance
//  so the result has to be assigned to a variable)
listener = DecoratingHyperlinkListener// adapt the 'HyperlinkListener' to be a 'DecoratingHyperlinkListener'// (looks better if it is not on its own line).from(listener)// call some concrete decorator functions.onHoverMakeVisible(urlLabel).onHoverSetUrlOn(urlLabel).logEvents()// call the generic decorator function with a lambda expression.decorate(l -> new OnActivateHighlightComponent(l, urlLabel))// call the generic decorator function with a constructor reference.decorate(OnEnterLogUrl::new);

反射

我们了解了如何使用Java 8的静态和默认接口方法为装饰器模式创建流畅的API。 它使代码同时更加简洁和可读性,同时又不干扰模式的机制。

正因为如此,我们使用默认的方法来创建特质有关其作者Brian Goetz写道 :

了解默认方法的关键是,主要的设计目标是接口演变 ,而不是“将接口转变为(中等)特性”

抱歉,Brian,这太诱人了。 ;)

对装饰器模式有一些见解? 想要改善我的想法还是批评它? 然后发表评论! 并且不要忘记在GitHub上签出代码 。

翻译自: https://www.javacodegeeks.com/2015/01/the-decorator-pattern-with-java-8.html

装饰器模式java

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/338435.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言-反转字符串

实例代码// //实现功能&#xff1a;输入一个字符串&#xff0c;然后将该字符串反向输出 //#include "stdio.h" #include "string.h"#define N 50void convert_str(char str[N]);void convert_str(char str[N]){int j;char temp;for (int i 0; i < strl…

信捷步进指令的使用_步进电机驱动器的模式

步进电动机和步进电动机驱动器构成步进电机驱动系统。步进电动机驱动系统的性能&#xff0c;不但取决于步进电动机自身的性能&#xff0c;也取决于步进电动机驱动器的优劣。对步进电动机驱动器的研究几乎是与步进电动机的研究同步进行的。步进电机驱动器有三种基本的步进电机驱…

jersey客户端_每个客户使用Jersey处理的Cookie

jersey客户端许多REST服务会将cookie用作身份验证/授权方案的一部分。 这是一个问题&#xff0c;因为默认情况下&#xff0c;旧的Jersey客户端将使用单例CookieHandler.getDefault &#xff0c;大多数情况下该值为null&#xff0c;如果不为null&#xff0c;则在多线程服务器环境…

C语言灵魂篇|指针作为函数返回值

C语言允许函数的返回值是一个指针&#xff08;地址&#xff09;&#xff0c;我们将这样的函数称为指针函数。下面的例子定义了一个函数 strlong()&#xff0c;用来返回两个字符串中较长的一个&#xff1a;#include #includechar *strlong(char *str1, char *str2){ if(strlen(s…

css 样式尾部带感叹号是什么意思_CSS书写规范

推荐大家看看百度FEX前端团队和腾讯AlloyTeam前端团队的CSS代码规范。fex-team/styleguide​github.comCode Guide by AlloyTeam​alloyteam.github.io1. 样式属性顺序单个样式规则下的属性在书写时&#xff0c;应按功能进行分组&#xff0c;组之间需要有一个空行。同时要以Pos…

c++返回指针时候注意提防_Java 8陷阱–提防Files.lines()

c返回指针时候注意提防Java8中有一个非常不错的新功能&#xff0c;它使您可以在一个内衬中从文件中获取字符串流。 List lines Files.lines(path).collect(Collectors.toList());您可以像处理任何其他Stream一样操作Stream&#xff0c;例如&#xff0c;您可能需要filter&…

C语言精髓篇|函数的参数和返回值

如果把函数比喻成一台机器&#xff0c;那么参数就是原材料&#xff0c;返回值就是最终产品&#xff1b;从一定程度上讲&#xff0c;函数的作用就是根据不同的参数产生不同的返回值。函数的参数在函数定义中出现的参数可以看做是一个占位符&#xff0c;它没有数据&#xff0c;只…

oc引导win方法_[OC更新]机械革命X1/X6TIS标压测试版更新

加关注这种话银家怎么好意思说出口嘛更新机型机械革命X1 i5-7300hq机械革命X1 i7-7700hq机械革命X6tis i5-7300hq机械革命X6tis i7-7700hq更新内容基于OC0.6.2 MOD版本编译修复WIN下电脑被识别为MBP导致电竞中心打不开问题修复INTEL网卡在BIG SUR下不识别问题禁用secureboot mo…

java 打开gc日志_在运行时打开GC日志记录

java 打开gc日志总是有下一个JVM表现不佳。 而且&#xff0c;您内心深知&#xff0c;如果您只有少数启动选项可以公开一些有关正在发生的事情的信息&#xff0c;那么您可能就有机会真正修复该死的东西。 但是不&#xff0c;您需要的标志&#xff08; -XX&#xff1a; HeapDumpO…

C 和C语言条件运算符的区别

条件运算符&#xff08;conditional operator&#xff09;有时候也称为三元运算符&#xff08;ternary operator&#xff0c;或者trinary operator&#xff09;&#xff0c;因为它是唯一需要 3 个操作数的运算符&#xff1a;条件 ? 表达式1 : 表达式2条件运算操作会首先计算条…

oracle 执行带参数的sql语句_当用EXECUTE IMMEDIATE执行SQL语句中的参数个数也是动态的?用什么方法实现?...

当用EXECUTE IMMEDIATE执行SQL语句中的参数个数也是动态的&#xff1f;用什么方法实现&#xff1f;描述详细一点就是&#xff1a;在要执行的SQL语句中所用到(: parameter)这种参数的个数&#xff0c;因具体条件不同&#xff0c;而不同&#xff0c;而在执行语句EXECUTE IMMEDIAT…

xsl调用java方法传参_Java中的XSL转换:一种简单的方法

xsl调用java方法传参XSL转换 &#xff08;XSLT&#xff09;是将一个XML文档转换为另一个XML文档的强大机制。 但是&#xff0c;在Java中&#xff0c;XML操作相当冗长和复杂。 即使是简单的XSL转换&#xff0c;也必须编写几十行代码—如果需要适当的异常处理和日志记录&#xff…

C 运算符和语句总结

运算符&#xff1a; C 表达式中的左值和右值&#xff1a;当一个对象被用作左值时&#xff0c;用的是对象的身份&#xff08;内存中的位置&#xff09;。当作为右值时&#xff0c;用的是对象的值&#xff08;内容&#xff09;。或者说&#xff0c;lvalue:具有存贮性质的对象&…

# 遍历结构体_C#学习笔记05--枚举/结构体

一.枚举当变量的取值范围是固定的几个, 例如性别--男,女; 英雄类型 -- 法师, 刺客.战士, 射手等等. 这时就可以使用枚举类型, 会更加简洁方便.1.1.定义:访问修饰符 enum 枚举类型名 {成员1,成员2,成员3,... }public enum Days{Mon 1,Tue,Wed,Thu,Fri,Sat,Sun}enum: 是枚举的…

单例嵌套 ios_嵌套类型的前5个用例

单例嵌套 ios前几天&#xff0c;关于reddit进行了有趣的讨论&#xff0c;即静态内部类。 什么时候太多&#xff1f; 首先&#xff0c;让我们回顾一下Java的基本历史知识。 Java语言提供了四个级别的嵌套类 &#xff0c;通过“ Java语言”&#xff0c;我的意思是这些构造仅是“…

C语言中枚举enum的用法

本文举例说明C语言中enum枚举关键字的用法。用来同时定义多个常量利用enum定义月份的例子如下。#include enum week {Mon1,Tue,Wed,Thu,Fri,Sat,Sun}; int main() {printf("%d",Tue); return 0; }这样定义Mon的值为1之后&#xff0c;Tue的值就被默认定义为2&#…

苹果更新未知错误17_iOS 13 新功能,静音未知来电

果粉俱乐部让科技更好的服务生活点击上方「蓝字」加入我们iOS 13 正式版系统已经推出了快三周时间&#xff0c;苹果在新系统当中带来了诸多功能改进&#xff0c;包括大家盼望已久的深色模式&#xff0c;新的音量调节设置&#xff0c;自定义流量下载限制等等。除此之外&#xff…

C/C 语言中extern的用法

声明外部变量现代编译器一般采用按文件编译的方式&#xff0c;因此在编译时&#xff0c;各个文件中定义的全局变量是互相透明的&#xff0c;也就是说&#xff0c;在编译时&#xff0c;全局变量的可见域限制在文件内部。下面举一个简单的例子。创建一个工程&#xff0c;里面含有…

设置公共请求参数_封装一个useFetch实现页面销毁取消请求

前端业务经常会出现这样一类问题&#xff0c;当用户网速过慢或是其他特殊情况下&#xff0c;该页面的请求还未完成&#xff0c;用户就已经点击其他页面跳出去了。理想状态下请求也是应该终止掉的&#xff0c;所以我们应该想办法将请求和页面卸载关联在一起。1 使用AbortControl…

rx 异步执行耗时_使用rx-java的异步抽象

rx 异步执行耗时对我而言&#xff0c;使用Rx-java的一大好处是&#xff0c;无论底层调用是同步还是异步&#xff0c;因此代码看起来都完全相同&#xff0c;因此该条目的标题也是如此。 考虑一个非常简单的客户代码用例&#xff0c;它执行三个缓慢运行的调用并将结果合并到一个…