Java 8的装饰器模式

在最近的一篇文章中,我描述了装饰器模式如何拯救了我的一天。 我给出了一个小代码段,其中包含创建装饰器的最简单方法,但承诺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写道 :

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

对不起,布莱恩,这太诱人了。 ;)

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

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

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

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

相关文章

WPF中使用流文档灵活地显示内容

WPF中使用流文档灵活地显示内容 by: Markus Egger form: http://msdn.microsoft.com/msdnmag/issues/07/08/wpf/default.aspx?loczh Windows Presentation Foundation (WPF) 提供了一系列功能。事实上&#xff0c;功能…

canvas图表(4) - 散点图

原文地址&#xff1a;canvas图表(4) - 散点图 今天开始完成散点图&#xff0c;做完这一节&#xff0c;我的canvas图表系列就算是完成了&#xff0c;毕竟平时最频繁用到的就是这几类图表了&#xff1a;柱状&#xff0c;折线&#xff0c;饼图&#xff0c;散点。经过编写canvas图表…

Java8排序–性能陷阱

Java 8带来了lambda的所有优点&#xff0c;使我们能够使用声明式样式进行编程。 但这真的免费吗&#xff1f; 我们是否应该担心必须为新的编程功能付出的代价&#xff1f; 这是一个我们可能要担心的例子。 考虑对这个简单类的实例进行排序&#xff1a; private static class…

词频统计工程相关

&#xff08;the format of this article is from SkYjoKEr&#xff09; //开始干之前 模块1、WordClass 一个存放单词以及实现相关操作的类&#xff0c;其中单词以二元组<word, freq>的形式存储。 &#xff08;20min&#xff09; 2、WordCounter 完成单词统计&#xff0…

canvas图形编辑器

原文地址&#xff1a;http://jeffzhong.space/2017/11/02/drawboard/   使用canvas进行开发项目&#xff0c;我们离不开各种线段&#xff0c;曲线&#xff0c;图形&#xff0c;但每次都必须用代码一步一步去实现&#xff0c;显得非常麻烦。有没有一种类似于PS&#xff0c;CAD…

2015年Java 8强势开始

JDK 8从2015年开始&#xff0c;其博客文章和文章的受欢迎程度将激增。 这与Java本月自动升级到JDK 8恰好吻合。 在这篇文章中&#xff0c;我列出并简要描述了2015年已经发布的有关JDK 8的众多文章和文章。 JDK 8 Streams在最近的帖子中很受欢迎。 我在2015年发表的第一篇博文是…

富文本编辑器、日期选择器、软件天堂、防止XSS攻击、字体icon、转pdf

【超好用的日期选择器】 Layui&#xff1a;http://www.layui.com/laydate/ 备注&#xff1a;日期选择器&#xff0c;用过很多很多&#xff0c;自己也写过一些&#xff1b;相信这个简单而又不简单的选择器&#xff0c;能够给你多些美好的时光 【很不错的几个富文本编辑器】 …

GIS开源程序收集(转载)

分类包括&#xff1a;GIS基础函数库、GIS控件、GIS桌面程序、GIS数据引擎、WEBGIS浏览器端程序、WEBGIS服务器程序、GPS相关程序&#xff0c;其它分类 派系&#xff1a;“NET”派系&#xff0c;“C”派系&#xff0c;“Java”派系&#xff0c;脚本派系&#xff0c;其它派系 “N…

Sacrilege –自定义SWT滚动条

SWT是本机OS小部件之上的薄抽象层。 如果您打算将应用程序与OS外观很好地集成在一起&#xff0c;那将是一件非常好的事情。 但是&#xff0c;作为一种折衷方案&#xff0c;这种方法大大限制了样式功能。 特别是&#xff0c;我感觉到本机SW​​T滚动条通常会干扰更精细的视图布…

关键字屏蔽-正则

【问题】关键字屏蔽是社交类软件必做的功能&#xff0c;当然了&#xff0c;一般来讲都是产品的中后期来做&#xff1b;不同产品规定不一样&#xff0c;跟着产品运营走&#xff0c;可以的 【方法】我们从技术的角度来看到这个问题&#xff0c;实现一个功能后者说实现一个需求&a…

Sub-Projects in Xcode(Xcode中的子项目)

source:http://www.cocoanetics.com/2011/12/sub-projects-in-xcode/ translation:http://www.xiaojiayi.com/2012/08/15/xcode中的子项目&#xff08;译文&#xff09;/ is work! 转载于:https://www.cnblogs.com/snowleung/archive/2012/09/26/2703250.html

堆上与堆外的内存使用情况

总览 最近有人问我在Java中使用堆内存的好处和智慧。 面临相同选择的其他人可能会对这些答案感兴趣。 堆外内存没什么特别的。 线程堆栈&#xff0c;应用程序代码&#xff0c;NIO缓冲区都在堆外。 实际上&#xff0c;在C和C 中&#xff0c;您只有非托管内存&#xff0c;因为默…

从CSS实现正片叠底看=混合模式mix-blend-mode

兼容性&#xff1a;这个东西说多了也没意思&#xff0c;像HTML5和CSS3这种兼容性时刻变化的东东&#xff0c;我们最好在自己支持的设备上实验&#xff0c;不支持&#xff0c;就在想办法呗&#xff0c;这个东西就是为了方便和好玩 所有属性&#xff1a; mix-blend-mode: normal…

实现对gridview删除行时弹出确认对话框的四种方法

实现对gridview删除行时弹出确认对话框的四种方法 在.net2.0中&#xff0c;实现对gridview删除行时弹出确认对话框的四种方法 1&#xff0c;GridView中如何使用CommandField删除时&#xff0c;弹出确认框? 在VS2005提供的GridView中我们可以直接添加一个CommandField删除列&am…

我最喜欢的Java拼图2 + 1 = 4

这是我当前最喜欢的Java难题。 您如何获取代码来执行此操作&#xff1f; Integer b 2; Integer c 1;System.out.println("bc : " (bc) ); // output: bc : 4 !!Sytem.out.println&#xff08;&#xff09;没有技巧&#xff0c;即您将能够在调试器中看到相同的值。…

CSS3盒模型温故

CSS有一种基础设计模式叫盒模型&#xff0c;定义了Web页面中的元素是如何看做盒子来解析的。每一个盒子有不同的展示界面&#xff0c;下面就来介绍盒模型&#xff0c;主要有一下几种盒模型&#xff1a;inline、inline-block、block、table、absolute position、float。浏览器把…

SSL与WildFly 8和Undertow

我一直在研究WildFly 8的一些安全性主题&#xff0c;偶然发现了一些配置文档&#xff0c;这些文档没有很好地记录。 其中之一是新Web子系统Undertow的TLS / SSL配置。 有许多关于较旧的Web子系统的文档&#xff0c;并且确实仍然可以使用&#xff0c;但是这里是使用新方法进行配…

伸展树

伸展树结合了二叉搜索树BST及二叉平衡树AVL的旋转特点&#xff0c;在每一次访问到某节点时都通过旋转将该节点往上推一位&#xff0c;由于没有保存高度信息因为空间复杂度稍优于二叉平衡树。伸展树的插入&#xff0c;删除&#xff0c;搜索的平均时间复杂度均为o(logn)&#xff…

CSS属性选择器温故-4

1.属性选择器就是通过元素属性来找到元素 2.属性选择器语法 CSS3遵循了惯用的编码规则&#xff0c;通配符的使用提高了样式表的书写效率&#xff0c;也使CSS3的属性选择器更符合编码习惯 3.浏览器兼容性 CSS选择器总结&#xff1a;CSS选择器和jQuery的选择器非常相似&#xff…

如何封装Spring bean

据我所知&#xff0c;Spring Framework除了具有单独的上下文之外&#xff0c;没有提供任何封装Spring bean的机制。 因此&#xff0c;当您在Spring的Inversion of Control容器中注册了公共类时&#xff0c;可以通过相同的上下文配置将其自动连接到任何Spring bean中。 这非常强…